Linux kernel 内核模块编译、装载和卸载

发布时间: 更新时间: 总字数:1075 阅读时间:3m 作者: IP上海 分享 网址

本文以 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 是内核模块输出函数,运行在内核空间;printfglibc 库函数,运行在用户空间
    • 标识为 __init 的函数如果直接编译进内核,在连接的时候都会放在 .init.text 区段内
    • 标识为 __exit 的函数修饰卸载函数,告诉内核如果相关模块被直接编译进内核,则 cleanup_function 函数会被省略,不编译进内核镜像
    • 支持的LICENSE,推荐使用 GPLGPL 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!
  • 查看 hello 模块是否加载
$ lsmod | grep hello
hello                  12496  0
  • 查看 modinfo 信息
$ 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
  • 将 hello 模块从内核卸载
$ rmmod hello.ko
  • dmesg -ctail /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.

参考

  1. Linux动态链接库和静态链接库
Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数