内核模块-hello.ko
本文讲述了一个最简单的内核模块HelloWorld编写、构建、载入的全过程。需要的环境如下:
内核版本 linux-5.5.6
交叉编译器 arm-linux-gnueabi-
根文件系统 busybox-1.31.1
虚拟机环境 qemu-system-arm
以上相关环境的搭建过程详见前文《用Busybox制作Linux最小根文件系统》
内核模块的意义
Linux内核是模块化组成的,它允许内核在运行时动态地向其中插入或从中删除代码。这些代码(包括相关的子例程、数据、函数入口和函数出口)被一并组合在一个单独的二进制镜像中,即所谓的可装载内核模块中,或简称为模块。
支持模块的好处是基本的内核镜像可以尽可能地小,因为可选的功能和驱动程序可以利用模块形式来提供。模块允许我们方便地删除和重新载入内核代码,也方便了调试工作。而且当热插拔新设备时,可通过命令载入新的驱动程序。
Hello, World
一个最简单的Hello, World内核模块代码如下
1 | /* |
模块初始化
hello_init
函数是模块的入口,通过module_init()
宏调用注册到系统中,在内核装载时被调用。初始化函数必须符合下面的形式
1 | int my_init(void); |
通常情况下,初始化函数不会导出,可标记为static
.
init
函数会返回一个int
型数值,成功返回零,否则,返回非零值。
模块退出
hello_exit
函数是模块的出口,通过module_exit()
宏调用注册到系统中,在模块从内存中卸载时被内核调用。退出函数必须符合下面的形式
1 | void my_exit(void); |
exit
函数也可以标记为static
退出函数可能会在返回前负责清理资源,以保证硬件处于一致状态,或者做其他的一些操作。
记录信息的宏
MODULE_LICENSE()
宏用于指定模块的版权。如果载入非GPL
模块到系统内存,则会在内核中设置被污染标识。MODULE_AUTHOR()
宏指定代码作者。MODULE_DESCRIPTION()
宏是对模块的简要描述。
构建模块
Linux内核模块的编译有两种方式:
- 放在内核代码树中
- 放在内核代码树外
这里我选择了放在内核代码树外进行编译。下面先说明一下我们的内核代码和模块代码所在的目录
- 内核代码目录:
~/kernel/linux-5.5.6
- 模块代码目录:
~/kernel/my_kernel_obj/helloworld
- 模块代码文件:
~/kernel/my_kernel_obj/helloworld/hello.c
在模块代码目录下新建一个Makefile
文件,内容如下:
1 | obj-m := hello.o |
obj-m := hello.o
这一行是告诉编译器将hello.c
编译为内核模块。
KERNEL_DIR
是内核代码的顶层目录。
PWD
是当前目录,表示将结果输出在当前目录(模块代码目录)下。
直接 make
编译。
ps: 由于我已经通过修改内核顶层
Makefile
文件指定了交叉编译选项,此处make
时无须重复指定。
完成后,模块代码目录下内容如下:
1 | dhd@dhd-pc:~/kernel/my_kernel_obj/helloworld$ ls |
hello.ko
就是所需的内核模块。
载入模块
载入内核模块的命令是 insmod
, 删除内核模块的命令是 rmmod
把刚刚生成的hello.ko
拷贝到根文件系统目录下,并重新生成根文件系统镜像,最后用先前写好的qemu
启动脚本启动。
1 | cp hello.ko ~/qemu/rootfs/lib/ |
进入系统后,使用命令载入和删除内核模块进行测试。如下
ps:第一次
insmod
不成功是因为在系统初始化脚本中加了insmod /lib/hello.ko