如何从另一个模块调用导出的内核模块函数?

我正在编写一个API作为内核模块,为设备驱动程序提供各种function。 我在mycode.c中写了三个函数。 然后我构建并加载了模块,然后将mycode.h复制到 / include / linux中 。 在设备驱动程序中,我有一个#include 并调用这三个函数。 但是当我构建驱动程序模块时,我收到三个链接器警告,说明这些函数是未定义的

笔记:

  • 函数在mycode.h中声明为extern
  • 使用mycode.c中的EXPORT_SYMBOL(func_name)导出函数
  • 运行命令nm mycode.ko显示符号表中可用的所有三个函数(它们旁边的大写字母T,表示符号在文本(代码)部分中找到)
  • 加载模块后,命令grep func_name / proc / kallsyms将所有三个函数显示为已加载

很明显,函数正确导出,内核知道它们的位置和位置。 那么为什么司机不能看到他们的定义呢? 知道我错过了什么吗?


编辑:我在这里找到了一些相关信息: http //www.kernel.org/doc/Documentation/kbuild/modules.txt

有时,外部模块使用来自另一个外部模块的导出符号。 kbuild需要完全了解所有符号,以避免吐出有关未定义符号的警告。 这种情况存在三种解决方案。

注意:建议使用顶级kbuild文件的方法,但在某些情况下可能不切实际。

使用顶级kbuild文件如果你有两个模块,foo.ko和bar.ko,其中foo.ko需要来自bar.ko的符号,你可以使用一个通用的顶层kbuild文件,所以这两个模块都是用同一个编译的建立。 请考虑以下目录布局:

./foo/ <= contains foo.ko ./bar/ <= contains bar.ko The top-level kbuild file would then look like: #./Kbuild (or ./Makefile): obj-y := foo/ bar/ And executing $ make -C $KDIR M=$PWD will then do the expected and compile both modules with full 

任何一个模块的符号知识。

使用额外的Module.symvers文件构建外部模块时,会生成Module.symvers文件,其中包含未在内核中定义的所有导出符号。 要从bar.ko访问符号,请将module.symvers文件从bar.ko的编译复制到构建foo.ko的目录。 在模块构建期间,kbuild将读取外部模块目录中的Module.symvers文件,并且在构建完成时,将创建一个新的Module.symvers文件,其中包含已定义的所有符号的总和,而不是内核的一部分。

使用“make”变量KBUILD_EXTRA_SYMBOLS如果从另一个模块复制Module.symvers是不切实际的,则可以在构建文件中将空格分隔的文件列表分配给KBUILD_EXTRA_SYMBOLS。 这些文件将在其符号表初始化期间由modpost加载。

但是对于所有这三种解决方案,为了让任何驱动程序都使用我的API,它必须创建一个新的Makefile或直接访问我的Module.symvers文件? 这看起来有点不方便。 我希望他们能够#include我的头文件并且好好去。 不存在其他替代方案吗?

从我的研究来看,似乎这是处理这种情况的唯一三种方法,我已经让它们各自起作用了,所以我想我会选择我最喜欢的那些。

最小的QEMU + Buildroot示例

我已经在完全可重现的QEMU + Buildroot环境中测试了以下内容,因此可能有这个工作版本版本将帮助您找出代码的wro。

GitHub上游以文件为中心: dep.c | dep2.c | Makefile文件

dep.c:

 #include  /* usleep_range */ #include  #include  #include  MODULE_LICENSE("GPL"); int lkmc_dep = 0; EXPORT_SYMBOL(lkmc_dep); static struct task_struct *kthread; static int work_func(void *data) { while (!kthread_should_stop()) { printk(KERN_INFO "%d\n", lkmc_dep); usleep_range(1000000, 1000001); } return 0; } static int myinit(void) { kthread = kthread_create(work_func, NULL, "mykthread"); wake_up_process(kthread); return 0; } static void myexit(void) { kthread_stop(kthread); } module_init(myinit) module_exit(myexit) 

dep2.c:

 #include  /* usleep_range */ #include  #include  #include  MODULE_LICENSE("GPL"); extern int lkmc_dep; static struct task_struct *kthread; static int work_func(void *data) { while (!kthread_should_stop()) { usleep_range(1000000, 1000001); lkmc_dep++; } return 0; } static int myinit(void) { kthread = kthread_create(work_func, NULL, "mykthread"); wake_up_process(kthread); return 0; } static void myexit(void) { kthread_stop(kthread); } module_init(myinit) module_exit(myexit) 

现在你可以这样做:

 insmod dep.ko insmod dep2.ko 

但Buildroot也已经使用依赖项配置depmod /lib/module/*/depmod ,所以这足以加载两者:

 modprobe dep 

如果使用CONFIG_KALLSYMS_ALL=y ,则可以看到符号:

 grep lkmc_dep /proc/kallsyms 

另见: kallsyms是否具有内核函数的所有符号?

好的:你有一个function所在的模块和一个想要导入的模块吗?

你必须在函数所在的地方使用“EXPORT_SYMBOL(”函数的名称“),例如foo。所以在”c“文件中定义了函数”foo“并放入:EXPORT_SYMBOL(foo)

确保你在一个公共的头文件中有“foo”的原型(你可以在每个模块的不同位置使用它,它会起作用,但如果签名改变你会遇到麻烦)。 所以说:void foo(void * arg);

然后另一个想要它的模块只是调用“foo”而你很好。

另外:确保首先使用foo加载模块。 如果你有像module2那样的交叉依赖,需要来自module1的foo,而module1需要来自module2的bar,你需要有一个寄存器function与另一个。 如果您想了解请另外询问Q.