Linux 内核代码中“EXPORT_SYMBOL”的含义是什么?

问题描述 投票:0回答:3

这里

 48 struct snd_card *snd_cards[SNDRV_CARDS];
 49 EXPORT_SYMBOL(snd_cards);

我不明白它的含义是什么以及为什么使用它。我试图搜索它,但不明白它的含义。

c linux-kernel
3个回答
57
投票

它使动态加载的模块可以访问符号(前提是所述模块添加

extern
声明)。

不久前,有人问如何使用


12
投票

这里有一个很好的解释。

https://www.quora.com/What-is-the-difference- Between-extern-and-EXPORT_SYMBOL-in-Linux-kernel-codes

Extern 是一个 C 存储类关键字。在内核中,就像在任何其他 C 中一样 代码,它告诉编译器变量的定义或 它所限定的功能是在另一个“文件”中实现的,或者更确切地说, 更准确地说 翻译单元(编程)- 维基百科。这 定义它的翻译单元不应使用静态 预选赛。因此,符号表中有一个条目对应于 它。在链接时,符号会正常解析。没有什么 内核特定于“extern”。

EXPORT_SYMBOL() 是 Linux 内核头文件定义的宏。它没有 与 extern 有很多共同点。它告诉 kbuild 机制 引用的符号应该是内核全局列表的一部分 符号。这反过来又允许内核模块访问它们。编码 内置于内核本身(而不是模块)中可以, 当然,通过外部声明访问任何非静态符号,在 与常规 C 一致。 EXPORT_SYMBOL() 机制允许我们 导出一个符号供可加载模块使用。一个有趣的 问题是由一个模块导出的符号变得可访问 到可能依赖它的另一个模块!

总而言之,extern 不是特定于内核的。它用于限定 来自另一个翻译单元的非静态符号的声明。 EXPORT_SYMBOL() 特定于 Linux 内核。它用于 定义的翻译单元,使符号可用于 可加载模块。

所以 EXPORT_SYMBOL 只是一种类似于 extern 的机制,但它是用于可加载模块之间的引用而不是文件之间的引用。

继续往下看,我们可以猜测它是由 extern 实现的,因为 extern 是 C 形式,这是基础。

这是一条线索。

https://elixir.bootlin.com/linux/v4.6.7/source/include/linux/export.h#L56

#define EXPORT_SYMBOL(sym)                  \
    __EXPORT_SYMBOL(sym, "")

/* For every exported symbol, place a struct in the __ksymtab section */
#define __EXPORT_SYMBOL(sym, sec)               \
    extern typeof(sym) sym;                 \
    __CRC_SYMBOL(sym, sec)                  \
    static const char __kstrtab_##sym[] __attribute__((section("__ksymtab_strings"), aligned(1)))  = VMLINUX_SYMBOL_STR(sym);               \
    extern const struct kernel_symbol __ksymtab_##sym;  \
    __visible const struct kernel_symbol __ksymtab_##sym    __used __attribute__((section("___ksymtab" sec "+" #sym), unused)) = { (unsigned long)&sym, __kstrtab_##sym }

首先声明一个外部符号。

然后是一个字符串 __kstrtab_##sym == VMLINUX_SYMBOL_STR(sym)。

最后一个外部结构 kernel_symbol __ksymtab_##sym = { (unsigned long)&sym, __kstrtab_##sym }。 &sym 记录函数或变量等 sym 的真实地址,_kstrtab##sym 记录名称字符串。


7
投票

本身不是答案,而是一个演示,正如我的评论所承诺的那样,导出的符号不需要是非静态的。 以下 2 个模块演示了这一点:

/* mod1.c */
#include <linux/module.h>

static int mod1_exp_func(int i)
{
    pr_info("%s:%d the value passed in is %d\n",
            __func__, __LINE__, i);

    return i;
}
EXPORT_SYMBOL(mod1_exp_func); /* export static symbol */

static int __init mod1_init(void)
{
    pr_info("Initializing simple mod\n");
    return 0;
}

static void __exit mod1_exit(void)
{
    pr_info("This module is exiting\n");
}

module_init(mod1_init);
module_exit(mod1_exit);
MODULE_LICENSE("GPL v2");

还有第二个模块

/* mod2.c */
#include <linux/module.h>

extern int mod1_exp_func(int);

static int __init mod2_init(void)
{
    pr_info("Initializing mod2\n");
    pr_info("Calling exported function in mod1\n");
    mod1_exp_func(3);
    return 0;
}

static void __exit mod2_exit(void)
{
    pr_info("mod2 exiting\n");
}

module_init(mod2_init);
module_exit(mod2_exit);
MODULE_LICENSE("GPL v2");

这些在 CentOS 6 和 CentOS 7 上进行了测试:内核 2.6.32 和 3.10(分别)。 加载 mod1.ko,然后加载 mod2.ko 将导致传递给 mod1_exp_func() 的值被打印到内核日志缓冲区。

© www.soinside.com 2019 - 2024. All rights reserved.