原文出处:
1.首先从hello world开始
#include
#include
#include
#include
#include
#include
static int
hello_load(module_t md, int cmd, void* arg)
{
int err = 0;
switch(cmd){
case MOD_LOAD:
printf("hello world!\n");
break;
case MOD_UNLOAD:
printf("bye world!\n");
break;
default:
err = EINVAL;
break;
}
return err;
}
DEV_MODULE(helloworld, hello_load, NULL);
Makefile
SRCS= hello_world.c
KMOD= hello_world
.include
现在实验一下,首先编译
[prime@hello_world] $ make
Warning: Object directory not changed from original /usr/home/prime/mycode/kld/hello_world
@ -> /usr/src/sys
machine -> /usr/src/sys/i386/include
cc -O2 -fno-strict-aliasing -pipe -Werror -D_KERNEL -DKLD_MODULE
-nostdinc -I- -I. -I@ -I@/contrib/altq -I@/../include -I/usr/include
-finline-limit=8000 -fno-common -mno-align-long-strings
-mpreferred-stack-boundary=2 -mno-mmx -mno-3dnow -mno-sse -mno-sse2
-ffreestanding -Wall -Wredundant-decls -Wnested-externs
-Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Winline
-Wcast-qual -fformat-extensions -std=c99 -c hello_world.c
ld -d -warn-common -r -d -o hello_world.kld hello_world.o
touch export_syms
awk -f /sys/conf/kmod_syms.awk hello_world.kld export_syms | xargs -J% objcopy % hello_world.kld
ld -Bshareable -d -warn-common -o hello_world.ko hello_world.kld
objcopy --strip-debug hello_world.ko
[prime@hello_world] $
然后加载
[root@hello_world]#kldload ./hello_world.ko
现在dmesg看看
2.hello world干了什么
先看看DEV_MODULE,这个宏定义在/sys/sys/conf.h
#define DEV_MODULE(name, evh, arg) \
static moduledata_t name##_mod = { \
#name, \
evh, \
arg \
}; \
DECLARE_MODULE(name, name##_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE)
DECLARE_MODULE呢,定义在/sys/sys/module.h
#define DECLARE_MODULE(name, data, sub, order) \
MODULE_METADATA(_md_##name, MDT_MODULE, &data, #name); \
SYSINIT(name##module, sub, order, module_register_init, &data) \
struct __hack
那么我们的DEV_MODULE最终的展开结果是什么呢
static moduledata_t helloworld_mod = {
"helloworld",
hello_load,
NULL
};
static struct mod_metadata _mod_metadata_md_helloworld = {
MDT_STRUCT_VERSION,
MDT_MODULE,
&helloworld_mod,
"helloworld"
};
static void const* const
__set_modmetadata_set_sym__mod_metadata_md_helloworld
__section("set_modmetadata_set") __used =
&_mod_metadata_md_helloworld;
static struct sysinit helloworldmodule_sys_init = {
SI_SUB_DRIVERS,
SI_ORDER_MIDDLE,
(sysinit_cfunc_t)(sysinit_nfunc_t)module_register_init,
(void*)&helloworld_mod
};
static void const* const
__set_sysinit_set_sym_helloworldmodule_sys_init __section("set_sysinit_set") __used = &helloworldmodule_sys_init;
看上去很乱,让我们捋一捋吧
<1> 我们通过kldload加载KLD,加载后内核首先根据modmetadata_set找到一组指向struct
mod_metadata的指针,我们这里只有一个,
__set_modmetadata_set_sym__mod_metadata_md_helloworld指向
_mod_metadata_md_helloworld,
内核使用这个指针指向的结构中的数据为参数(/sys/kern/kern_linker.c中函数
linker_file_register_modules)调用module_register(/sys/kern/kern_module.c)对
模块进行注册
<2>然后,内核根据sysinit_set找到一组指向struct
sysinit的指针,我们这里也是只有一个__set_sysinit_set_sym_helloworldmodule_sys_init指向
helloworldmodule_sys_init,内核使用这个指针指向的结构中的数据为参数(/sys/kern/kern_linker.c中函
数linker_file_sysinit)调用结构中的函数,我们这里就是以&helloworld_mod为参数调用
module_register_init
<3>在module_register_init中,首先通过模块名称(helloworld_mod中的"helloworld")找到
module_register注册的模块,然后,通知该模块MOD_LOAD,我们这里也就是调用hello_load其中cmd参数为
MOD_LOAD,mod参数为指向本模块的指针,arg参数为helloworld_mod中的NULL,也就是我们的DEV_MODULE中的
NULL
3.复杂一点的hello world,在一个文件中实现多个模块
其实并不复杂,代码一样简单,Makefile就省略了
#include
#include
#include
#include
#include
#define NMODULE_SYSTEM SI_SUB_DRIVERS
#define NMODULE_ORDER SI_ORDER_MIDDLE
static int
modload(module_t mod, int cmd, void* arg )
{
int err = 0;
char* me = (char*)arg;
switch(cmd){
case MOD_LOAD:
printf("Module %s loaded\n",me);
break;
case MOD_UNLOAD:
printf("Module %s unloaded\n",me);
break;
default:
err = EINVAL;
break;
}
return err;
}
static moduledata_t firstmodule = {
"firstmodule",
modload,
"first one"
};
DECLARE_MODULE(firstmodule, firstmodule, NMODULE_SYSTEM, NMODULE_ORDER);
static moduledata_t secondmodule = {
"secondmodule",
modload,
"second one"
};
DECLARE_MODULE(secondmodule, secondmodule, NMODULE_SYSTEM, (NMODULE_ORDER + 1));
static moduledata_t thirdmodule = {
"thirdmodule",
modload,
"third one"
};
DECLARE_MODULE(thirdmodule, thirdmodule, NMODULE_SYSTEM, (NMODULE_ORDER + 2));
试试把secondmodule与thirdmodule的声明顺序颠倒一下,也就是先DECLARE_MODULE(thirdmodule.....)然后DECLARE_MODULE(secondmodule....),dmesg变了吗?
4.定义模块的版本与模块间的依赖关系
MODULE_VERSION用来定义模块的版本,在/sys/sys/module.h中定义,
#define MODULE_VERSION(module, version) \
static struct mod_version _##module##_version = {\
version \
}; \
MODULE_METADATA(_##module##_version, MDT_VERSION,\
&_##module##_version, #module)
其中module参数为模块的名称,version为版本号
MODULE_DEPEND定义模块的依赖关系,在/sys/sys/module.h定义,
#define MODULE_DEPEND(module, mdepend, vmin, vpref, vmax) \
static struct mod_depend _##module##_depend_on_##mdepend = {\
vmin,\
vpref,\
vmax\
};\
MODULE_METADATA(_md_##module##_on_##mdepend, MDT_DEPEND,\
&_##module##_depend_on_##mdepend, #mdepend)
定义的依赖关系为module依赖于mdepend,module可以接受的mdepend的版本号在vmin到vmax之间,最好为vpref
看看我们的实验代码,我们有四个模块,包括1个版本的dephello,与版本号分别为1,2,3的三个版本的hello(茴字不过有四种写法
),dephello依赖于hello,而且可以接受版本号的范围是1--3,最接受的版本号为2
代码的目录结构
[prime@modverdep] $ ls
Makefile dephello hello2
common hello1 hello3
[prime@modverdep] $
dephello目录下有源文件dephello.c与前面的hello world差不多,就是多了一行
MODULE_DEPEND(dephello, hello, 1, 3, 2);
hello*目录下面的源文件为hello.c,比如hello1下的内容为
#define HELLO_VERSION 1
#include "../common/hello.c"
其他的依此类推
common目录下的源文件如下
#include
#include
#include
#include
#include
static int
hello_load(module_t mod, int cmd, void* arg)
{
int err = 0;
char* name = (char*)arg;
switch(cmd){
case MOD_LOAD:
printf("version %d %s loading...\n", HELLO_VERSION, name);
break;
case MOD_UNLOAD:
printf("version %d %s unloading...\n", HELLO_VERSION, name);
break;
default:
err = EINVAL;
break;
}
return err;
}
static moduledata_t hellomod = {
"hello",
hello_load,
"hello"
};
DECLARE_MODULE(hello, hellomod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(hello, HELLO_VERSION);
上层目录的Makefile
SUBDIR= hello1 hello2 hello3 dephello
.include
首先make
然后换成超级用户 make install(为什么要install,后面解释);
[prime@modverdep]$ make
[root@modverdep]#make install
现在
[root@modverdep]# kldload dephello
[root@modverdep]# kldunload dephello
然后dmesg看看
如果我们只安装版本号为1或者3的hello呢?如果我们不安装hello呢?
MODULE_METADATA宏出现了很多次,详细看看她吧,这个宏
定义了一个mod_metadata结构,并在modmetadata_set中保存一个指向这个结构的指针,内核在加载了KLD后根据这些指针找到所有
的这些结构。mod_metadata有三种作用,MDT_DEPEND声明模块间的依赖关系,MDT_MODULE声明模块,MDT_VERSION声
明模块的版本。
struct mod_metadata {
int md_version;
int md_type;
void *md_data;
const char *md_cval;
};
md_type指明了这个结构的作用
md_type为MDT_DEPEND时,md_cval为所依赖的模块名称,而md_data则指向一个mod_depend结构;
md_type为MDT_MODULE时,md_cval为声明的模块名称,而md_data则指向一个moduledata结构;
md_type为MDT_VERSION时,md_cval为模块的名称,而md_data则指向一个说明该模块版本的mod_version结构。
***为什么要make install?前面的例子我们都没有安装。
make install将模块安装到/boot/kernel下面,然后执行kldxref
/boot/kernel。在加载dephello以前,内核发现dephello依赖于hello,那么他将查找包含合适版本的模块hello的KLD
文件并加载(/sys/kern/kern_linker.c中函数linker_load_dependencies),查找时需要所查找的目录(内核
查找的目录包括/boot/kernel
/boot/modules)下的linker.hints文件,根据该文件的记录,内核决定应该加载哪个KLD文件。kldxref的作用就是在
linker.hints文件中记录哪些模块的哪些版本包含在哪些KLD文件中(kldxref的详细用法参看man kldxref)。
****这个实验做完了,我不想在/boot/kernel下保存这些KLD,所以,我
#rm -i /boot/kernel/dephello.ko
#rm -i /boot/kernel/hello*
#kldxref /boot/kernel
5.在KLD中使用非全局变量
一直以为KLD是不能使用内核中用static修饰定义的变量(Linux内核中是这样的,只能使用导出的有限的符号),但是今天实验了一下,发现没有这
个限制,在KLD中可以使用内核中所有的变量,可以做个实验,比如kld_mtx在kern/kern_linker.c中定义为static,但是,在
KLD中我们依然可以找出他的地址,只要使用以前声明一下。
之所以要使用这种变量是因为内核给我们封装的功能有时侯无法满足我们的需要,比如,我要直接操纵所有KLD的链表,所有module的链表,那么我需要首先锁住kld_mtx。
6.内核中KLD的实现简介
内核中每个KLD文件使用一个linker_file结构来描述,这个结构记录了文件的名称,引用计数,唯一ID,文件之间的依赖关系以及特定文件格式相
关信息。所有的linker_file使用一个TAILQ连接起来(kern/kern_linker.c
linker_files),每个linker_file使用TAILQ记录了文件中的所含有的所有module(TAILQ_HEAD(,
module) modules)。
对于模块(module),内核使用module结构来描述。每个module结构记录了模块名称,引用计数,唯一ID,包含模块的
linker_file,事件(加载,卸载)处理函数等。所有加载的module通过TAILQ连接起来(kern/kern_module.c
modules),如前面所说,每个linker_file中的module也通过TAILQ连接起来。
另外,为了处理模块的版本问题,内核用一个TAILQ(kern/kern_linker.c found_modules)记录所加载的模块及其版本。
1>加载KLD模块的任务主要由linker_load_module( kern/kern_linker.c )完成。
<1> 如果只是给定了module名,那么它首先根据module名来确定需要加载的KLD文件的路径(这里会用到搜索目录下的linker.hints文件,参看kern/kern_linker.c 中函数 linker_search_module)
<2>然后进行模块是否已经加载等常规检查
<3>接下来调用格式相关的加载方法LINKER_LOAD_FILE来将KLD文件加载进入内存并进行重定位等操作
<4>然后注册KLD文件中的module(使用前面提到的mod_metadata),把他们加入到相应的TAILQ中,包括全局的TAILQ与KLD文件的TAILQ;注册sysctl;使用SYSINIT框架初始化(使用前面提到的sysinit结构)
<5>最后如果这个KLD文件是因为其他模块需要而加载进来的,那么在它的linker_file中记录下这种依赖关系
对于格式相关的加载方法LINKER_LOAD_FILE的实现,可以在kern/link_elf.c 与
kern/link_elf_obj.c中找到,他们在对KLD文件进行重定位以前会调用linker_load_dependencies
(kern/kern_linker.c)来加载这个KLD文件所依赖的KLD文件。另外他们通过调用linker_make_file
(kern/kern_linker.c )来分配一个新的linker_file并将其连接到TAILQ中。
2>卸载KLD文件主要由linker_file_unload( kern/kern_linker.c )完成
<1>首先减少linker_file的计数器,如果为0那么继续
<2>对linker_file包含的每个module通知卸载事件( MOD_UNLOAD )并减少他们的引用计数,如果到0,释放module
<3>从found_modules TAILQ中去掉KLD文件中包含的module的版本信息
<4>如果是正常卸载,那么通过SYSUNINIT框架执行清理代码,取消注册的sysctl.
<5>从全局TAILQ中删除linker_file,释放因为依赖而对其他KLD文件进行的引用计数
<6>使用LINKER_FILE_UNLOAD进行格式相关的卸载操作
7.一个实际的KLD文件加载过程
我们使用前面 4 中的代码来看一下实际的加载KLD加载过程.注意的是,我们已经把dephello等等模块安装到了/boot/kernel下.
执行加载命令
[root@~]#kldload dephello
首先看看kldload(8 )的实现,代码在/usr/src/sbin/kldload,比较简单直接使用了kldload(2),而kldload(2)的实现在kern/kern_linker.c kldload函数.
首先系统调用后内核进入函数kldload(kern/kern_linker.c)
<1>首先调用securelevel_gt与suser检查权限,现在我们是root用户而且安全等级是默认的-1所以通过安全检查
<2>把字符串参数复制进内核 pathname指向新分配的内存,把"dephello"复制到pathname中
<3>确定它是KLD文件名还是模块名,由于"dephello"中没有`.'与`/'所以认为它是模块名,调用
linker_load_module,KLD文件名为NULL,模块名为"dephello",我们不是以为其他KLD文件的依赖而加载,所以
parent参数为NULL,版本信息为NULL,并讲结果保存到lf中
<4>进入linker_load_module(kern/kern_linker.c)函数
首先查找"dephello"是否已经加载,调用modlist_lookup2,
在modlist_lookup2发现没有版本信息所以直接使用modlist_lookup
在found_moudles指向的TAILQ上查找是否有名字为"dephello"的模块,因为我们以前没有加载dephello所以查找的结果是不
存在.所以可以继续进行加载过程
<5>因为KLD文件名为NULL,所以调用linker_search_module来确定包含"dephello"的KLD文件
<6>进入linker_search_module(kern/kern_linker.c)
在linker_path中指定的所有目录下使用linker_hints_lookup查找包含"dephello"的KLD文件
<7>进入linker_hints_lookup(kern/kern_linker.c)函数
首先打开参数path指定的目录下的"linker.hints"文件并将其读入内存,然后在查找包含"dephello"的KLD文件,找到文件名
"dephello.ko"后,使用linker_lookup_file来确定"dephello.ko"是存在.最后返回结果
"/boot/kernel/dephello.ko"
<8>linker_search_module返回结果"/boot/kernel/dephello.ko",回到linker_load_module
<9>调用linker_find_file_by_name查找"dephello.ko"是否已经加载,
linker_find_file_by_name遍历linker_files指向的TAILQ查找"dephello,ko",没有找到,继续加载过
程
<10>调用linker_load_file加载"/boot/kernel/dephello.ko"
linker_load_file首先调用linker_find_file_by_name查找"/boot/kernel/dephello.ko"是否加载,没有找到,继续加载过程
<11>调用LINKER_LOAD_FILE来进行格式相关的加载,经过kobj处理,最终调用link_elf_load_file
(kern/link_elf.c)来加载"/boot/kernel/dephello.ko".首先进行ELF格式的处理,然后调用
linker_make_file来产生新的linker_file,得到的新linker_file的名字为"dephello.ko",并连接进
linker_files 所指向的TAILQ中
<12>调用linker_load_dependencies来加载"dephello.ko"所依赖的KLD.进入
linker_load_dependencies
(kern/kern_linker.c),因为所有的KLD都依赖于内核,所以首先给"dephello.ko"注册一个对"kernel"的依赖
(linker_file_add_dependency kern/kern_linker.c)
<13>找到"dephello.ko"中modmetadata_set的开始,因为这其中包含了对其他KLD的依赖信息以及模块的版本信息(一组指向mod_metadata结构的指针)
<14>首先检查版本信息,确定"dephello.ko"中所有的模块的相应版本没有加载,因为我们没有在"dephello.ko"中声明版本信息,所以,这一步不会执行
<15>检查依赖信息,根据指针指向的mod_metadata结构的内容确定"dephello.ko"所依赖的模块名,我们这里是
"hello",最接受版本2,接受版本1--3.首先确定"hello"不在"dephello.ko"中,然后调用modlist_lookup2来
查找版本在1--3间最好为2的"hello"是否加载,这里没有加载
<16>调用linker_load_module来加载需要版本的"hello"模块,并且在参数中指出是因为"dephello.ko"依赖而加载
<17>调用linker_addmodules将"dephello.ko"中的模块的版本信息加入到found_modules指向的TAILQ中,由于"dephello.ko"中没有版本信息,所以什么也不做
<18>返回到link_elf_load_file(kern/link_elf.c)
link_elf_load_file进行一系列的ELF处理后返回linker_load_file
<19>完成了格式相关的加载,调用liner_file_register_modules注册"dephello.ko"中的模块.在
linker_file_register_modules中,首先找到modmetadata_set,根据他找到声明模块的mod_metadata
结构,根据结构的内容调用module_register来注册"dephello.ko"中的模块"dephello",包括将"dephello"模
块连接到modules指向的TAILQ中,连接到"dephello.ko"的linker_file的TAILQ中.
<20>调用linker_file_register_sysctls来注册sysctl, "dephello.ko"中没有sysctl,所以这一步略过
<21>调用linker_file_sysinit来使用SYSINIT框架初始化"dephello.ko"这里,就是调用module_register_init
<22>在module_register_init中简单的根据模块名("dephello" )找到加载的模块,然后通知MOD_LOAD事件,我们这里也就是调用deph_load
<23>从linker_load_file返回linker_load_module, linker_load_module检查需要加载的模块("dephello" )是否已经加载
<24>返回kldload,完成加载,把"dephello.ko"的ID返回给进程
-------<休息一下--待续
>--------