一文讲解内核模块依赖!

前言

不知大家有没有想过,在一个内核模块代码中,会用到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_initvser_exit分别是模块的入口函数和出口函数,使用nm命令查看模块目标文件的符号信息时,可以看到vser_exitvser_init的符号类型是t,表示它们是函数

printk的 符号类型是U,表示它是一个未决符号。意思是说在编译阶段不知道这个符号的地址,因为它被定义在其他文件中,没有放在模块代码一起编译。

那printk函数的地址问题怎么解决呢?答案是用EXPORT_SYMBOL宏将printk导出即可。

EXPORT_SYMBOL导出符号

大致原理:利用EXPORT_SYMBOL宏生成一个特定的结构并放在ELF文件的一个特定段中,在内核的启动过程中,会将符号的确切地址填充到这个结构的特定成员中

模块加载时,加载程序将去处理未决符号,在特殊段中搜索符号的名字,如果找到,则将获得的地址填充在被加载模块的相应段中,这样符号的地址就可以确定。

使用这种方式处理未决符号,其实相当于把链接的过程推后,进行了动态链接,和普通的应用程序使用共享库函数的道理是类似的。可以发现,内核将会有大量的符号导出,为模块提供了丰富的基础设施。

内核模块依赖

通常情况下,一个模块只使用内核导出的符号,自己不导出符号。但是如果一个模块需要提供全局变量或函数给另外的模块使用,那么就需要将这些符号导出。

这在一个驱动调用另一个驱动代码时比较常见,这样模块和模块之间就形成了依赖关系,使用导出符号的模块将会依赖于导出符号的模块。

举个具体的例子,下面是两个C文件,vser.c调用了dep.c中的变量和函数:

vser.c

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

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 <linux/kernel.h>
#include <linux/module.h>

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_SYMBOLEXPORT_SYMBOL_GPL导出。而vser.c里则调用了dep.c的变量和函数,编译安装后:

# modprobe vser
# dmesg
[58278.204677] vser_init
[58278.204683] expval:5
[58287.206464] expfun

从输出信息中可以看到,vser.c正确引用到了dep.c的变量和函数。

  资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈

这里有三点重要说明:

  • 如果使用insmod命令加载模块,则必须先加载dep模块,再加载vser模块

因为vser模块用到了dep模块的东西。从这里可以看出,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模块时在内核的符号表中找不到expvalexpfun的项,而vser模块又完全不知道dep模块的存在。

解决这个问题的方法是将两个模块放在一起编译,或者将dep模块放在内核源码中,先在内核源码下编译完所有的模块,再编译vser模块。

  • 卸载模块时要先卸载vser模块,再卸载dep模块,否则会因为dep模块被vser模块使用而不能卸载

内核将会创建模块依赖关系的链接,只有当依赖于这个模块的链表为空时,模块才能被卸载.

猜你喜欢

转载自blog.csdn.net/youzhangjing_/article/details/130223514