笨叔 陈悦. 奔跑吧 Linux 内核(第2版) [M]. 北京: 人民邮电出版社, 2020.
Linux 内核采用了宏内核架构,操作系统的大部分功能在内核中实现,比如进程管理、内存管理、进程调度、设备管理等,并且在特权模式下(内核空间中)运行。Linux 的这种宏内核可以理解为完全静态的内核,那么如何实现运行时内核的动态扩展呢?其实 Linux 内核在发展过程中早就引入了内核模块的这种机制,可在内核运行时加载一组目标代码来实现某个特定的功能,这样在实际使用 Linux 的过程中就不需要重新编译内核代码来实现动态扩展。
内核在初始化各个模块时有优先级顺序。对于驱动模块来说,它的优先级不是特别高,而且内核把所有模块的初始化函数都存放在一个特别的段中来管理。
通过 file
命令检查编译的模块是否正确,通过 modinfo
命令进一步检查,lsmod
命令查看当前模块是否已经被加载到系统中。
为了根据不同的应用场景给内核模块传递不同的参数,Linux 内核提供了一个宏实现模块的参数传递。module_param是 Linux 内核模块编程中的一个关键宏,用于在加载模块时传递参数,从而动态配置模块行为。
module_param(name, type, perm);
示例代码
#include
#include
static char *msg = "hello";
static int count = 1;
module_param(count, int, 0644);
module_param(msg, charp, 0444);
static int __init init_func(void) {
for (int i = 0; i < count; i++)
printk(KERN_INFO "%s\n", msg);
return 0;
}
module_init(init_func);
insmod example.ko msg="world" count=3
说明:
为了在同一驱动程序的不同内核模块中实现函数的相互调用、参数的访问,Linux 内核为我们提供了对应的宏。
符号(Symbol)在内核中指的是函数或全局变量的名称,每个符号对应内存中的一个地址:函数名对应代码段中的起始地址,变量名对应数据段中的存储位置。内核模块符号共享的核心思想是:一个模块可以将自己的函数或变量"导出",供其他模块使用,这类似于工厂中不同车间共享工具的场景。
内核维护着一个全局的符号表(本质是哈希表),记录了所有导出符号的名称和地址。当模块A导出符号后,这些符号会被注册到这个公共表中,模块B就可以通过名称找到并使用它们。
导出宏的使用
Linux内核提供了两个主要的宏用于符号导出:
1. EXPORT_SYMBOL(sym):将符号对全部内核代码公开,允许所有模块使用(无论许可证)。
2. EXPORT_SYMBOL_GPL(sym):仅允许GPL兼容许可证的模块使用导出的符号。
推荐做法是:除非必要,优先使用EXPORT_SYMBOL_GPL,保证内核许可证纯洁性。
导出步骤
int my_crc32(const unsigned char *buf, size_t len) {
// CRC32计算实现
return crc;
}
int global_counter = 0;
EXPORT_SYMBOL(my_crc32);
EXPORT_SYMBOL(global_counter);
extern int my_crc32(const unsigned char *buf, size_t len);
extern int global_counter;
static int __init use_module_init(void) {
int crc = my_crc32("hello", 5);
global_counter++;
return 0;
}
符号重复可能导致的问题包括
如何避免符号重复