🥇今日学习目标:什么是Linux内核?
🤵♂️ 创作者:JamesBin
⏰预计时间:10分钟
🎉个人主页:嵌入式悦翔园个人主页
🍁专栏介绍:Linux驱动开发100问
什么是模块?如何编写和使用模块?
- 一、什么是模块
- 二、如何编写模块
- 三、如何编译模块
- 四、如何挂载和卸载模块
- 五、如何使用模块
- 六、相关知识
一、什么是模块
在 Linux 内核中,模块是一种动态加载和卸载的可执行文件,它可以在运行时向内核添加新的功能和驱动。 与编译进内核的代码相比,模块的使用可以减少内核的体积和启动时间,并且可以让内核更加灵活,根据需要动态地添加或删除功能。
模块可以是设备驱动、文件系统、网络协议、安全模块等,通过内核提供的模块管理接口,可以动态地插入和删除模块。模块的代码通常是以源码形式提供,需要编译成二进制文件才能加载到内核中运行。模块也可以被其他模块所依赖,形成模块之间的依赖关系。
模块的使用可以帮助我们解决很多问题,例如:
- 节省内存空间:模块可以按需加载,不需要一直占用内存,从而节省内存空间。
- 扩展内核功能:模块可以添加新的设备驱动程序、文件系统、网络协议等内核功能。
- 提高系统安全性:模块可以对内核进行功能增强,从而提高系统的安全性。
总之,模块是Linux内核中一个非常重要的组成部分,可以让内核变得更加灵活、可扩展和可维护。
Linux模块可以是驱动,也可以是其他功能模块。模块和驱动之间存在一定的关系,因为驱动通常也是作为模块的形式存在于内核中的。
在Linux内核中,模块可以被动态地插入和卸载,因此模块通常被用来扩展内核的功能。而驱动则是一种特殊的模块,用于管理硬件设备,控制硬件设备的操作。在Linux中,驱动通常也以模块的形式存在于内核中,从而使得内核可以支持更多的硬件设备。因此,可以说驱动是模块的一种特殊形式。
二、如何编写模块
编写Linux内核模块需要遵循以下步骤:
1、包含必要的头文件
在C文件的开头,需要包含一些头文件,比如<linux/module.h>
和<linux/init.h>
等,这些头文件包含了模块开发所需的函数和宏等。
2、编写模块初始化和退出函数
模块初始化函数是模块载入时调用的函数,模块退出函数是模块被卸载时调用的函数。模块初始化函数需要使用宏module_init
进行定义,模块退出函数需要使用宏module_exit
进行定义。
3、定义模块许可证
Linux内核模块的代码需要遵循一定的许可证,这可以在模块代码中使用宏MODULE_LICENSE
进行定义。
4、编写模块代码
这是编写模块最主要的部分。模块代码需要定义模块的功能,并提供接口以便其他程序可以使用这些功能。
下面是一个简单的Linux内核模块代码示例:
#include <linux/module.h> // 模块头文件
#include <linux/init.h> // 初始化函数头文件
static int __init hello_init(void) // 初始化函数
{
printk(KERN_ALERT "Hello, world!\n"); // 打印信息
return 0;
}
static void __exit hello_exit(void) // 退出函数
{
printk(KERN_ALERT "Goodbye, cruel world!\n"); // 打印信息
}
MODULE_LICENSE("Dual BSD/GPL"); // 定义许可证
module_init(hello_init); // 定义初始化函数
module_exit(hello_exit); // 定义退出函数
三、如何编译模块
编译 Linux 模块可以使用 make
工具来完成,需要使用内核源码目录中的 Makefile 文件。下面是具体步骤:
进入 Linux 内核源码目录,并切换到要编译的内核版本分支。
运行命令 make modules_prepare
,该命令会生成 Module.symvers
文件,该文件包含了内核中所有可导出符号的信息,包括符号名、地址和版本等。
进入模块源码所在的目录,并创建一个名为 Makefile 的文件。
在 Makefile 文件中添加以下内容:
obj-m := module_name.o
其中 module_name
表示模块的名称,.o
表示编译后生成的目标文件格式。
运行命令
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
-C
选项指定内核源码目录,$(uname -r)
表示当前系统运行的内核版本号。
M
选项指定模块源码目录。
如果编译成功,将会生成一个名为 module_name.ko
的文件,该文件即为编译后的模块文件。
四、如何挂载和卸载模块
我们上一步骤通过make命令编译出来的.ko文件即为在最终的模块文件,如果想要使用该模块文件还需要使用 insmod
命令加载模块,例如:
$ sudo insmod module_name.ko
如果需要卸载模块,可以使用 rmmod
命令,例如:
$ sudo rmmod module_name
注意,在加载和卸载模块时需要拥有管理员权限。
为什么要挂载和卸载模块?
在Linux内核中,模块是可以被动态加载和卸载的。当内核启动时,并不会将所有的模块都加载进来,而是按需加载,这样可以提高系统的启动速度和节省内存空间。
当需要使用某个模块时,就需要将它挂载到内核中,使得内核能够调用模块中的功能。而当不再需要某个模块时,可以将其从内核中卸载,释放内存空间。因此,模块的挂载和卸载是为了方便动态管理模块的加载和卸载,提高系统的效率和稳定性。
五、如何使用模块
假设我们已经编写好了一个名为hello_module的内核模块,现在需要编写一个测试程序来调用该模块。下面是一个简单的测试程序代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd = open("/dev/hello", O_RDWR);
if (fd < 0) {
perror("Failed to open device /dev/hello");
exit(1);
}
char buf[32];
int ret = read(fd, buf, sizeof(buf));
if (ret < 0) {
perror("Failed to read from device /dev/hello");
close(fd);
exit(1);
}
printf("Read from /dev/hello: %s\n", buf);
close(fd);
return 0;
}
该测试程序的作用是打开内核模块创建的设备文件/dev/hello
,然后从设备文件中读取数据并打印到控制台。接下来,我们需要编译和运行这个测试程序。
首先,我们需要将测试程序的源代码保存为一个名为test.c
的文件。我们可以使用gcc编译器来编译该程序:
$ gcc -o test test.c
这将生成一个名为test
的可执行文件。接下来,需要将该可执行文件拷贝到Linux系统中运行。可以通过SSH等方式将文件传输到Linux系统中。在运行测试程序之前需要为测试程序添加可执行权限:
$ chmod +x test
现在,我们就可以运行测试程序了:
$ ./test
运行程序后,应该能够看到从/dev/hello
设备文件中读取的数据被打印到控制台上。
模块内打印的信息可以通过dmesg
命令进行打印,可以看到测试程序调用模块的过程。
六、相关知识
除了如何编写、编译和使用模块之外,还需要了解以下知识:
-
模块的依赖关系:在编写和使用模块时,需要考虑模块之间的依赖关系。如果一个模块依赖于另一个模块,则必须在前者之前将后者加载。否则,前者将无法正确运行。
-
模块的参数传递:有些模块需要在加载时传递参数。这些参数可以用于配置模块的行为或传递信息给模块。模块参数的传递方式有很多种,包括命令行参数、环境变量、配置文件等。
-
模块的版本管理:内核版本更新时,模块也需要相应更新。因此,需要了解如何为不同版本的内核编写适配的模块。
-
模块的调试:在开发模块时,可能会遇到各种各样的问题。因此,需要了解如何调试模块,例如使用 printk() 输出调试信息、使用 gdb 调试等。
-
模块的安全性:模块可以在内核空间执行,因此需要确保模块的安全性。内核提供了各种安全机制,例如模块签名验证、模块加载限制等。