DKMS(Dynamic Kernel Module Support)是由 Dell 公司开发的一套内核模块管理框架,被大多数 Linux 发行版采用。 DKMS 在内核源码树之外做了个拷贝,每当内核更新时可自动重新编译内核模块,对于多个不同的内核版本、模块版本的管理非常便利。
#
1、安装依赖
Fedora/CentOS/RedHat:
1
2
3
|
sudo dnf install kernel-devel-$(uname -r)
sudo dnf install epel-release
sudo dnf install dkms
|
Debian/Ubuntu:
1
2
3
|
sudo apt-get update
sudo apt-get install -y linux-headers-$(uname -r) build-essential libelf-dev
sudo apt-get install dkms
|
#
2、使用 DKMS 自动构建内核模块
使用 DKMS 管理内核模块,需要满足两个要求:
- 将源码放在
/usr/src/<module_name>-<module_version>
目录下;
- 在源码根目录下存在配置正确的
dkms.conf
配置文件。
目录结构如下:
1
2
3
4
|
/usr/src/hello-1.5.0/
├── dkms.conf
├── Makefile
└── hello.c
|
在Makefile
中可使用变量$(KVERSION)
指定内核版本号,这样在执行dkms
时,就可以用“-k
”选项来设定为哪个内核版本编译模块。
#
2.1 hello.c
文件
hello.c
文件内容可填写如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_INFO */
int init_module(void)
{
printk(KERN_INFO "Hello world 1.\n");
/*
* A non 0 return means init_module failed; module can't be loaded.
*/
return 0;
}
void cleanup_module(void)
{
printk(KERN_INFO "Goodbye world 1.\n");
}
module_init(init_module);
module_exit(cleanup_module);
MODULE_AUTHOR("example <abc@example.com>");
MODULE_LICENSE("GPL2");
MODULE_DESCRIPTION("A hello world driver module example");
MODULE_VERSION("1.5.0");
|
#
2.2 Makefile
文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
obj-m += hello.o
#####################################
# Work out where the kernel source is
ifeq (,$(KVER))
KVER=$(shell uname -r)
endif
KERNEL_SEARCH_PATH := \
/lib/modules/$(KVER)/build \
/lib/modules/$(KVER)/source \
/usr/src/linux-$(KVER) \
/usr/src/linux-$($(KVER) | sed 's/-.*//') \
/usr/src/kernel-headers-$(KVER) \
/usr/src/kernel-source-$(KVER) \
/usr/src/linux-$($(KVER) | sed 's/\([0-9]*\.[0-9]*\)\..*/\1/') \
/usr/src/linux
# prune list to those containing a configured kernel source tree
test_dir = $(shell [ -e $(dir)/include/config ] && echo $(dir))
KERNEL_SEARCH_PATH := $(foreach dir, $(KERNEL_SEARCH_PATH), $(test_dir))
ifneq (,$(INSTALL_MOD_PATH))
DEPMOD_PATH=--basedir=$(INSTALL_MOD_PATH)
endif
# Use first one
ifeq (,$(KSRC))
KSRC := $(firstword $(KERNEL_SEARCH_PATH))
endif
ifeq (,$(KSRC))
$(error Could not find kernel source)
endif
EXTRA_CFLAGS += $(CFLAGS_EXTRA)
all:
make -C $(KSRC) M=$(PWD) modules
clean:
make -C $(KSRC) M=$(PWD) clean
|
#
2.3 dkms.conf
文件
该文件内容如下:
1
2
3
4
5
6
7
8
|
PACKAGE_NAME="hello"
PACKAGE_VERSION="0.1"
CLEAN="make clean"
AUTOINSTALL="yes"
MAKE[0]="make all KVERSION=$kernelver"
BUILT_MODULE_NAME[0]="hello"
DEST_MODULE_LOCATION[0]="/updates"
|
PACKAGE_NAME
和PACKAGE_VERSION
:与文件夹的命名一致
CLEAN
:指定的命令是每次build的时候第一条执行的动作
AUTOINSTALL=“yes"
:表示在Linux引导之后DKMS会自动对这个模块执行Build
和Install
的动作,当然如果模块已经处于该状态的话,相应的动作是不用再执行的。
MAKE[0]
:用来设定编译的命令,一般情况下是不用设定的。在本例中,就可以把MAKE[0]
这行删掉。但在下面这种情况下就需要设定了。比如,Makefile
里有多个target
,分别为all
、debug
、release
等,不指定MAKE[0]
时,编译会选择第一个target
来执行,也就是make all
,如果想执行make release
来编译,就需要在dkms.conf
里明确设定。
BUILD_MODULE_NAME[0]
:用来指定模块的名称,一般情况下也可以不设定。
DEST_MODULE_LOCATION[0]
:用来设定模块安装的目的地址,本例是"/lib/module/$(KVERSION)/updates
"。
该文件中还可以添加一些其他信息,例如对于一些自制的操作系统,可能需要一些定制化的路径设置,可以在dkms.conf
中添加以下内容以辅助识别:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# Some operation systems may not be recognized by dkms,
# e.g. UOS, Kylin and openEuler. Set default destination
# module location for them.
if [ -r /etc/os-release ]; then
os_id="$(sed -n 's/^ID\s*=\s*\(.*\)$/\1/p' /etc/os-release | tr -d '"')"
fi
if [ -r /etc/os-version ]; then
os_ver="$(sed -n 's/^EditionName\s*=\s*\(.*\)$/\1/p' /etc/os-version)"
fi
if [ "${os_id}" == "UOS" ] && [ "${os_ver}" == "d" ]; then
DEST_MODULE_LOCATION[0]="/updates/dkms"
else
DEST_MODULE_LOCATION[0]="/extra"
fi
|
#
2.4 dkms
测试
#
2.4.1 驱动模块编译安装
将hello-1.5.0
拷贝到目录/usr/src/
。
执行以下命令:
1
2
3
|
sudo dkms add -m hello -v 1.5.0
sudo dkms build -m hello -v 1.5.0
sudo dkms install -m hello -v 1.5.0
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
$ sudo cp hello-1.5.0/ /usr/src/ -r
$ sudo dkms add -m hello -v 1.5.0
Creating symlink /var/lib/dkms/hello/1.5.0/source -> /usr/src/hello-1.5.0
$ sudo dkms build -m hello -v 1.5.0
Sign command: /lib/modules/5.14.0-457.el9.x86_64/build/scripts/sign-file
Signing key: /var/lib/dkms/mok.key
Public certificate (MOK): /var/lib/dkms/mok.pub
Certificate or key are missing, generating self signed certificate for MOK...
Building module:
Cleaning build area...
Building module(s)...
Signing module /var/lib/dkms/hello/1.5.0/build/hello.ko
Cleaning build area...
$ sudo dkms install -m hello -v 1.5.0
hello.ko.xz:
Running module version sanity check.
- Original module
- No original module exists within this kernel
- Installation
- Installing to /lib/modules/5.14.0-457.el9.x86_64/extra/
Adding any weak-modules
depmod.....
|
经过以上同个命令,模块就安装好了。
模块编译过程如果出错,可参考/var/lib/dkms/hello/1.5.0/build/make.log
文件。
可以通过sudo dkms status
命令查看:
1
2
|
$ sudo dkms status
hello/1.5.0, 5.14.0-457.el9.x86_64, x86_64: installed
|
安装完成后默认不会加载模块,可通过命令sudo modprobe hello
加载模块。之后通过modinfo
命令查看模块信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
$ sudo dkms status
hello/1.5.0, 5.14.0-457.el9.x86_64, x86_64: installed
[sxd@hgdpu ixgbe-4.3.15]$ sudo modinfo hello
filename: /lib/modules/5.14.0-457.el9.x86_64/extra/hello.ko.xz
version: 1.5.0
description: A hello world driver module example
license: GPL2
author: example <abc@example.com>
rhelversion: 9.5
srcversion: E5D888FA5A9FAD20048E988
depends:
retpoline: Y
name: hello
vermagic: 5.14.0-457.el9.x86_64 SMP preempt mod_unload modversions
sig_id: PKCS#7
signer: DKMS module signing key
sig_key: 4A:88:EB:73:2F:26:8E:75:C6:16:8B:E5:F8:3F:64:DE:4C:62:82:67
sig_hashalgo: sha512
signature: 16:B7:6E:1F:6E:72:51:CA:FB:BC:C6:DB:A9:64:47:F6:0E:19:92:46:
10:C8:A7:8C:7A:AF:F4:FC:DB:DD:FE:F2:87:79:A3:EE:42:AB:55:7B:
D0:46:6E:C4:A8:92:54:56:44:30:97:59:58:35:74:5A:C1:8F:79:AC:
E1:2D:20:12:2D:0A:B3:8F:FC:A2:18:7C:F5:F1:DA:18:BA:F5:DD:06:
AB:36:97:E7:42:76:5B:4F:C4:33:26:78:F0:EB:35:66:CD:BC:49:66:
E7:FA:99:5E:5B:12:82:86:19:B3:4D:F9:0D:CD:AA:81:4E:91:E7:97:
41:E6:5C:F1:52:7A:2B:9E:6E:7C:8D:50:9B:40:2A:12:28:A2:37:0B:
AE:05:C7:E3:DD:A0:E8:93:B8:83:26:C6:4B:D3:55:1F:63:FB:3C:9F:
95:C8:0B:B5:59:FE:41:7B:CF:A5:37:07:F2:45:B3:15:03:4A:3B:6F:
BB:A9:C4:16:56:77:E2:2E:74:58:05:AB:67:28:C8:1F:66:66:5E:45:
F9:0E:99:54:77:1B:F2:62:CB:6D:0A:63:EC:E3:44:60:6C:60:54:E5:
AB:34:F6:22:EB:D9:03:F6:8D:FF:1B:74:C6:D8:CC:02:03:57:42:5B:
AD:01:7D:48:BB:7F:88:49:0E:1B:E6:F1:86:52:13:12
|
#
2.4.2 移除dkms管理的驱动模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ sudo rmmod hello
$ sudo dkms uninstall -m hello -v 1.5.0
Module hello-1.5.0 for kernel 5.14.0-457.el9.x86_64 (x86_64).
Before uninstall, this module version was ACTIVE on this kernel.
Removing any linked weak-modules
hello.ko.xz:
- Uninstallation
- Deleting from: /lib/modules/5.14.0-457.el9.x86_64/extra/
- Original module
- No original module was found for this module on this kernel.
- Use the dkms install command to reinstall any previous module version.
depmod.....
|
如果要完全移除该模块,可通过命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ sudo dkms remove -m hello -v 1.5.0 --all
Module hello-1.5.0 for kernel 5.14.0-457.el9.x86_64 (x86_64).
Before uninstall, this module version was ACTIVE on this kernel.
Removing any linked weak-modules
hello.ko.xz:
- Uninstallation
- Deleting from: /lib/modules/5.14.0-457.el9.x86_64/extra/
- Original module
- No original module was found for this module on this kernel.
- Use the dkms install command to reinstall any previous module version.
depmod.....
Deleting module hello-1.5.0 completely from the DKMS tree.
|
该命令会删除/var/lib/dkms/
目录下对应的模块信息,但不会删除/usr/src/
目录下对应的模块源码。
#
2.4.3 内核升级测试
升级内核与匹配的头文件:
1
|
sudo dnf install kernel kernel-devel
|
请注意,如果升级到新内核而没有安装匹配的内核头文件,则不会触发 DKMS,并且不会从其源代码重新构建驱动程序,而是会使用新内核附带的原内核驱动程序(如果可用)。
更新完内核后,可看dkms状态:
1
2
3
|
$ sudo dkms status
hello/1.5.0, 5.14.0-457.el9.x86_64, x86_64: installed
hello/1.5.0, 5.14.0-496.el9.x86_64, x86_64: installed
|
之后重启系统,再次查看:
#
3、问题处理
更新到新内核,重启用新内核启动系统后,无法加载内核,提示如下内容:
1
2
3
4
5
6
7
|
$ sudo modprobe hello
modprobe: ERROR: could not insert 'hello': Exec format error
$ sudo dmesg | tail
......
[ 43.012887] kexec_file: kexec_file_load: type:1, start:0x5bff9140 head:0x4 flags:0xa
[ 51.767484] module hello: .gnu.linkonce.this_module section size must match the kernel's built struct module size at run time
|
可尝试执行以下命令并重新加载:
如果还不行,可重新编译:
1
2
3
|
$ sudo dkms build -m hello -v 1.5.0 -k $(uname -r) --force
$ sudo dkms install -m hello -v 1.5.0 -k $(uname -r) --force
|
参考: