模块的基本描述
Linux kernel由诸多模块组成,这些模块可以直接与硬件交互,我们也叫它为硬件模块。诸多模块以模块化的方式存在于kernel中。在编译kernel时,可以将需要的模块加入到核心中,也可以将各个子模块编译成各自的单独的模块(模块以ko为扩展名),在需要的时候再分别载入。
写一个简单的模块程序
编写模块代码hello.c
#include <linux/init.h> #include <linux/module.h> MODULE_AUTHOR("voipman"); MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void) { printk(KERN_ERR"Module, hello world. "); return 0x0; } static void hello_exit(void) { printk(KERN_ERR"Module, hello exit. "); return; } module_init(hello_init); module_exit(hello_exit);
编写Makefile文件
CURR_PATH = $(shell pwd) KERNEL_PATH = /usr/src/kernels/3.10.0-1160.el7.x86_64 BUILD_PATH = $(CURR_PATH)/build obj-m := hello.o default: make -C $(KERNEL_PATH) M=$(CURR_PATH) modules .PHONY:clean clean: @$(RM) *.o *.ko *.mod.* Module.*
编写Makefile文件时,需要设定内核源码的地址KERNEL_PATH,需要linux系统首先安装kernel-devel包,如下
yum install kernel-devel
安装完毕内核开发包后,检查内核代码的路径/usr/src/kernels/3.10.0-1160.el7.x86_64/
编译demo模块 make,会生成hello.ko的模块文件。
到此,模块文件已经准备ok,实验这些模块操的命令。
管理模块的各个命令说明
insmod
加载hello.ko模块,会触发系统调用finit_module,通过系统调用自动调用module_init函数,执行回调函数hello_init,输出对应初始化信息。
[root@localhost ldd]# strace insmod hello.ko ... stat("/home/w/ldd", {st_mode=S_IFDIR|0775, st_size=235, ...}) = 0 stat("/home/w/ldd/hello.ko", {st_mode=S_IFREG|0644, st_size=101648, ...}) = 0 open("/home/w/ldd/hello.ko", O_RDONLY|O_CLOEXEC) = 3 read(3, "177ELF21", 6) = 6 lseek(3, 0, SEEK_SET) = 0 fstat(3, {st_mode=S_IFREG|0644, st_size=101648, ...}) = 0 mmap(NULL, 101648, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9f4b253000 finit_module(3, "", 0) = 0 munmap(0x7f9f4b253000, 101648) = 0 close(3) = 0 exit_group(0) = ?
从系统调用发现,insmod加载模块时,会调用系统调用 finit_module 实现模块的初始化,从而执行内核代码的init_module函数。
CentOS加载模块的系统调用号是313
#define __NR_finit_module 313
几个主要的内核函数执行顺序如下
finit_module --> load_module --> do_init_module
在do_init_module中会执行模块初始化函数module_init的函数hello_init,如下
static noinline int do_init_module(struct module *mod) { // ... /* Start the module */ if (mod->init != NULL) ret = do_one_initcall(mod->init); // ...
查看模块的执行信息
[root@localhost ldd]# cat /proc/kmsg <3>[28204.904444] Module, hello world.
加载模块后,会在系统目录下产生模块的目录
[root@localhost ldd]# ls /sys/module/hello/ coresize initsize notes/ rhelversion srcversion uevent holders/ initstate refcnt sections/ taint [root@localhost ldd]# cat /sys/module/hello/initstate live
另外,加载模块,需要加载着明确的指定模块的依赖关系。如 模块A依赖模块B,加载模块A之前需要先加载模块B。
insmod modB insmod modA
lsmod
列出加载的模块,找出上面开发的hello.ko模块信息
[root@localhost ldd]# lsmod |grep hello hello 12496 0
这个12496代码模块的大小,可以从文件中读取
[root@localhost ldd]# cat /sys/module/hello/coresize 12496
rmmod
移除模块时,系统会调用系统调用delete_module,更新系统调用如下
[root@localhost ldd]# strace rmmod hello.ko .... open("/sys/module/hello/refcnt", O_RDONLY|O_CLOEXEC) = 3 read(3, "0 ", 31) = 2 read(3, "", 29) = 0 close(3) = 0 delete_module("hello", O_NONBLOCK) = 0 exit_group(0) = ? +++ exited with 0 +++
在内核代码中调用module_exit函数,执行回调函数hello_exit,输出对应的退出信息。
[root@localhost ldd]# rmmod hello.ko [root@localhost ldd]# cat /proc/kmsg <3>[28947.565658] Module, hello exit.
删除模块的系统调用号176
#define __NR_delete_module 176
几个主要的内核函数执行顺序如下
delete_module --> free_module
其中delete_module系统调用函数如下,会调用模块的退出函数mod->exit()
SYSCALL_DEFINE2(delete_module, const char __user *, name_user, unsigned int, flags) { struct module *mod; // ... /* Final destruction now no one is using it. */ if (mod->exit != NULL) mod->exit(); // ... free_module(mod); // ... }
modprobe
加载模块,检查模块依赖的方式加载模块
-r 删除模块
modules.dep
depmod
分析kernel下的模块,将要载入的模块的相互依赖保存到依赖文件(modules.dep)
参考材料:
https://github.com/gityf/ldd
Done.