模块的基本描述

  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.