本文编写一个简单的 Linux 内核模块,然后在 QEMU 中使用 insmod 加载模块, lsmod 查看模块信息, rmmod 卸载模块来进行测试工作。
实验环境在前文Linux Kernel 从编译到运行 和 为 Linux Kernel 添加系统调用 中已经准备好了,这里不再赘述。
编译模块的工作流程如下:
-
环境准备
-
编写模块代码
-
编写 Makefile
-
编译模块
-
加载、卸载测试
环境准备
在前文 为 Linux Kernel 添加系统调用 中我们使用 make bzImage
编译内核,这个命令不会编译模块,也不会生成模块导出的符号。
所谓模块导出的符号,就是模块中的函数或者变量,如果其他模块或者内核代码需要调用模块中的函数或者变量,就需要这些符号。
在 Linux Kernel 中,模块导出的符号是通过 EXPORT_SYMBOL
和 EXPORT_SYMBOL_GPL
来导出的。
编译模块需要使用 make modules
命令,这个命令会编译模块,并且生成模块导出的符号。对应的文件是 Module.symvers
。
在编写我们自己的模块之前,我们需要先编译内核,然后生成 Module.symvers
文件。
编译内核在前文中已经完成,这里不再重复编译,我们需要编译模块,并生成 Module.symvers
文件。
# 切换到内核源码目录
cd ~/Desktop/kernel/linux
make modules -j8
这个过程比 make bzImage
要慢一些。
编译完成后,如果我们在 ~/Desktop/kernel/linux
还看不到 Module.symvers
文件,需要执行 make modules_prepare
命令。
make modules_prepare
执行完成后,我们就可以在 ~/Desktop/kernel/linux
目录下看到 Module.symvers
文件了。
wgxls@server:~/Desktop/kernel/linux$ file Module.symvers
Module.symvers: ASCII text
Module.symvers
文件是一个文本文件,里面记录了模块导出的符号。
它的基本格式如下:
<符号地址> <符号名称> <所在模块> <导出类型>
这是我的 Module.symvers 文件
编写模块代码
来到上文为 Linux Kernel 添加系统调用中的 workspace
目录,创建一个新的目录。
mkdir my_module_demo
cd my_module_demo
先写一个最简单的模块,只包含加载和退出函数。
创建 my_module.c touch my_module.c
写入以下内容。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wgxls");
MODULE_DESCRIPTION("A simple Linux kernel module.");
MODULE_VERSION("0.1");
static int __init my_module_init(void) {
printk(KERN_INFO "Hello, world!\n");
return 0;
}
static void __exit my_module_exit(void) {
printk(KERN_INFO "Goodbye, world!\n");
}
module_init(my_module_init);
module_exit(my_module_exit);
编写 Makefile
为 my_module 编写 Makefile 文件,其中 -C 后面根 linux 内核代码的根路径。
obj-m += my_module.o
all:
make -C /home/wgxls/Desktop/kernel/linux M=$(PWD) modules
clean:
make -C /home/wgxls/Desktop/kernel/linux M=$(PWD) clean
编译模块
截至目前,workspace 目录下的内容如下:
wgxls@server:~/Desktop/kernel/workspace$ tree -L 2
.
├── busybox
├── bzImage
├── initramfs
│ ├── bin
│ ├── etc
│ ├── init
│ ├── proc
│ ├── sbin
│ ├── sys
│ ├── test_sys_call
│ └── test_sys_call.c
├── initramfs.img
├── Makefile
└── my_module_demo
├── Makefile
└── my_module.c
8 directories, 10 files
进入到 my_module_demo 目录下,执行 make
命令。
wgxls@server:~/Desktop/kernel/workspace/my_module_demo$ make
make -C /home/wgxls/Desktop/kernel/linux M=/home/wgxls/Desktop/kernel/workspace/my_module_demo modules
make[1]: 进入目录“/home/wgxls/Desktop/kernel/linux”
CC [M] /home/wgxls/Desktop/kernel/workspace/my_module_demo/my_module.o
MODPOST /home/wgxls/Desktop/kernel/workspace/my_module_demo/Module.symvers
CC [M] /home/wgxls/Desktop/kernel/workspace/my_module_demo/my_module.mod.o
CC [M] /home/wgxls/Desktop/kernel/workspace/my_module_demo/.module-common.o
LD [M] /home/wgxls/Desktop/kernel/workspace/my_module_demo/my_module.ko
BTF [M] /home/wgxls/Desktop/kernel/workspace/my_module_demo/my_module.ko
make[1]: 离开目录“/home/wgxls/Desktop/kernel/linux”
编译完成后,在当前目录下会生成 my_module.ko
wgxls@server:~/Desktop/kernel/workspace/my_module_demo$ file my_module.ko
my_module.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=c6f883b75116f6157daacfa6287799c1b0540009, with debug_info, not stripped
测试运行
最后,我们在 QEMU 的环境下测试这个模块的安装和卸载。
首先把 my_module.ko 拷贝到 initramfs 目录下。
cp my_module.ko ../initramfs/
然后重新制作 initramfs
wgxls@server:~/Desktop/kernel/workspace$ make clean
wgxls@server:~/Desktop/kernel/workspace$ make initramfs
启动 QEMU
wgxls@server:~/Desktop/kernel/workspace$ make run
安装模块
这里除了打印了 Hello, world! 之外,还报了一个模块签名验证失败的信息,同时将内核标记为污染。
暂时忽略这些信息。
使用 lsmod
来查看模块。
~ # lsmod | grep -ie "my"
my_module 12288 0 - Live 0xffffffffc050c000 (OE)
使用 rmmod
来卸载模块。
当我们第二次安装模块时,就不会再次打印模块验证失败的消息了。
The End…