书接上文,在
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;