本文以 demo 例子的形式演示 Linux kernel 内核模块编译、装载和卸载
常用命令
apt install kmod
- 加载kernel模块命令
insmod <mod-name>
将指定模块加载到内核,推荐使用 modprobe <mod-name>
modprobe <mod-name>
insmod 安装时不处理依赖,仅安装当前模块;modprobe 会安装模块的依赖依次安装
modinfo <mod-name>
查看模块信息
rmmod <mod-name>
卸载模块
depmod
查找 /lib/moduels/(uname -r)/
中的所有模块并建立 modules.dep.bin
文件,该文件记录了模块位置及依赖关系
示例
demo 文件
假设编译目录为 /root/hello-module/
,示例编译共涉及两个文件:
- hello.c 源代码
printk
是内核模块输出函数,运行在内核空间;printf
是 glibc
库函数,运行在用户空间
- 标识为
__init
的函数如果直接编译进内核,在连接的时候都会放在 .init.text
区段内
- 标识为
__exit
的函数修饰卸载函数,告诉内核如果相关模块被直接编译进内核,则 cleanup_function
函数会被省略,不编译进内核镜像
- 支持的LICENSE,推荐使用
GPL
或 GPL v2
:
- GPL
- GPL v2
- GPL and additional rights
- Dual BSD/GPL
- Dual MPL/GPLProprietary
$ cat hello.c
#include <linux/init.h>
#include <linux/module.h>
/*
* hello module init method
* 内核模块加载函数
*/
static int __init hello_init(void)
{
printk(KERN_INFO "hello world init!\n");
return 0;
}
/* 告诉内核 hello_init 是模块驱动程序的入口函数 */
module_init(hello_init);
/*
* hello module exit method
* 内核模块卸载函数
*/
static void __exit hello_exit(void)
{
printk(KERN_ALERT "bye kernel world!\n");
}
/* 告诉内核hello_init是模块驱动程序的出口函数 */
module_exit(hello_exit);
module_exit(hello_exit);
MODULE_AUTHOR("xiexianbin <me@xiexianbin.cn>");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRITION("A simple Hello World Module");
/* MODULE_DEVICE_TABLE(table_info); 模块的设备表 */
MODULE_ALIAS("a simplest module");
obj-m := hello.o
KERNEL_DIR := /lib/modules/`uname -r`/build
PWD := $(shell pwd)
default:
make -C ${KERNEL_DIR} M=${PWD} modules
clean:
rm -f *.o *.ko *.mod.c *.mod.o modules.* Module.*
说明:
- obj-m := hello.o # 要生成
hello
模块即 hello.ko
- 若有多个文件生成,则须添加
hello-objs :=file1.o file2.o…..
KERNEL_DIR
# 内核源码目录
PWD
# 当前目录,即:/root/hello-module/
- make
-C ${KERNEL_DIR}
# 指定内核源码目录
M=${PWD} modules
# 指定模块源码目录
环境准备
$ yum install kernel-devel-$(uname -r) kernel-headers-$(uname -r) # 版本需要与当前的 kernel 版本对应
$ cd /lib/modules/`uname -r`/ # 查看 build 链接是否正常,若不正常,需要安装对应版本的 kernel-devel
编译
$ make
make -C /lib/modules/`uname -r`/build M=/root/hello-module modules
make[1]: Entering directory `/usr/src/kernels/3.10.0-1160.6.1.el7.x86_64'
CC [M] /root/hello-module/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /root/hello-module/hello.mod.o
LD [M] /root/hello-module/hello.ko
make[1]: Leaving directory `/usr/src/kernels/3.10.0-1160.6.1.el7.x86_64'
$ ls -l
total 228
-rw-r--r-- 1 root root 410 Apr 18 07:35 hello.c
-rw-r--r-- 1 root root 104768 Apr 18 07:59 hello.ko
-rw-r--r-- 1 root root 907 Apr 18 07:59 hello.mod.c
-rw-r--r-- 1 root root 60568 Apr 18 07:59 hello.mod.o
-rw-r--r-- 1 root root 47304 Apr 18 07:59 hello.o
-rw-r--r-- 1 root root 188 Apr 18 07:38 Makefile
-rw-r--r-- 1 root root 34 Apr 18 07:59 modules.order
-rw-r--r-- 1 root root 0 Apr 18 07:59 Module.symvers
加载
- 将
hello.ko
模块加载到内核,要使用绝对路径:
$ insmod /root/hello-module/hello.ko
$ dmesg -c
[ 1834.003419] hello world init!
$ lsmod | grep hello
hello 12496 0
$ modinfo hello.ko
filename: /root/hello-module/hello.ko
license: Dual BSD/GPL
...
retpoline: Y
rhelversion: 7.9
srcversion: 039659EB2884A32F2B6B26F
depends:
vermagic: 3.10.0-1160.6.1.el7.x86_64 SMP mod_unload modversions
$ rmmod hello.ko
dmesg -c
或 tail /var/log/messages
查看加载日志:
$ dmesg -c
[ 1907.262481] bye kernel world!
配置
- 黑名单:Linux 禁用功能,可以通过禁用对应的驱动实现
echo "blacklist amd76x_edac" >> /etc/modprobe.d/blacklist.conf
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF
systemd-modules-load.service
- 作用:系统启动早期的服务,用于从静态的配置文件加载内核模块
- 配置:加载如下路径的配置
$ systemctl cat systemd-modules-load.service
...
ConditionDirectoryNotEmpty=|/lib/modules-load.d
ConditionDirectoryNotEmpty=|/usr/lib/modules-load.d
ConditionDirectoryNotEmpty=|/usr/local/lib/modules-load.d
ConditionDirectoryNotEmpty=|/etc/modules-load.d
ConditionDirectoryNotEmpty=|/run/modules-load.d
...
FAQ
编译kernel版本问题
编译 hello.ko
时用的 kenerl
不是安装机器的 kenerl
版本会报如下错误:
$ insmod hello.ko
insmod: ERROR: could not insert module hello.ko: Invalid module format
$ modprobe hello.ko
modprobe: FATAL: Module hello.ko not found.