在Linux下使用dkms构建内核模块

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_NAMEPACKAGE_VERSION:与文件夹的命名一致
  • CLEAN:指定的命令是每次build的时候第一条执行的动作
  • AUTOINSTALL=“yes":表示在Linux引导之后DKMS会自动对这个模块执行BuildInstall的动作,当然如果模块已经处于该状态的话,相应的动作是不用再执行的。
  • MAKE[0]:用来设定编译的命令,一般情况下是不用设定的。在本例中,就可以把MAKE[0]这行删掉。但在下面这种情况下就需要设定了。比如,Makefile里有多个target,分别为alldebugrelease等,不指定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
sudo dkms autoinstall

如果还不行,可重新编译:

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

参考:

Licensed under CC BY-NC-SA 4.0
最后更新于 2024-08-26 21:49 CST
网站已稳定运行 小时 分钟
使用 Hugo 构建
主题 StackJimmy 设计