实验五:添加内核模块
一、实验目的:
学习模块机制:它是Linux特有的一种机制。模块可以用来动态地增加内核的功能。
二、实验内容
一、模块的介绍
Linux
模块(module
)是一些可以作为独立程序来编译(使用适当的标志说明这是内核代码)的函数和数据类型的集合。在装载这些模块时,将它的代码链接到内核中。
Linux
模块可以单独编译成为目标代码,module
是一个目标文件。它可以根据需要在系统启动后动态地加载到系统核心之中。Linux
中大多数设备驱动程序或文件系统都作成的module
。超级用户可以通过insmod
和rmmod
命令显示地将module载入核心或从核心中将它卸载。
二、模块的组织结构
一旦module
载入核心后,它就成为核心代码的一部分。它与其它核心代码的地位是相同的。module
在需要的时可通过“内核符号表”symbol table
使用核心资源。核心将资源登记在符号表中,当module
装载时,核心利用符号表来解决module
中资源引用的问题。Linux
中允许module
堆栈(module stacking
),既一个module可请求其他module为之提供服务。当module
装载入系统核心时,系统修改核心中的符号表,将新装载module
提供的资源和符号加到核心符号表中。通过这种通信机制,新载入的module可以访问已装载的module
提供的资源。通过/proc/ksyms
文件可以查看内核符号表中各类符号及其地址。
三、模块的装载与卸载
模块在用户空间中(使用适当的标志)进行编译,结果产生一个可执行格式的文件。如前所述,模块可以引用内核变量,其条件是内核代码已编写好,从而允许变量被另一个模块所引用。简单说来,一个模块可以导出自己的符号,这样其他模块就可以使用它们。
在用insmod
命令装载一个模块时(见下面的解决问题部分),将会发生以下事件:
-
新模块(通过内核函数
create_module()
加入到内核地址空间。 -
另外一个内核函数
get_kernel_syms()
,通过查找导出的内核符号对模块中的外部符号引用进行解析,导出内核符号之后是一个已经装载到系统中的其他模块的列表。这意味着如果一个模块引用了其他模块中定义的符号,那么定义这个符号的模块就必须在那些引用这个符号的模块装载进系统之前被装载。 -
create_module()
为这个模块分配内存空间。 -
通过
init_module()
系统调用装载模块。该模块定义的符号在此时被导出,供其他可能后来装载的模块使用。 -
Insmod
为新装载的模块调用init_module()
函数
当模块卸载时(通过使用rmmod
,见下面的“解决问题”部分),调用cleanup_module()
函数。释放模块所使用的空间,同时取消虚拟地址的映象。
三、实验步骤
编写程序如下:
#define __KERNEL__
#define MODULE
#include<linux/module.h>
#include<linux/kernel.h>
int init_module() {
printk("Hello! My stu_number xxxxxxxx\n");
return 0;
}
void cleanup_module() {
printk("Quit...My stu_number xxxxxxxx\n");
}
使用如下命令进行编译
gcc –c –I/usr/src/linux-2.4.20-8/include –Wall hello.c
使用如下命令加载这个模块
insmod hello.o
如果显示内核不匹配(missmatch
)导致无法加载,可以使用如下命令进行强制加载
insmod -f hello.o
查看系统日志
dmesg
使用如下命令查看所有的模块
lsmod
卸载hello
模块
rmmod hello
再次查看系统日志,卸载模块的输出语句已经成功执行完毕
四、实验心得
本次实验的内容是增加一个内核模块。添加内核模块时需注意:代码运行在内核态下,很多用户态常用的函数、调用等可能无法使用(例如需要使用printk
代替常用的printf
);模块编译使用的命令与编译普通程序有所不同;模块的输出内容不会直接显示在控制台,需要使用dmesg
命令查看系统日志读取相关输出。