从零开始linux内核模块开发

本文为机器翻译的翻译文,经过简单校正。
原文地址:https://rayanfam.com/topics/start-linux-kernel-module-development/
嗨,大家好!
在这篇文章中,我将向您介绍linux内核模块开发领域。我本人是该领域的新手,但是随着我逐步学习它们,我决定在此博客中记录所有内容。
要启动,您需要某种虚拟机。当然,您可以在自己的系统上测试内核模块,但是这样做非常危险,您无法真正有效地调试它们,必须使用printk和调试消息来发现代码中的问题。
第一种选择是使用VirtualBox并在其中安装一个Linux发行版。这样,您的系统就不会崩溃,而且对于任何数据丢失,您都将是安全的。但是调试仍然不是很简单。
第二种选择是使用QEMU和buildroot,您可以轻松地对Linux内核代码或您自己的代码进行调试。这是推荐的方法。
我在这里找到了一个非常不错的linux + buildroot设置。顾名思义,再加上linux内核编程中的一些非常好的技巧。here
使用cirosantilli buildroot
只需按照GitHub上的指南进行操作即可。这只是我在设置buildroot时遇到的一些故障。
- 克隆整个存储库。这样您会更好。
git clone --recurse-submodules -j16 "https://github.com/cirosantilli/linux-kernel-module-cheat"
- 注意依赖关系。奇怪的是configure脚本没有检查所有的脚本。如果遇到任何错误消息,请注意错误消息,并找到适合您发行版安装的软件包。 (很可能您也需要该软件包的开发版本)
如果您使用的是提及的buildroot,则无需执行此步骤。这是使您开始内核模块开发的最小设置。
安装用于编译内核模块的依赖项(如果在Ubuntu上,请运行以下命令):
apt-get install build-essential linux-headers-$(uname -r)
您的第一个模块
创建目录并将这段代码放入文件中(例如ko_example.c):
ude
include
include
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shahriar EV");
MODULE_DESCRIPTION("sample linux kernel module.");
MODULE_VERSION("1.00");
static int __init mylkm_init(void) {
printk(KERN_INFO "HI!n");
return 0;
}
static void __exit mylkm_exit(void) {
printk(KERN_INFO "BYE!n");
}
module_init(mylkm_init);
module_exit(mylkm_exit);
- 顶部的包含物非常明显。它们是linux内核编程所必需的,并为我们提供了此处使用的所有功能。
- 下一块是模块规格。自我解释…
- mylkm_init 是加载模块时调用的函数.
- mylkm_exit 是在模块卸载时调用的函数.
- printk 用于从内核中打印内容,然后可以通过dmesg读取。
- KERN_INFO 是之后的日志消息的严重性级别
到目前为止,这已经足够了代码,但是我们需要一个Makefile来编译内核模块。
将此文件保存在与源代码相同的目录中的文件名Makefile中:
obj-m += lkm_example.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
翻译结果
现在运行make,它将成功构建。 要加载它(以root身份)运行:
insmod ko_example.ko
如果一切正常,您可以在dmesg缓冲区中看到“ Hello World”:
dmesg
The module should now be visible on lsmod output.
在卸载内核模块时,再次调用printk:
rmmod ko_example.ko
知道L’M开发与用户态应用程序不同是非常重要的。 我喜欢直接从derekmolloy.ie引用:
内核模块不是应用程序!
不按顺序执行— 内核模块使用其初始化函数进行注册,以处理请求,该初始化函数先运行然后终止。 它可以处理的请求类型在模块代码中定义。 这与图形用户界面(GUI)应用程序中常用的事件驱动编程模型非常相似。
没有自动清理 — 卸载模块时,必须手动释放分配给模块的所有资源,否则在系统重新引导之前,这些资源可能不可用。
没有printf()函数 — 内核代码无法访问为Linux用户空间编写的代码库。内核模块在内核空间中运行并运行,内核空间具有自己的内存地址空间。内核空间和用户空间之间的接口已明确定义和控制。但是我们确实有一个printk()函数可以输出信息,可以从用户空间中查看信息。
可以打断 — 内核模块在概念上的一个困难方面是它们可以同时被多个不同的程序/进程使用。我们必须仔细构造我们的模块,以使它们在被中断时具有一致且有效的行为。我们必须考虑多个进程同时访问模块的影响。
具有更高级别的执行特权 — 通常,分配给内核模块的CPU周期要多于分配给用户空间程序的CPU周期。这听起来像是一个优势,但是,您必须非常小心,以免模块对系统的整体性能产生不利影响。
没有浮点支持 — 内核代码使用陷阱为用户空间应用程序从整数模式转换为浮点模式。但是,在内核空间中执行这些陷阱非常困难。另一种方法是手动保存和恢复浮点运算-最好避免执行此任务,并将其留给用户空间代码。
http://derekmolloy.ie/writing-a-linux-kernel-module-part-1-introduction/
我认为第一次尝试就足够了! 进一步的话题可能实际上是在为内核模块做一些实际的任务。 例如向用户区显示一些数据或提供一些设备供其他程序使用。
参考文献
- https://blog.sourcerer.io/writing-a-simple-linux-kernel-module-d9dc3762c234
- https://github.com/cirosantilli/linux-kernel-module-cheat
- http://derekmolloy.ie/writing-a-linux-kernel-module-part-1-introduction/
- https://stackoverflow.com/questions/39652385/kernel-modules-how-can-i-modify-printk-severity-based-on-a-passed-parameter