Linux 内核模块编程:Hello World 程序
内核模块是可以根据需要加载和卸载到内核中的代码片段。它们无需重新启动系统即可扩展内核的功能。
自定义代码可以通过两种方法添加到 Linux 内核中。
- 基本方法是将代码添加到内核源代码树中并重新编译内核。
- 一种更有效的方法是在内核运行时向内核添加代码。这个过程称为加载模块,其中模块指的是我们要添加到内核中的代码。
由于我们在运行时加载这些代码并且它们不是官方 Linux 内核的一部分,因此它们被称为可加载内核模块(LKM),与“基本内核”不同。基本内核位于 /boot 目录中,并且总是在我们启动机器时加载,而 LKM 在基本内核已经加载之后加载。尽管如此,这些 LKM 是我们内核的重要组成部分,它们与基础内核通信以完成其功能。
LKM 可以执行多种任务,但基本上它们分为三大类,
- 设备驱动,
- 文件系统驱动程序和
- 系统调用。
那么 LKM 有什么优势呢?
它们的一个主要优点是我们不需要在每次添加新设备或升级旧设备时不断重建内核。这节省了时间,也有助于保持我们的基本内核没有错误。一个有用的经验法则是,一旦我们有了一个工作的基础内核,我们就不应该改变我们的基础内核。
此外,它有助于诊断系统问题。例如,假设我们向基础内核添加了一个模块(即,我们通过重新编译修改了基础内核)并且该模块中有一个错误。这将导致系统启动错误,我们永远不会知道内核的哪一部分导致了问题。然而,如果我们在运行时加载模块并导致问题,我们会立即知道问题所在,我们可以卸载模块直到我们修复它。
LKM 非常灵活,因为它们可以通过一行命令加载和卸载。这有助于节省内存,因为我们仅在需要时才加载 LKM。此外,它们并不比基本内核慢,因为调用它们中的任何一个都只是从内存的不同部分加载代码。
**警告:LKM 不是用户空间程序。它们是内核的一部分。他们可以自由运行系统,并且很容易使系统崩溃。
所以现在我们已经建立了使用可加载内核模块,我们将编写一个 hello world 内核模块。这将在我们加载模块时打印一条消息,并在我们卸载模块时打印一条退出消息。
代码:
/**
* @file hello.c
* @author Akshat Sinha
* @date 10 Sept 2016
* @version 0.1
* @brief An introductory "Hello World!" loadable kernel
* module (LKM) that can display a message in the /var/log/kern.log
* file when the module is loaded and removed. The module can accept
* an argument when it is loaded -- the name, which appears in the
* kernel log files.
*/
#include /* Needed by all modules */
#include /* Needed for KERN_INFO */
#include /* Needed for the macros */
///< The license type -- this affects runtime behavior
MODULE_LICENSE("GPL");
///< The author -- visible when you use modinfo
MODULE_AUTHOR("Akshat Sinha");
///< The description -- see modinfo
MODULE_DESCRIPTION("A simple Hello world LKM!");
///< The version of the module
MODULE_VERSION("0.1");
static int __init hello_start(void)
{
printk(KERN_INFO "Loading hello module...\n");
printk(KERN_INFO "Hello world\n");
return 0;
}
static void __exit hello_end(void)
{
printk(KERN_INFO "Goodbye Mr.\n");
}
module_init(hello_start);
module_exit(hello_end);
对上述代码的解释:
内核模块必须至少有两个函数:一个名为 init_module() 的“开始”(初始化)函数,它在模块被装入内核时调用,以及一个名为 cleanup_module() 的“结束”(清理)函数,它刚刚被调用在它被 rmmoded 之前。实际上,从内核 2.3.13 开始,情况发生了变化。您现在可以为模块的开始和结束函数使用任何您喜欢的名称。事实上,新方法是首选方法。然而,许多人仍然使用 init_module() 和 cleanup_module() 作为他们的开始和结束函数。在这段代码中,我们使用 hello_start() 作为 init函数,使用 hello_end() 作为清理函数。
您可能已经注意到的另一件事是,我们使用了 printf()函数代替了 printf()函数。这是因为模块不会在控制台上打印任何内容,但会在 /var/log/kern.log 中记录消息。因此它用于调试内核模块。此外,在 header 中定义了八个可能的日志级别字符串,在使用 printk() 时需要这些字符串。我们按照严重性降序列出了它们:
- KERN_EMERG:用于紧急消息,通常是在崩溃之前的消息。
- KERN_ALERT:需要立即采取行动的情况。
- KERN_CRIT:临界条件,通常与严重的硬件或软件故障有关。
- KERN_ERR:用于报告错误情况;设备驱动程序经常使用 KERN_ERR 来报告硬件问题。
- KERN_WARNING:有关问题情况的警告,这些情况本身不会对系统造成严重问题。
- KERN_NOTICE:正常但仍值得注意的情况。在此级别报告了许多与安全相关的条件。
- KERN_INFO:信息性消息。许多驱动程序会在此级别打印有关它们在启动时找到的硬件的信息。
- KERN_DEBUG:用于调试消息。
我们已经使用 KERN_INFO 来打印消息。
准备系统运行代码:
系统必须准备好构建内核代码,为此您必须在您的设备上安装 Linux 头文件。在典型的 Linux 台式机上,您可以使用包管理器来定位要安装的正确包。例如,在 64 位 Debian 下,您可以使用:akshat@gfg:~$ sudo apt-get install build-essential linux-headers-$(uname -r)
编译源代码的Makefile:
obj-m = hello.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
**注意:不要忘记 Makefile 中的制表符
编译和加载模块:
运行 make 命令编译源代码。然后使用 insmod 加载模块。akshat@gfg:~$ make make -C /lib/modules/4.2.0-42-generic/build/ M=/home/akshat/Documents/hello-module modules make[1]: Entering directory `/usr/src/linux-headers-4.2.0-42-generic' CC [M] /home/akshat/Documents/hello-module/hello.o Building modules, stage 2. MODPOST 1 modules CC /home/akshat/Documents/hello-module/hello.mod.o LD [M] /home/akshat/Documents/hello-module/hello.ko make[1]: Leaving directory `/usr/src/linux-headers-4.2.0-42-generic'
现在我们将使用 insmod 加载 hello.ko 对象。
akshat@gfg:~$ sudo insmod hello.ko
测试模块:
您可以使用 modinfo 命令获取有关模块的信息,该命令将标识描述、作者和定义的任何模块参数:akshat@gfg:~$ modinfo hello.ko filename: /home/akshat/Documents/hello-module/hello.ko version: 0.1 description: A simple Hello world LKM author: Akshat Sinha license: GPL srcversion: 2F2B1B95DA1F08AC18B09BC depends: vermagic: 4.2.0-42-generic SMP mod_unload modversions
要查看该消息,我们需要阅读 /var/log 目录中的 kern.log。
akshat@gfg:~$ tail /var/log/kern.log ... ... Sep 10 17:43:39 akshat-gfg kernel: [26380.327886] Hello world To unload the module, we run rmmod: akshat@gfg:~$ sudo rmmod hello Now run the tail command to get the exit message. akshat@gfg:~$ tail /var/log/kern.log ... Sep 10 17:43:39 akshat-gfg kernel: [26380.327886] Hello world Sep 10 17:45:42 akshat-gfg kernel: [26503.773982] Goodbye Mr.