内核EXPORT_SYMBOL函数讲解(二)

本文深入解析EXPORT_SYMBOL宏作用及ksymtab数据栈在Linux内核模块加载过程中的运用,阐述模块导出符号存储机制,以及内核如何通过ksymtab查找和验证模块符号,确保模块间的符号唯一性和兼容性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

书接上文,在

https://blog.youkuaiyun.com/weixin_37867857/article/details/84526808

这个博客里面写到EXPORT_SYMBOL函数使用就是把导出的符号以符号值+符号的字符串表示的形式表示的。只是讲解了EXPORT_SYMBOL宏的作用就是把导出的符号以符号名称+符号值的形式存储在struct kernel_symbol为数据结构的文件里面,如果文件加载过程中则是把数据加载在内存里,具体的存储地方则是ksymtab数据栈里。如果模块编译成内核,则是编译进内核的ksymtab的数据栈里。
但是还没有讲解模块加载过程中对于ksymtab数据栈的操作。本章就讲解下该数据栈的作用以及使用。
所有的已经加载的模块在内核中都有一个struct module的表示,并且加入进去modules的全局链表中。其中struct module关于导出的符号的表示如下:

struct module{
	....
/* Exported symbols */
        const struct kernel_symbol *syms;
        /***存储着模块导出的符号的数组**/
        const unsigned long *crcs;//校验
        unsigned int num_syms;
        /***存储着模块导出的符号的数量****/

        /* GPL-only exported symbols. */
        unsigned int num_gpl_syms;
        /***存储模块导出gpl符号的数组长***/
        const struct kernel_symbol *gpl_syms;
        /***存储着模块导出GPL符号的数组的个数***/
        const unsigned long *gpl_crcs;//校验
	....
	};

内核在模块加载过程中先获取正在加载的模块的符号表

static int verify_export_symbols(struct module *mod)
{
        unsigned int i;
        struct module *owner;
        const struct kernel_symbol *s;
        /***先获取正在加载模块的符号表并且存放在arr数组中**/
        struct {
                const struct kernel_symbol *sym;
                unsigned int num;
        } arr[] = {
                { mod->syms, mod->num_syms },
                { mod->gpl_syms, mod->num_gpl_syms },
                { mod->gpl_future_syms, mod->num_gpl_future_syms },
#ifdef CONFIG_UNUSED_SYMBOLS
                { mod->unused_syms, mod->num_unused_syms },
                { mod->unused_gpl_syms, mod->num_unused_gpl_syms },
#endif
        };
	/***遍历arr数组中每一个struct kernel_symbol结构***/
        for (i = 0; i < ARRAY_SIZE(arr); i++) {
                for (s = arr[i].sym; s < arr[i].sym + arr[i].num; s++) {
                	/***查找符号名称是否是已知模块使用,若果是已知模块使用则把模块内容加入到owner中**/
                        if (find_symbol(s->name, &owner, NULL, true, false)) {
                                printk(KERN_ERR
                                       "%s: exports duplicate symbol %s"
                                       " (owned by %s)\n",
                                       mod->name, s->name, module_name(owner));
                                return -ENOEXEC;
                        }
                }
        }
        return 0;
}

以下是根据导出的符号名称查找的的过程:

	1.根据内核镜像加载过程中导出的符号查找,保存在:
	                { __start___ksymtab, __stop___ksymtab, __start___kcrctab,
                  NOT_GPL_ONLY, false },
                { __start___ksymtab_gpl, __stop___ksymtab_gpl,
                  __start___kcrctab_gpl,
                  GPL_ONLY, false },
                { __start___ksymtab_gpl_future, __stop___ksymtab_gpl_future,
                  __start___kcrctab_gpl_future,
                  WILL_BE_GPL_ONLY, false },
#ifdef CONFIG_UNUSED_SYMBOLS
                { __start___ksymtab_unused, __stop___ksymtab_unused,
                  __start___kcrctab_unused,
                  NOT_GPL_ONLY, true },
                { __start___ksymtab_unused_gpl, __stop___ksymtab_unused_gpl,
                  __start___kcrctab_unused_gpl,
                  GPL_ONLY, true },
	全局变量里;
	2.遍历各个模块,查找各个模块导出的符号表。

以下代码是查找符号表的过程:

const struct kernel_symbol *find_symbol(const char *name,
                                        struct module **owner,
                                        const unsigned long **crc,
                                        bool gplok,
                                        bool warn)
{
        struct find_symbol_arg fsa;

        fsa.name = name;
        fsa.gplok = gplok;
        fsa.warn = warn;
	/**each_symbol是遍历每一个由镜像或者内核模块导出的符号表;
           find_symbol_in_section则是一个回调函数,由each_symbol调用,执行真正
           的查找过程
	   查找的顺序如下:
	   	1.查找由于内核镜像导出的符号表;
	   	2.查找由于各个模块导出的符号表;
	**/
        if (each_symbol(find_symbol_in_section, &fsa)) {
                if (owner)
                        *owner = fsa.owner;
                if (crc)
                        *crc = fsa.crc;
                return fsa.sym;
        }

        DEBUGP("Failed to find symbol %s\n", name);
        return NULL;
}

以下是find_symbol_in_section执行过程:
find_symbol_in_section则是一个回调函数,由each_symbol调用,执行真正
的查找过程:

static bool find_symbol_in_section(const struct symsearch *syms,
                                   struct module *owner,
                                   unsigned int symnum, void *data)
{
        struct find_symbol_arg *fsa = data;
	/**比较开始的队列的符号名称,如果比较失败则返回false*/
        if (strcmp(syms->start[symnum].name, fsa->name) != 0)
                return false;

        if (!fsa->gplok) {
                if (syms->licence == GPL_ONLY)
                        return false;
                if (syms->licence == WILL_BE_GPL_ONLY && fsa->warn) {
                        printk(KERN_WARNING "Symbol %s is being used "
                               "by a non-GPL module, which will not "
                               "be allowed in the future\n", fsa->name);
                        printk(KERN_WARNING "Please see the file "
                               "Documentation/feature-removal-schedule.txt "
                               "in the kernel source tree for more details.\n");
                }
        }

#ifdef CONFIG_UNUSED_SYMBOLS
        if (syms->unused && fsa->warn) {
                printk(KERN_WARNING "Symbol %s is marked as UNUSED, "
                       "however this module is using it.\n", fsa->name);
                printk(KERN_WARNING
                       "This symbol will go away in the future.\n");
                printk(KERN_WARNING
                       "Please evalute if this is the right api to use and if "
                       "it really is, submit a report the linux kernel "
                       "mailinglist together with submitting your code for "
                       "inclusion.\n");
        }
#endif
	/***如果比较成功则返回true,并且把模块的指针赋予fsa->owner**/
        fsa->owner = owner;
        fsa->crc = symversion(syms->crcs, symnum);
        fsa->sym = &syms->start[symnum];
        return true;
}

each_symbol执行则相对简单不在赘述,大体上说一下执行过程:

	struct symsearch {
        const struct kernel_symbol *start, *stop;
        const unsigned long *crcs;
        enum {
                NOT_GPL_ONLY,
                GPL_ONLY,
                WILL_BE_GPL_ONLY,
        } licence;
        bool unused;
	};
	这个结构体在each_symbol函数里面经常用到,用于临时保存导出符号的符号表;
	1.先导出内核每一个模块到struct symsearch类型的数组里面。
	2.遍历保存的symsearch数组,从start->stop过程;
	3.遍历每一个模块,把每个模块导出的符号表导出到symsearch数组里面;
	4.执行过程2
<think>好的,用户想了解 Linux 内核中的 EXPORT_SYMBOL 函数的使用说明和示例。首先,我需要回忆一下相关知识,并结合提供的引用内容来组织回答。 首先,用户提到的引用[1]和[2]已经解释了EXPORT_SYMBOL的基本作用,即导出符号供其他模块使用。需要先介绍它的基本概念和用途,然后说明其工作原理,比如符号表管理和模块加载过程。引用[4]提到宏定义展开和链接脚本,这部分可能需要稍微深入一点,但用户可能更关注用法,所以可以简要提及。 接下来,用户需要使用说明,应该分步骤讲解如何在代码中使用EXPORT_SYMBOL,比如定义函数导出符号、其他模块声明并使用。同时,要给出示例代码,像引用[1]中的示例,但可能需要更详细,比如两个模块代码示例,一个导出,一个使用。 注意事项部分需要包括作用域、依赖关系、版本兼容性等,引用[2]和[4]可能有相关内容,比如符号表管理和版本问题。此外,要提到GPL协议的问题,因为内核模块如果使用EXPORT_SYMBOL_GPL的话需要注意许可证兼容性。 然后生成相关问题,要围绕EXPORT_SYMBOL的相关知识点,比如原理、与GPL版本的区别、调试方法、应用场景等。 需要注意的是,回答要符合用户的要求,结构清晰,分点说明,同时正确使用LaTeX格式,比如变量名用$...$包裹,代码块用三个反引号标注语言。另外,引用标识要正确添加到对应的引用内容后面,比如在提到导出符号时引用[1],在原理部分引用[2]和[4]。 最后,检查是否有遗漏的信息,比如是否覆盖了使用步骤、示例、注意事项,以及相关问题是否相关。确保语言口语化,避免使用专业术语过多,保持自然的中文表达。</think>### EXPORT_SYMBOL 函数介绍及用法 #### 1. 核心功能 `EXPORT_SYMBOL` 是 Linux 内核提供的宏,用于将模块中的**函数或变量**导出内核符号表,使其他内核模块能跨模块调用这些符号[^1]。这是实现模块间动态交互的基础机制。 #### 2. 工作原理 - **符号表管理**:导出符号会被记录在`__ksymtab`段中,形成全局可见的符号表[^4] - **模块依赖**:加载模块时,内核通过符号表解析跨模块调用关系[^2] - **地址绑定**:编译时生成未解析的符号引用,运行时通过符号表完成地址绑定[^4] #### 3. 使用步骤 1. **定义待导出函数** ```c // module_a.c void my_exported_func(void) { printk("Exported function called\n"); } EXPORT_SYMBOL(my_exported_func); // 关键导出语句 ``` 2. **外部模块调用** ```c // module_b.c extern void my_exported_func(void); // 声明外部符号 static int __init mod_init(void) { my_exported_func(); // 跨模块调用 return 0; } ``` #### 4. 完整示例 **导出模块 (export_demo.c):** ```c #include <linux/init.h> #include <linux/module.h> int shared_var = 100; EXPORT_SYMBOL(shared_var); // 导出变量 void demo_func(void) { printk(KERN_INFO "Demo function executed\n"); } EXPORT_SYMBOL(demo_func); // 导出函数 MODULE_LICENSE("GPL"); ``` **调用模块 (use_demo.c):** ```c #include <linux/init.h> #include <linux/module.h> extern int shared_var; // 导入变量 extern void demo_func();// 导入函数 static int __init mod_init(void) { printk("Shared value: %d\n", shared_var); demo_func(); return 0; } module_init(mod_init); MODULE_LICENSE("GPL"); ``` #### 5. 关键注意事项 1. **符号可见性**:导出符号必须是非静态(non-static)的 2. **依赖顺序**:被调用模块需要先于调用模块加载 3. **版本控制**:使用`EXPORT_SYMBOL_GPL`限制仅GPL兼容模块可用[^4] 4. **命名冲突**:避免与内核已有符号重名 5. **内存安全**:确保导出符号的生命周期覆盖所有调用者 #### 6. 调试技巧 - 查看符号表:`cat /proc/kallsyms | grep <symbol>` - 模块依赖检查:`modinfo <module_name>` - 动态加载测试:`insmod`/`rmmod`顺序验证
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值