Linux 模块管理方法之 DKMS
本文将描述 DKMS 是什么、怎么用、有哪些问题。
一、DKMS 是什么
如果要问 Linux 内核模块如何发布、安装。脑回路的第一反应肯定是 make && insmod。
上述方法可以满足嵌入式场景,因为嵌入式产品的软件是整体发布,包括:内核、模块、软件等交付件。但是在 PC/服务器 领域,各个组件都是互相独立的,如果一个模块基于内核 A 编译并发布,那用户更改内核后,之前发布的内核模块就不能用了。
所以,DELL 发布了 DKMS,全称 Dynamic Kernel Module System。可以做到内核变更后自动编译模块,适配新内核。
二、DKMS 怎么用
DKMS 既是软件包也是规范,使用 DKMS 必须遵守 DKMS 约定的规范。下面我们以一个可编译的内核模块为例说明 DKMS 怎么用。
DKMS 模块代码目录
DKMS 模块代码目录位于 /usr/src/modulename-version
,比如:/usr/src/tinylab-1.0.0
,表示 tinylab 模块的 1.0.0 版本。
DKMS 模块安装
前提:有一个可编译的内核模块,且目录符合 DKMS 规范
root@llc-vpc:/home/llc/tmp/module# tree /usr/src/tinylab-1.0.0/
/usr/src/tinylab-1.0.0/
├── Makefile
└── tinylab.c
0 directories, 2 files
添加 dkms.conf
root@llc-vpc:/usr/src/tinylab-1.0.0# cat dkms.conf
PACKAGE_NAME="tinylab"
PACKAGE_VERSION="1.0.0"
CLEAN="make clean"
MAKE[0]="make all"
BUILT_MODULE_NAME[0]="tinylab"
DEST_MODULE_LOCATION[0]="/updates"
AUTOINSTALL="yes"
字段含义还比较清晰,设置模块名,编译方法。最重要的就是 AUTOINSTALL
字段,表示内核变更时要重新编译模块。
DKMS ADD:将模块纳入 DKMS 管辖
root@llc-vpc:/usr/src/tinylab-1.0.0# dkms add -m tinylab -v 1.0.0
Creating symlink /var/lib/dkms/tinylab/1.0.0/source ->
/usr/src/tinylab-1.0.0
DKMS: add completed.
DKMS INSTALL:编译 && 安装
root@llc-vpc:/usr/src/tinylab-1.0.0# dkms install -m tinylab -v 1.0.0
...
- Installing to /lib/modules/5.4.0-62-generic/updates/dkms/
...
PS:install 命令会自动执行 build,也可以手动执行 build 后,再执行 install。
dkms build -m tinylab -v 1.0.0
dkms install -m tinylab -v 1.0.0
DKMS REMOVE:删除模块产出,同时将模块移出 DKMS 管理
//第一次查看 status,可以看到 tinylab 模块状态为 installed
root@llc-vpc:/usr/src/tinylab-1.0.0# dkms status
tinylab, 1.0.0, 5.4.0-62-generic, x86_64: installed
root@llc-vpc:/usr/src/tinylab-1.0.0#
root@llc-vpc:/usr/src/tinylab-1.0.0# dkms remove -m tinylab -v 1.0.0 --all
//remvoe 后查看 status,什么都看不到,说明 tinylab 模块被移除
root@llc-vpc:/usr/src/tinylab-1.0.0# dkms status
root@llc-vpc:/usr/src/tinylab-1.0.0#
总结
从上面的分步操作中可以看到基于 DKMS 编译、安装模块的几个步骤:ADD、BUILD、INSTALL。
但是上面介绍的命令并不具备产品发布能力,因为总不能全部手动操作吧。所以 DKMS 具备产出 deb/rpm 包的能力。以 deb 包为例,DKMS 产出 deb 包后,通过 dpkg -i module.deb
可以实现:
- 代码自动安装至
/usr/src
目录 - 自动 ADD/BUILD/INSTALL
产出 deb 包的命令: dkms mkdeb -m tinylab -v 1.0.0 --source-only
三、实际使用中的疑问与问题
- 模块如何自动加载
严格说,这不是 DKMS 的问题,而是 Linux 模块自动加载机制的问题。这里简单描述一下。
关于模块机制就简单描述,大家 high level 理解一下即可,不是本文重点。
Linux 基于 module alias 机制实现模块自动加载,module alias 是模块代码中硬性指定的,就像 MODULE_LICENSE
一样。以 PCIE 设备为例,PICE 设备都有自己的 vendor_id、device_id,通过 MODULE_DEVICE_TABLE
为 PCIE 设备驱动模块添加 alias。
当 PCIE 设备添加到系统时会向系统发送 uevent 消息。这里的 uevent 消息除了包括设备号,设备名称外,还包括 module alias。用户态的 udev 机制会基于 uevent 添加设备节点,同时也会根据 uevent 中携带的 module alias 信息进行模块匹配和模块加载。
这也是为什么内核支持了 devtmpfs 后还需要 udev 机制的一个原因。
module alias 的模样
llc@llc-vpc:/usr/src/tinylab-1.0.0$ modinfo e1000
...
alias: pci:v00008086d00002E6Esv*sd*bc*sc*i*
module alias 的含义
alias: pci:v00008086d00002E6Esv*sd*bc*sc*i*
拆开后如下:
v00008086 --- vendor id
d00002E6E --- device id
sv* --- sub vendor id, * 表示任意
sd* --- sub device id, * 表示任意
bc* --- base class, * 表示任意
sc* --- sub class, * 表示任意
i* --- programming interface, * 表示任意,不知道啥意思,很少用
- 内核更新后如何触发 DKMS 编译
DKMS 安装后会生成两个新文件 /etc/kernel/postinst.d/dkms
与 /etc/kernel/header_postinst.d/dkms
,这两个文件内容一样,会执行 dkms_autoinstaller
,将 DKMS 管理的模块编译。在内核安装完成后,即 deb 包安装的 postinst 阶段,会执行 /etc/kernel/postinst.d/
里面的脚本,这样 dkms
脚本就被执行到了。
头文件安装同理。
- 系统有 4.19/5.4 两个内核,基于 DKMS 在 5.4 下编译模块后,重启系统切换到 4.19 内核,模块会重新编译吗?
不会,因为 DKMS 自动编译只能通过内核更新或内核头文件更新触发。
如果需要为系统所有已安装内核同时编译模块,需要修改 /etc/dkms/framework.conf
将 autoinstall_all_kernels
设置为非 0 值。