28

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

笔记:

  • 这些函数在 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

顶级 kbuild 文件将如下所示:

  #./Kbuild (or ./Makefile): 
      obj-y := foo/ bar/

并执行

  $ make -C $KDIR M=$PWD

然后将执行预期并编译两个模块,并充分了解任一模块的符号。

使用额外的 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 我的头文件并且一切顺利。不存在其他替代方案吗?

4

3 回答 3

8

根据我的研究,似乎只有这三种方法可以处理这种情况,而且我已经让它们中的每一种都起作用了,所以我想我会从中挑选出我最喜欢的。

于 2012-09-08T14:34:42.390 回答
3

最小的 QEMU + Buildroot 示例

我已经在一个完全可重现的 QEMU + Buildroot 环境中测试了以下内容,所以也许拥有这个工作版本将帮助你找出你的代码有什么问题。

GitHub 上游以文件为中心:

dep.c

#include <linux/delay.h> /* usleep_range */
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/module.h>

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

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 设置,事情已经/lib/module/*/depmod在使用依赖项配置 depmod,所以这足以加载两者:

modprobe dep

此外,如果您使用 构建内核CONFIG_KALLSYMS_ALL=y,则可以通过以下方式看到导出的符号:

grep lkmc_dep /proc/kallsyms

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

于 2017-06-18T10:55:39.093 回答
0

OK:你有一个功能所在的模块和一个要导入它的地方,对吗?

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

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

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

另外:确保首先使用 foo 加载模块。如果您有交叉依赖项,例如 module2 需要来自 module1 的 foo ,而 module1 需要来自 module2 的 bar ,则您需要一个注册函数与另一个注册函数。如果你想知道,请单独问一个Q。

于 2018-03-25T22:29:24.140 回答