内核EXPORT_SYMBOL函数讲解(二)

书接上文,在

https://blog.csdn.net/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

猜你喜欢

转载自blog.csdn.net/weixin_37867857/article/details/84531268
今日推荐