Loadable kernel modules allow you to add codes to the Linux kernel while it is running.
These modules can do lots of things, especially device drivers, file system drivers and system calls.
- 编辑历史
- 其他
LKM
LKM (Loadable Kernel Modules, 可加载内核模块),其最主要的优势在于可以动态的修改部分内核功能而不需要重新编译内核。
LKM 可以在需要时加载,不需要时卸载,这有助于节省系统资源。
但相对的,这也会导致内核偏向“碎片化”,可能导致其他性能问题。
许多操作系统都支持可装载内核模块,不过操作系统不同,LKM 的实现与称呼也可能不同。
如:
- Linux - loadable kernel module (LKM)
- FreeBSD - kernel loadable module (kld)
- macOS - kernel extension (kext)
- Windows NT - kernel-mode driver
Utilities
下面列出一些 LKM 相关的系统工具,也可以使用其他的命令辅助。
Utilities | Descriptions |
---|---|
lsmod | List currently loaded LKMs. |
insmod | Insert a LKM into the kernel. |
rmmod | Remove a LKM from the kernel. |
depmod | Determine interdependencies between LKMs. |
modinfo | Display contents of .modinfo section in a LKM object file. |
modprobe | Insert or remove a LKM or set of LKMs intelligently. For example, if you must load A before loading B, Modprobe will automatically load A when you tell it to load B. |
kerneld | Kerneld daemon program |
ksyms | Display symbols that are exported by the kernel for use by new LKMs. |
一般最常用的就是 lsmod
, insmod
, rmmod
,分别用于模块的列出、安装、卸载。
Example
说这么多虚的,都不如来一个实际的例子,就让我们简单编写一个 LKM 试试。
C source code
LKM 的代码编写与普通的用户态可执行文件还是有所不同的。
最明显的区别就是 LKM 的源码中不包含 main
函数,
而是包含了 init
与 exit
:它们分别是初始化函数与清除函数,并会在模板被加载或卸载时执行。
除此之外,还可以在代码中定义一些模块的相关信息,如:开源协议、作者、模块说明、模块版本、支持的设备等。
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_INFO */
#include <linux/init.h> /* Needed for the macros */
#define LICENSE "GPL"
#define AUTHOR "IceyBlackTea <IceyBlackTea@outlook.com>"
#define DESCRIPTION "A simple module example"
static int __init init_hello(void) {
printk(KERN_INFO "The LKM example loaded: Hello World!\n");
// A non 0 return means init_module failed; module can't be loaded.
return 0;
}
static void __exit cleanup_hello(void) {
printk(KERN_INFO "The LKM example unloaded: Goodbye module!\n");
}
module_init(init_hello);
module_exit(cleanup_hello);
MODULE_LICENSE(LICENSE);
MODULE_AUTHOR(AUTHOR);
MODULE_DESCRIPTION(DESCRIPTION);
从代码的内容来看,我们引用了一些需要的头文件,定义了一些模块相关信息,在模块被加载或卸载时输出一些信息。
printk
注意到输出打印使用的是 printk
而不是 printf
,因为期望输出于内核日志而不是用户空间中,
而在内核态中 C 标准库包括 printf
是不能使用的。
printk
基于 printf
,不过它们在使用时还是有一些区别的。
例如,printk
在输出时可以指定优先级,如代码中的 KERN_INFO
。
The log level | Priorities | Descriptions |
---|---|---|
0 | KERN_EMERG | An emergency condition; the system is probably dead |
1 | KERN_ALERT | A problem that requires immediate attention |
2 | KERN_CRIT | A critical condition |
3 | KERN_ERR | An error |
4 | KERN_WARNING | A warning |
5 | KERN_NOTICE | A normal, but perhaps noteworthy, condition |
6 | KERN_INFO | An informational message |
7 | KERN_DEBUG | A debug message, typically superfluous |
note
printk 相关在本文中不太重要,请自行查阅。
Kbuild Makefile
编译 LKM 代码与普通用户层代码相比也是稍微复杂一些。
编译 LKM 基于 make modules
,例子中进行了扩展。
-C
将当前工作目录转移到你所指定的位置。
M=
当用户需要以某个内核为基础编译一个外部模块的话,程序会自动到你所指定的dir目录中查找模块源码,将其编译,生成 .ko 文件。
# obj-m := mymodule.o
obj-m += hello.o
# kernel include dir
KERNELDIR := /lib/modules/$(shell uname -r)/build
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
make -C $(KERNELDIR) M=$(PWD) clean
obj-m
obj-m := mod_name.o
编译器会将 mod_name.c 编译得到 mod_name.o,在链接后会得到内核模块文件 mod_name.ko。
如果有多个源文件也可以写成
obj-m := mod_name1.o mod_name2.o ...
note
Linux Kernel Makefiles 的其他相关信息请自行查阅。
Compile
caution
这部分中我略去了自己的路径和内核版本信息,实际是正常输出的。
执行 make
。
正常情况的输出大致类似这样:
# make
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
make[1]: Entering directory '/usr/src/linux-headers-$(shell uname -r)'
CC [M] $(PWD)/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC $(PWD)/hello.mod.o
LD [M] $(PWD)/hello.ko
make[1]: Leaving directory '/usr/src/linux-headers-$(shell uname -r)'
使用 ls
查看当前目录可以发现多了好几个文件。
# ls
hello.c hello.ko hello.mod.c hello.mod.o
hello.o Makefile modules.order Module.symvers
其中最重要的就是 *.ko
文件了
modinfo
可以使用 modinfo
工具查看编译好后的 *.ko
的信息。
输出的信息大致如下:
# modinfo ./hello.ko
filename: $(PWD)/hello.ko
description: A simple module example
author: IceyBlackTea <IceyBlackTea@outlook.com>
license: GPL
srcversion: C4A2BC53933020930189167
depends:
retpoline: Y
name: hello
vermagic: $(shell uname -r) SMP mod_unload
Test the module
要测试我们的 LKM 能否正常使用,最简单的方法就是将其装载与卸载,并检查系统日志中是否正常输出。
insert the module
使用 insmod
工具即可,正常情况不会有输出。
# insmod ./hello.ko
list the module
我们可以使用 lsmod
工具列出已经加载的模块。
可以使用 grep
指令按条件检索过滤下方便观察。
输出大致如下:
# lsmod
Module Size Used by
hello 16384 0
......
可以观察到模块的名称、大小、被其他模块使用的总数和对象等信息。
remove the module
使用 rmmod
工具即可,正常情况不会有输出。
注意卸载模块时不是文件,不需要 .ko
的后缀。
# rmmod hello
check system log
使用 dmesg
可以快速查看系统日志。
可以使用 tail -[number]
指令显示末尾若干条指令方便观察。
输出大致如下:
# dmesg | tail -2
[880287.052304] The LKM example loaded: Hello World!
[880615.125196] The LKM example unloaded: Goodbye module!
至此我们就完成了 LKM 最基本的编写、编译、加载、卸载与检查等多个步骤!
Referrences
主要参考了:
The Linux Kernel Module Programming Guide
Linux Loadable Kernel Module HOWTO
Loadable kernel module - Wikipedia
最后
简单介绍了如何编写和使用一个 LKM,但是有关它的具体用途却还没有提,看看之后在其他博客中补上吧!
Flag 立起😅!