内核模块再学习之模块的实现
如果你对内核模块编程已经有了简单的认识,那么可以更进一步学习模块在内核中的实现。对于每个内核模块来说,系统都为其分配一块内存区,这块内存区包括:一个module结构、唯一表示模块名称的字符串和实现模块功能的代码。
module结构中包含众多字段,从多个方面对内核模块进行描述,这一点如同task_struct结构对一个进程的全面描述一样。我们接下来只关注几个与我们平时简单的内核模块编程相关的字段。
enum module_state state:显示内核模块的状态;该枚举类型包含三种模块状态:MODULE_STATE_COMING、MODULE_STATE_LIVE和MODULE_STATE_GOING。分别表示正在加载模块、模块已经加载并且可用、正在卸载模块。
struct list_head list:内核中的所有模块结构通过双链表组织在一起,该字段表示当前模块在双链表中的节点。
char name[MODULE_NAME_LEN]:模块的名称,每个模块的名称都是唯一的。
int (*init)(void):init是一个函数指针,它指向模块的加载函数。
void (*exit)(void):exit是一个函数指针,它指向模块的卸载函数。
unsigned int core_size:用于模块核心函数与数据结构的动态内存区大小。
通过上述的字段分析,我们知道内核中的模块是通过双链表进行组织的。因此,我们就可以通过双链表的操作函数来打印内核中每个模块的相关信息。不过,模块链表的头指针modules并未被导出,所以我们通过在符号表中查看modules在内存中的地址来对其进行操作(关于通过符号表得到内核中某个未导出符号的地址可查看这里的文章)。整个程序的关键代码参考如下。
#define modules 0xc0771264 static int __init print_module_init(void) { struct module *p; struct list_head *module_head; struct list_head *pos; int i = 0; printk("print_module module is starting..\n"); module_head = (struct list_head *)modules; list_for_each(pos, module_head) { p = list_entry(pos, struct module, list); printk("%3d %20s %10d\n", ++i, p->name, p->core_size); } return 0; }
遍历的方法很简单,先得到模块链表的头指针module_head,再通过list_for_each函数依次遍历每个内核模块。我们在此打印的模块信息是模块的名称和模块的大小,其输出结果和lsmod命令的输出结果相同。如下所示。
[ 7405.518605] print_module module is starting.. [ 7405.518610] 1 print_module 632 [ 7405.518613] 2 hello 830 [ 7405.518616] 3 binfmt_misc 6587 [ 7405.518618] 4 vboxnetadp 6808 [ 7405.518621] 5 vboxnetflt 18753 [ 7405.518624] 6 via 37920 [ 7405.518626] 7 drm 162345 [ 7405.518629] 8 vboxdrv 229952 …………
完整代码可以在这里下载。