不知大家有没有想过,在一个内核模块代码中,会用到printk
函数,而这个函数不是我们实现的,它是内核代码的一部分,但我们为什么能够编译通过呢?
我们的代码之所以能够编译通过,是因为对模块的编译仅仅是编译,并没有链接。
.ko
文件是一个普通的ELF
文件,使用file
命令和nm
命令,我们可以看到相关的信息:# file vser.ko
vser.ko ELF 32-bit LSB relocatable, Intel 80386, vserion 1 (SYSV), BuildID[sha1]=0x09ca747e6f75c65v19a5da9102113v98d7cea24, not stripped
# nm vser.ko
......
00000004 d port
U printk
00000000 t vser_exit
00000000 t vser_init
vser_init
和vser_exit
分别是模块的入口函数和出口函数,使用nm
命令查看模块目标文件的符号信息时,可以看到vser_exit
和vser_init
的符号类型是t
,表示它们是函数。而printk
的 符号类型是U
,表示它是一个未决符号。意思是说在编译阶段不知道这个符号的地址,因为它被定义在其他文件中,没有放在模块代码一起编译。
EXPORT_SYMBOL
宏将printk
导出即可。EXPORT_SYMBOL
宏生成一个特定的结构并放在ELF
文件的一个特定段中,在内核的启动过程中,会将符号的确切地址填充到这个结构的特定成员中。vser.c
调用了dep.c
中的变量和函数:#include
#include
#include
extern int expval;
extern void expfun(void);
static int __init vser_init(void)
{
printk("vser_init\");
printk("expval:%d\n", expval);
expfun();
return 0;
}
static void __exit vser_exit(void)
{
printk("vser_exit\n");
}
module_init(vser_init);
module_exit(vser_exit);
dep.c
#include
#include
static int expval = 5;
EXPORT_SYMBOL(expval);
static void expfun(void)
{
printk("expfun");
}EXPORT_SYMBOL_GPL(expfun);
Makefile关键处:
obj-m := vser.o
obj-m += dep.o
上述代码中,dep.c定义了一个变量expval
和一个函数expfun
,并分别用EXPORT_SYMBOL
和EXPORT_SYMBOL_GPL
导出。而vser.c
里则调用了dep.c
的变量和函数,编译安装后:
# modprobe vser
# dmesg
[58278.204677] vser_init
[58278.204683] expval:5
[58287.206464] expfun
从输出信息中可以看到,vser.c
正确引用到了dep.c
的变量和函数。
insmod
命令加载模块,则必须先加载dep模块,再加载vser模块。modprobe
命令优于insmod
命令的地方在于其可以自动加载被依赖的模块。而这又要归功于depmod
命令,depmod
命令会生成模块的依赖信息,保存在/lib/modules/5.10.111-64-generic/modules.dep
文件中。其中,5.10.111-64-generic是内核源码版本。查看该文件可以发现vser模块所依赖的模块。# cat /lib/modules/5.10.111-64-generic/modules.dep
......
extra/vser.ko: extra/dep.ko
extra/dep.ko:
WARNING: "expfun" [/home/ubuntu/driver/module/vser.ko] undefined!
WARNING: "expval" [/home/ubuntu/driver/module/vser.ko] undefined!
# sudo insmod dep.ko
# sudo insmod vser.ko
insmod:error inserting 'vser.ko': -1 Invalid parameters
这是因为在编译vser
模块时在内核的符号表中找不到expval
和expfun
的项,而vser
模块又完全不知道dep
模块的存在。
内核将会创建模块依赖关系的链接,只有当依赖于这个模块的链表为空时,模块才能被卸载。
END
→点关注,不迷路←