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.\n");
return 0x0;
}
static void hello_exit(void)
{
printk(KERN_ERR"Module, hello exit.\n");
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, "\177ELF\2\1", 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\n", 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.