1. 项目概述:为什么内核驱动编译是道坎?
搞Linux驱动开发,或者仅仅是需要给特定硬件打上补丁的朋友,一定都绕不开“编译内核驱动”这个环节。这听起来像是资深工程师的专属领域,但实际上,无论是为了启用一块新网卡、调试一个外设,还是单纯想了解系统底层是如何工作的,掌握内核驱动的编译方法都是一项非常实用的技能。很多人第一次尝试时,往往会被复杂的Kbuild系统、版本依赖和层出不穷的编译错误劝退,感觉像是在迷宫里打转。
其实,内核驱动的编译并没有想象中那么神秘。它本质上是一套高度自动化但又极其严谨的构建流程。说它自动化,是因为你通常不需要手动指定每一个源文件和链接库;说它严谨,是因为它对你的编译环境、内核版本、配置选项有着近乎苛刻的要求。一个驱动模块(.ko文件)能否成功加载并运行,编译这一步就决定了八成。我见过不少新手,驱动代码逻辑写得没问题,却卡在编译上,反复折腾一两天,最后发现只是少装了一个kernel-headers包,或者Makefile里少写了一个-C参数。
所以,这篇内容的目的,就是帮你把这道坎踏平。我们不谈艰深的驱动原理,就聚焦在“如何正确地编译出一个能用的内核模块”这个实操目标上。我会从最基础的环境准备讲起,带你一步步拆解内核的Kbuild构建系统,然后分别演示“为当前运行的内核编译驱动”和“为特定内核版本编译驱动”这两种最常遇到的场景,最后把那些最容易踩坑的地方和排查技巧掰开揉碎讲清楚。无论你是嵌入式开发者、运维工程师,还是对Linux底层感兴趣的学习者,这套方法都能让你在遇到驱动编译问题时,心里有底,手上有招。
2. 编译环境与内核头文件:打好地基
在动手写Makefile之前,一个正确、完整的编译环境是成功的前提。很多人编译失败,第一步就栽在这里。
2.1 确认当前内核版本与获取头文件
编译内核驱动,核心是要针对某个特定版本的内核源代码进行。最常用的场景就是为你当前正在运行的系统编译驱动。首先,你必须知道你系统的内核版本。
打开终端,运行:
uname -r你会得到一个像5.15.0-91-generic这样的输出。这不仅仅是版本号,后面的-generic是发行版定制后缀,同样重要。
接下来,你需要获取与这个完全一致版本的内核头文件或完整源代码。头文件(位于/usr/src/linux-headers-$(uname -r)/)包含了内核数据结构、函数声明和配置参数,是编译驱动时必须的“接口说明书”。
在Ubuntu/Debian系系统上,安装头文件包非常简单:
sudo apt update sudo apt install linux-headers-$(uname -r)这个命令会安装与你当前运行内核精确匹配的头文件包。安装完成后,你可以在/usr/src/目录下找到对应的文件夹。
注意:
linux-headers包和linux-source包不同。前者只包含编译所需头文件和构建框架,体积较小;后者是完整的内核源代码。对于驱动编译,通常安装linux-headers就足够了。
在RHEL/CentOS/Fedora系系统上,命令略有不同:
sudo yum install kernel-devel-$(uname -r) # 或者使用dnf sudo dnf install kernel-devel-$(uname -r)同样,请确保安装的版本与uname -r的输出完全一致。
2.2 安装必要的编译工具链
仅有头文件还不够,你还需要编译器、链接器等一套完整的工具链。最基本的包括:
- gcc: GNU C编译器,编译内核和驱动的核心工具。
- make: 构建自动化工具,用于解析和执行
Makefile。 - libc-dev: C标准库的开发文件。
在Ubuntu/Debian上,可以一次性安装:
sudo apt install build-essential这个build-essential元包会自动安装gcc,g++,make,libc6-dev等必备工具。
在RHEL/CentOS/Fedora上,对应的组是Development Tools:
sudo yum groupinstall "Development Tools" # 或 sudo dnf groupinstall "Development Tools"2.3 一个关键检查:内核版本与头文件是否匹配?
这是最经典的坑。有时候,系统更新了内核(uname -r显示新版本),但linux-headers包还没来得及安装,或者安装的是旧版本。这会导致编译时找不到正确的头文件路径。
检查方法:
- 确认已安装的头文件包版本:
dpkg -l | grep linux-headers # Debian/Ubuntu rpm -qa | grep kernel-devel # RHEL/CentOS/Fedora - 对比
uname -r的输出与已安装的头文件版本号。必须完全一致,包括后缀。 - 检查头文件目录是否存在:
ls -d /usr/src/linux-headers-$(uname -r)
如果不匹配,你需要手动安装正确版本的头文件,或者重启系统进入新内核后再安装。我曾遇到过服务器自动更新内核后,驱动编译失败,就是因为重启后才触发了头文件包的安装。
3. 理解内核构建系统:Kbuild是如何工作的?
在随便写一个Makefile之前,我们必须先理解Linux内核独特的构建系统——Kbuild。它不是你平时编译普通C程序用的那种简单的Makefile,而是一套复杂、精密且高度集成的规则集合。驱动编译的Makefile,实际上是“调用”了内核源码树中的这套Kbuild系统。
3.1 核心思想:向内核构建系统“注册”你的模块
当你编译一个独立的内核模块时,你不是在独立地编译一个程序。你的Makefile主要做两件事:
- 切换目录:通过
-C选项,告诉make命令:“请先切换到内核源代码(或头文件)的目录下,读取那里顶层的Makefile。” - 指明目标:通过
M=选项,告诉内核的构建系统:“我的模块源代码在另外一个独立的目录(M=指定的路径)里,请你根据内核的规则来构建它。”
这样,构建的主动权交给了内核的Kbuild。Kbuild系统会:
- 使用内核配置(
.config文件)来决定哪些函数和宏是可用的。 - 使用内核的编译标志(如优化级别、架构相关参数)。
- 处理模块所有的依赖关系,比如你的驱动依赖了内核的
USB子系统或网络协议栈。
3.2 一个最小化的驱动Makefile解析
下面是一个最经典、最精简的驱动Makefile示例,适用于只有一个源文件hello.c的模块:
# 指定内核源码目录,使用绝对路径更可靠 KDIR := /lib/modules/$(shell uname -r)/build # 或者使用头文件目录 # KDIR := /usr/src/linux-headers-$(shell uname -r) # 指定当前模块源码所在目录 PWD := $(shell pwd) # 定义模块名(最终生成的.ko文件名) obj-m += hello.o # 如果你的模块由多个源文件组成,比如file1.c, file2.c # 则需要将模块名改为一个“复合对象” # obj-m += mymodule.o # mymodule-objs := file1.o file2.o all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean逐行解读:
KDIR: 这是最关键的一行。它指向了内核的构建目录。/lib/modules/$(shell uname -r)/build是一个标准的符号链接,通常指向已安装的内核头文件目录。使用$(shell uname -r)可以自动获取当前内核版本,使脚本更具可移植性。PWD: 获取当前驱动源码所在的绝对路径。obj-m += hello.o: 这是告诉Kbuild:“请将hello.o构建成一个内核模块(m代表module)。Kbuild会自动寻找hello.c来编译。如果模块名和源文件名不同(例如源文件是mydrv.c,但想生成foobar.ko),则需要写obj-m += foobar.o,并添加foobar-objs := mydrv.o。all目标:执行真正的编译命令。$(MAKE)是make命令的引用。-C $(KDIR): 先改变目录到内核构建目录。M=$(PWD): 然后告诉内核Makefile,模块源码在$(PWD)这个外部目录。modules: 这是内核Makefile中定义的一个目标,意思是“构建所有在obj-m列表中指定的模块”。
clean目标:清理编译生成的文件,同样需要调用内核的构建系统来执行清理工作。
3.3 多文件模块与依赖处理
当你的驱动变得复杂,需要拆分成多个.c和.h文件时,Makefile需要稍作调整。假设你有main.c,helper.c,helper.h三个文件,想生成mydrv.ko。
obj-m += mydrv.o mydrv-objs := main.o helper.o这里,mydrv.o不是一个实际存在的源文件,而是一个“组合对象”。Kbuild看到这个规则后,会先去编译main.c生成main.o,编译helper.c生成helper.o,最后将这两个.o文件链接成mydrv.ko模块。头文件helper.h的依赖关系会被自动处理(只要你在main.c和helper.c中正确#include了它)。
实操心得:在编写多文件驱动时,务必确保头文件中使用
#ifndef ... #define ... #endif防止重复包含,并且函数声明要清晰。编译时如果出现未定义的引用错误,首先检查mydrv-objs列表是否包含了所有必需的.o文件。
4. 标准编译流程实操:从代码到.ko文件
环境准备好了,Makefile也理解了,现在我们来走一遍完整的编译流程。我会用一个最简单的“Hello World”内核模块作为例子。
4.1 编写最简单的内核模块代码
创建一个名为hello.c的文件,内容如下:
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> // 模块的初始化函数,在insmod时调用 static int __init hello_init(void) { printk(KERN_INFO "Hello, World! Kernel module loaded.\n"); return 0; // 返回0表示成功 } // 模块的清理函数,在rmmod时调用 static void __exit hello_exit(void) { printk(KERN_INFO "Goodbye, World! Kernel module unloaded.\n"); } // 注册初始化和清理函数 module_init(hello_init); module_exit(hello_exit); // 模块的元信息 MODULE_LICENSE("GPL"); // 许可证声明,必须(否则会有警告) MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple hello world kernel module"); MODULE_VERSION("0.1");这段代码做了几件事:
- 包含必要的内核头文件。
- 定义了一个初始化函数
hello_init,它通过printk(内核的打印函数)向内核日志输出一条信息。 - 定义了一个清理函数
hello_exit,在模块卸载时输出信息。 - 使用
module_init和module_exit宏将这两个函数注册为模块的入口和出口。 - 添加模块信息,其中
MODULE_LICENSE(“GPL”)几乎是强制性的,使用非GPL兼容的许可证可能会导致模块无法加载或内核被污染。
4.2 编写对应的Makefile
在同一目录下创建Makefile(注意M大写),内容就是上一节给出的最小化示例。
4.3 执行编译
在终端中,切换到hello.c和Makefile所在的目录,直接执行make命令:
make如果一切顺利,你将看到类似以下的输出:
make -C /lib/modules/5.15.0-91-generic/build M=/home/your/path/to/module modules make[1]: Entering directory '/usr/src/linux-headers-5.15.0-91-generic' CC [M] /home/your/path/to/module/hello.o MODPOST /home/your/path/to/module/Module.symvers CC [M] /home/your/path/to/module/hello.mod.o LD [M] /home/your/path/to/module/hello.ko BTF [M] /home/your/path/to/module/hello.ko Skipping BTF generation for /home/your/path/to/module/hello.ko due to unavailability of vmlinux make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-91-generic'这个过程依次执行了:
- 编译(CC):将
hello.c编译成hello.o目标文件。 - 模块后处理(MODPOST):处理模块间的符号依赖,生成
Module.symvers文件(记录符号版本信息)。 - 编译模块元数据:生成
hello.mod.o。 - 链接(LD):将
hello.o和hello.mod.o链接成最终的内核模块文件hello.ko。 - BTF生成:尝试为模块生成BPF Type Format信息(用于更新的调试工具),这里因为缺少
vmlinux文件跳过了,不影响模块功能。
编译成功后,目录下会生成一堆文件,其中最关键的就是hello.ko。
4.4 加载、测试与卸载模块
现在,你可以将这个模块插入到运行中的内核里:
sudo insmod hello.ko这条命令没有输出是正常的。如何验证模块已经加载了呢?使用dmesg命令查看内核环形缓冲区的最新消息:
dmesg | tail -5你应该能看到类似这样的输出:
[ 1234.567890] Hello, World! Kernel module loaded.这说明我们的初始化函数被成功调用了。你还可以用lsmod命令查看所有已加载的模块,hello模块应该会在列表中。
卸载模块使用rmmod命令,注意参数是模块名(不是文件名):
sudo rmmod hello再次使用dmesg | tail -5查看,应该能看到清理函数输出的告别信息。
注意事项:
insmod/rmmod需要root权限。模块加载后,其代码就成为内核空间的一部分,拥有极高的权限。务必确保你加载的模块来自可信来源。编写有缺陷的模块可能导致内核崩溃(Kernel Panic)。
5. 为特定内核版本交叉编译驱动
很多时候,我们不是在为当前运行的系统编译驱动。比如,在x86的PC上为ARM架构的嵌入式开发板编译驱动,或者为某个未安装的特定内核版本(如服务器内核)编译驱动。这就需要交叉编译。
5.1 核心概念:指定KERNEL_DIR和ARCH
交叉编译的关键在于为make命令明确指定两个参数:
KERNEL_DIR:目标内核的完整源代码目录的路径。注意,这里不再是头文件目录(/lib/modules/.../build),而必须是配置并准备用于构建的完整源码树。ARCH:目标CPU的架构,如arm,arm64,x86_64等。CROSS_COMPILE:交叉编译工具链的前缀。
5.2 准备目标内核源码树
你不能使用发行版提供的linux-headers包进行交叉编译,因为它不包含完整的源码和构建框架。你必须获取目标内核的完整源代码,并进行最小化配置。
假设你的目标板是ARM架构,内核版本是5.10.123。
- 下载或复制目标内核源码到你的开发主机,例如路径为
/home/you/linux-5.10.123/。 - 进入该目录,进行配置。最快捷的方式是使用目标板配置文件(如果存在):
这个步骤会生成内核构建所需的cd /home/you/linux-5.10.123 # 假设你的板子供应商提供了defconfig文件 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- your_board_defconfig # 如果没有特定defconfig,可以使用一个通用的最小配置 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig.config文件。
5.3 修改驱动Makefile以支持交叉编译
你的驱动Makefile需要做相应调整,不再使用$(shell uname -r)来自动获取路径,而是硬编码或通过外部变量传入。
# 方法1:在Makefile中硬编码(不灵活) # KERNEL_DIR ?= /home/you/linux-5.10.123 # ARCH ?= arm # CROSS_COMPILE ?= arm-linux-gnueabihf- # 方法2:通过命令行参数传入(推荐) KERNEL_DIR ?= /lib/modules/$(shell uname -r)/build ARCH ?= x86_64 CROSS_COMPILE ?= PWD := $(shell pwd) obj-m += hello.o all: $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNEL_DIR) M=$(PWD) modules clean: $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNEL_DIR) M=$(PWD) clean5.4 执行交叉编译命令
在驱动源码目录下,通过命令行覆盖Makefile中的变量:
make KERNEL_DIR=/home/you/linux-5.10.123 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-这条命令做了以下事情:
- 调用
/home/you/linux-5.10.123/目录下的内核顶层Makefile。 - 告知内核构建系统,目标架构是
arm。 - 使用
arm-linux-gnueabihf-gcc等工具进行编译(CROSS_COMPILE指定了工具链前缀)。 - 最终在驱动源码目录生成
hello.ko,但这个.ko文件是ARM架构的,无法在x86主机上加载运行。
实操心得:交叉编译时最常见的错误是“找不到文件”或“架构不匹配”。务必确保:
KERNEL_DIR指向的内核源码树已经执行过make ... _defconfig,生成了有效的.config文件。CROSS_COMPILE指定的工具链前缀路径已加入PATH环境变量,并且版本与内核兼容。可以通过arm-linux-gnueabihf-gcc --version来测试。- 驱动源码中不要包含任何与主机架构相关的硬编码路径或假设。
6. 编译问题深度排查与解决技巧
即使按照步骤操作,编译过程也常常不会一帆风顺。下面是一些常见错误及其排查思路,这些是我在多年实践中积累下来的“血泪经验”。
6.1 错误:Makefile:xxx: *** No rule to make target ‘modules’。 Stop。
问题分析:这几乎总是因为KDIR或KERNEL_DIR路径设置错误。make -C $(KDIR)命令首先会尝试进入该目录并寻找Makefile。如果路径不对,或者该目录下没有内核的顶层Makefile,就会报这个错。
排查步骤:
- 检查路径是否存在:
ls -ld $(KDIR)。确保它指向一个真实目录。 - 检查路径内容:
ls $(KDIR)/Makefile。确认该目录下存在内核的Makefile文件。 - 确认路径含义:
- 如果为当前主机编译,
KDIR通常是/lib/modules/$(uname -r)/build,这是一个符号链接。用ls -l /lib/modules/$(uname -r)/build查看它指向哪里,通常是/usr/src/linux-headers-$(uname -r)。确保这个目标目录存在且完整。 - 如果交叉编译,
KERNEL_DIR必须是完整的内核源码目录,并且已经配置过(存在.config文件)。
- 如果为当前主机编译,
6.2 错误:fatal error: linux/module.h: No such file or directory
问题分析:编译器找不到内核头文件。这通常意味着:
- 没有安装对应版本的
linux-headers或kernel-devel包。 KDIR指向了错误的位置(例如指向了普通源码目录而非包含构建框架的头文件目录)。- 在交叉编译时,
ARCH或CROSS_COMPILE设置错误,导致构建系统去错误的位置寻找头文件。
排查步骤:
- 为主机编译:运行
apt list --installed | grep linux-headers或rpm -qa | grep kernel-devel,确认包已安装且版本匹配。 - 检查
KDIR目录下的include/linux/module.h文件是否存在。 - 交叉编译:确保已在内核源码目录执行过
make ARCH=xxx ... menuconfig或... defconfig,这会生成必要的头文件链接和配置。
6.3 错误:error: expected ‘;’ before ‘XXXX’或类型未定义
问题分析:这属于代码语法或语义错误,但有时根源在环境。
- 内核API变更:这是驱动开发者最头疼的问题。不同内核版本间,函数原型、数据结构、头文件位置可能会发生变化。你在网上找到的驱动代码,可能是针对旧内核写的。例如,
create_proc_entry()函数在较新内核中被proc_create()取代了。 - 配置依赖:你的驱动代码使用了某个内核配置选项(如
CONFIG_PCI)下的函数或宏,但当前内核的.config文件没有启用该选项。
排查步骤:
- 核对内核版本:确认你的驱动代码设计针对的内核版本,与你正在编译的内核版本是否兼容。查看内核的
ChangeLog或Documentation/目录下的更新说明。 - 使用内核的
make命令检查配置:在内核源码目录下,运行make ARCH=$(ARCH) menuconfig,搜索错误信息中提到的函数或配置选项,看它是否被启用([*]或[M])。 - 查阅当前内核头文件:直接去
$(KDIR)/include/或$(KDIR)/include/linux/下查看相关头文件,确认函数原型、宏定义是否存在、是否改变。 - 为驱动代码添加版本兼容宏:这是专业驱动开发中的常用技巧。
#include <linux/version.h> #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) // 使用新内核的API my_struct = new_api_call(); #else // 使用旧内核的API my_struct = old_api_call(); #endif
6.4 错误:MODPOST阶段报错,如未定义的符号
问题分析:MODPOST阶段负责解决模块间的符号引用。如果出现undefined symbol: _function_name,意味着:
- 你的模块调用了一个内核函数,但该函数没有被导出(即不在内核的符号表中)。
- 或者,你依赖另一个内核模块(如
cfg80211.ko)导出的符号,但编译时没有解决这个依赖。
排查步骤:
- 检查函数是否导出:在内核源码目录,查看
/proc/kallsyms(需要root)或编译生成的Module.symvers文件,搜索你调用的函数名。如果找不到,说明该函数是内核内部函数,模块不能直接调用。 - 使用
EXPORT_SYMBOL():只有被EXPORT_SYMBOL()或EXPORT_SYMBOL_GPL()显式导出的函数,才能被模块使用。你只能调用这些已导出的函数。 - 处理模块依赖:如果你的模块
A.ko依赖于模块B.ko导出的符号,你需要在A.ko的Makefile中添加:
并且在加载时,需要先KBUILD_EXTRA_SYMBOLS += /path/to/B/Module.symversinsmod B.ko,再insmod A.ko。更规范的做法是将B编译进内核(=y),而不是编成模块(=m)。
6.5 调试与信息输出技巧
编译成功只是第一步,加载运行可能还有问题。除了用printk,还有更多调试手段:
使用
pr_debug,dev_dbg等动态调试:这些宏在默认配置下不会打印信息,但可以通过dynamic debug机制在运行时开启,避免日志被刷屏。// 在代码中 pr_debug(“Debug info: value=%d\n”, var); // 在shell中,动态开启该文件的调试信息 echo ‘file hello.c +p’ > /sys/kernel/debug/dynamic_debug/control查看详细的编译命令:在
make命令前加上V=1,可以打印出Kbuild实际执行的每一条编译和链接命令,对于排查工具链、参数问题非常有用。make V=1清理与重新构建:当修改了
Makefile或头文件依赖关系时,简单的make可能不够彻底。使用make clean清理旧文件,或者更彻底地删除所有生成文件(包括Module.symvers,.mod.c等)再重新编译。
7. 进阶话题:集成到内核树与DKMS
当你只是测试一个简单的模块,放在独立目录编译没问题。但如果驱动比较复杂,或者希望长期维护、分发给他人使用,就需要更规范的方法。
7.1 将驱动集成到内核源码树
这是最正规的驱动开发方式。你将驱动的源代码(如mydriver/目录)放置在内核源码的drivers/某个子目录下(例如drivers/char/或drivers/net/wireless/)。
创建驱动目录和Kconfig/Makefile:
- 在
drivers/char/mydriver/目录下,放置你的.c和.h文件。 - 创建
Kconfig文件,定义在make menuconfig时出现的配置选项。config MY_DRIVER tristate “My Hardware Driver” depends on PCI help This is a sample driver for my special hardware. - 创建
Makefile,内容非常简单:obj-$(CONFIG_MY_DRIVER) += mydriver.o mydriver-objs := main.o helper.o
- 在
修改上级目录的Kconfig和Makefile:
- 在
drivers/char/Kconfig末尾添加:source “drivers/char/mydriver/Kconfig” - 在
drivers/char/Makefile末尾添加:obj-$(CONFIG_MY_DRIVER) += mydriver/
- 在
配置与编译:
- 回到内核根目录,执行
make menuconfig,在Device Drivers -> Character devices下就能看到你的驱动选项,可以将其编入内核(*)或编成模块(M)。 - 执行
make或make modules,你的驱动就会随内核一起被编译。模块会生成在drivers/char/mydriver/目录下,或者统一输出到modules目录。
- 回到内核根目录,执行
这种方式的好处是驱动可以享受内核完整的版本控制和配置系统,但缺点是必须拥有并管理整个内核源码树。
7.2 使用DKMS管理第三方内核模块
DKMS(Dynamic Kernel Module Support)是发行版(如Ubuntu, RHEL)用来管理第三方内核模块(如NVIDIA显卡驱动、VirtualBox增强工具)的标准框架。它的核心作用是:在内核升级后,自动为新的内核重新编译模块。
假设你有一个驱动hello-1.0,想用DKMS管理。
创建DKMS源码目录结构:
/usr/src/hello-1.0/ ├── Makefile ├── hello.c └── dkms.conf编写
dkms.conf配置文件:PACKAGE_NAME=“hello” PACKAGE_VERSION=“1.0” MAKE[0]=“make all KVERSION=$kernelver” CLEAN=“make clean” BUILT_MODULE_NAME[0]=“hello” DEST_MODULE_LOCATION[0]=“/extra” AUTOINSTALL=“yes”PACKAGE_NAME,PACKAGE_VERSION: 模块包名和版本。MAKE: 编译命令,$kernelver是DKMS传递的内核版本变量。BUILT_MODULE_NAME: 生成的模块名(不含.ko)。DEST_MODULE_LOCATION: 安装路径,相对于/lib/modules/$kernelver/。/extra是一个常用目录。
DKMS操作命令:
- 添加模块到DKMS树:
sudo dkms add -m hello -v 1.0 - 为当前内核编译模块:
sudo dkms build -m hello -v 1.0 -k $(uname -r) - 安装模块:
sudo dkms install -m hello -v 1.0 -k $(uname -r) - 检查状态:
dkms status - 移除模块:
sudo dkms remove -m hello -v 1.0 --all
- 添加模块到DKMS树:
安装后,模块文件(如hello.ko)会被安装到/lib/modules/$(uname -r)/extra/目录。当系统通过包管理器升级内核后,DKMS服务会检测到,并自动为新内核重新编译和安装这个模块,这极大地简化了第三方驱动的维护。
注意事项:DKMS要求你的
Makefile能够接受KVERSION参数来指定内核版本。我们之前写的通用Makefile通过$(shell uname -r)获取版本,这在DKMS自动构建时可能不工作。一个更兼容的写法是:KVER ?= $(shell uname -r) KDIR ?= /lib/modules/$(KVER)/build ... all: $(MAKE) -C $(KDIR) M=$(PWD) modules这样,DKMS调用
make all KVERSION=5.15.0-92-generic时,就会覆盖KVER变量,从而为指定内核编译。