模块的加载过程二(下)

简介: 模块的加载过程二(下)

在深入到这个函数内部之前,有必要先介绍几个数据结构,这几个数据结构将在find_symbol函数中用到。

//对应要查找的每一个符号表section,换句话说,对要查找的每个符号表section,内核代码都要为之产生一个struct symsearch类型的实例。
/*
 *NOT_GPL_ONLY符号由EXPORT_SYMBOL负责导出,
 *GPLONLY符号由EXPORT_SYMBOL_GPL负责导出,
 *WILL_BE_GPL_ONLY符号由EXPORT_SYMBOL_GPL_FUTURE负责导出。
 */
struct symsearch {
  const struct kernel_symbol *start, *stop;//指向对应符号表section的起始和结束地址
  const unsigned long *crcs;//导出符号的校验和数组
  enum {
    NOT_GPL_ONLY,//表示不一定要只给满足GPL协议的模块使用
    GPL_ONLY,//表示符号只提供给满足GPL协议的模块使用
    WILL_BE_GPL_ONLY,//表示将来只提供给满足GPL协议的模块使用。
  } licence;//许可证标记
  bool unused;//bool型的unused成员用来表示内核是否配置了CONFIG_UNUsED_SYMBOLS选项
};

struct symsearch用来对应要查找的每一个符号表section,换句话说,对要查找的每个符号表section,内核代码都要为之产生一个struct symsearch类型的实例。结构体中的成员变量和stop分别指向对应section的开始和结束地址,bool型的unused成员用来表示内核是否配置了CONFIG_UNUSED_SYMBOLS选项,不过这个选项是“非主流”的,长远看这个选项最终会消失,因此本书只在这里提一下,在后续的章节中将忽略所有该选项被启用时才起作用的代码·另一个比较重要的成员是型的licence,GPL_ONLY表示符号只提供给满足GPL协议的模块使用,NOT_GPL_ONLY表示不一定要只给满足GPL协议的模块使用,WILL_BE_GPLONLY表示将来只提供给满足GPL协议的模块使用。再提醒一下·NOT_GPL_ONLY符号由EXPORT_SYMBOL负责导出,GPLONLY符号由EXPORT_SYMBOL_GPL负责导出,WILL_BE_GPL_ONLY符号由EXPORT_SYMBOL_GPL_FUTURE负责导出。

//查找符号的标识参数
struct find_symbol_arg {
  /* Input */
  /*查找函数的输入*/
  const char *name;
  bool gplok;
  bool warn;
  /* Output */
  /*查找函数的输出*/
  struct module *owner;
  const unsigned long *crc;
  const struct kernel_symbol *sym;//一个用以表示内核符号构成的数据结构
};

find_symbol_arg用做查找符号的标识参数,可以看到其大部分数据成员与find_symbol函数原型中的参数完全一致,其中的kernel_symbol是一个用以表示内核符号构成的数据结构,在前面的“EXPORT_SYMBOL的内核实现”一节中介绍过。

find_symbol函数首先构造被查找模块的标识参数,然后通过each_symbol来查找符号。each_symbol是用来进行符号查找的主要函数,为节约篇幅起见,这里不再摘录其源代码,而是直接讲述其主要功能框架。

总体上,each_symbol_section函数可以分成两个部分:第一部分是在内核导出的符号表中查找对应的符号,如果找到,就通过返回该符号的信息,否则,再进行第二部分的查找:第2部分是在系统中己加载的模块(系统中所有己成功加载的模块都以链表的形式保存在一个全局变量modules中)的导出符号表中查找对应的符号,如果找到就通过返回该符号的信息,否则函数返回false。图1·6展示了find_symbol在查找一个符号时的搜索路径:

第一部分在对内核符号表进行查找时,首先构造一个struct symsearch类型的数组arr。

bool each_symbol_section(bool (*fn)(const struct symsearch *arr,
            struct module *owner,
            void *data),
       void *data)
{
  struct module *mod;
  static const struct symsearch arr[] = {
    { __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 },
#endif
  };
  if (each_symbol_in_section(arr, ARRAY_SIZE(arr), NULL, fn, data))
    return true;
  list_for_each_entry_rcu(mod, &modules, list) {
    struct symsearch arr[] = {//构造一个struct symsearch类型的数组arr
      { mod->syms, mod->syms + mod->num_syms, mod->crcs,
        NOT_GPL_ONLY, false },
      { mod->gpl_syms, mod->gpl_syms + mod->num_gpl_syms,
        mod->gpl_crcs,
        GPL_ONLY, false },
      { mod->gpl_future_syms,
        mod->gpl_future_syms + mod->num_gpl_future_syms,
        mod->gpl_future_crcs,
        WILL_BE_GPL_ONLY, false },
#ifdef CONFIG_UNUSED_SYMBOLS
      { mod->unused_syms,
        mod->unused_syms + mod->num_unused_syms,
        mod->unused_crcs,
        NOT_GPL_ONLY, true },
      { mod->unused_gpl_syms,
        mod->unused_gpl_syms + mod->num_unused_gpl_syms,
        mod->unused_gpl_crcs,
        GPL_ONLY, true },
#endif
    };
    if (mod->state == MODULE_STATE_UNFORMED)
      continue;
    if (each_symbol_in_section(arr, ARRAY_SIZE(arr), mod, fn, data))
      return true;
  }
  return false;
}

注意这里的__start__ksymtab、__start__kcrctab和_stop__ksymtab等变量己经在前面的“EXPORT_SYMBOL的内核实现”一节中交代过,它们在内核的链接脚本中定义,由链接器负责产生,由内核源码负责声明,现在到了使用它们的时候了。

接下来函数通过调用each_symbol_in_section查询内核的导出符号表,each_symbol_in_section的核心代码如下(经过适当改写):

each_symbol_section->each_symbol_in_section

static bool each_symbol_in_section(const struct symsearch *arr,
           unsigned int arrsize,
           struct module *owner,
           bool (*fn)(const struct symsearch *syms,
                struct module *owner,
                void *data),
           void *data)
{
  unsigned int j;
  for (j = 0; j < arrsize; j++) {
    if (fn(&arr[j], owner, data))
      return true;
  }
  return false;
}

为了在内核的导出符号表中查找某一指定的符号名,each_symbol_insection函数使用了两层循环:外层j引导的for循环用来遍历符号可能所在的内核导出符号表中的各section:内层i引导的for循环用来遍历外层j引导for循环所指定的section中的每个struct kernel_symbol类型的元素。对于每个kernel_symbol,都会调用find_symbol_in_setion函数。

为了清楚地理解内核加载模块时如何处理“未解决的引用”符号,有必要仔细分析一下find_symbol_in_section函数的主要功能。因为对Linux下的设备驱动程序员而言,几乎每天都在和这个功能打交道,清楚地理解其内核机制,将来一旦在加载模块时出现相关问题,也可以将其快速定位并最终解决。另外,对于带“GPL”后缀的符号名,在写驱动程序的内核模块时常常会遇到,然而其背后到底蕴涵着怎样的设计理念呢?通过分析find_symbol_in_section函数,就可以得到所需的答案:

static bool find_symbol_in_section(const struct symsearch *syms,
           struct module *owner,
           void *data)
{
  struct find_symbol_arg *fsa = data;
  struct kernel_symbol *sym;
  sym = bsearch(fsa->name, syms->start, syms->stop - syms->start,
      sizeof(struct kernel_symbol), cmp_name);
  if (sym != NULL && check_symbol(syms, owner, sym - syms->start, data))
    return true;
  return false;
}

函数首先用check_symbol函数来比较kernel_symbol结构体中的name与fsa中的name(正在查找的符号名,即要加载的内核模块中出现的“未解决的引用”的符号)是否匹配,如果不匹配,那么函数直接返回false。

fsa->gplok和fsa->warn的设定最早是在find_symbol函数中,是通过后者的函数参数传入的。fsa->wam主要用来控制警告信息的输出。fsa->gplok用来表示当前的模块是不是满足GPL协议(GPL module或non-GPL module),fsa->gplok=true表明这是个GPL module,否则就是non-GPL modulee内核判断一个模块是否GPL兼容,要使用到本章后面的“模块的信息”部分中的内容。

对于一个non-GPLmoduie而言,它不能使用内核导出的属于GPL_ONLY的那些符号,所以即使要查找的符号匹配上一个属于GPL_ONLY的符号,也不能认为查找成功。但是如果要查找的符号匹配上一个属于WILL_BE_GPL_ONLY的符号,因为这个导出的符号“将要成为GPL_ONLY”,所以即使现在还不是GPL_ONLY,查找姑且算是成功的,不过即便如此,内核对模块将来对该符号的成功使用没有保障,所以应该给出一个警告信息。对于一个GPL module而言,一切好说,可以使用内核导出的所有符号。

函数如果成功查找到符号,利用传进来的d指针将符号相关信息传给上层调用的函数。

至此,find_symbol的第一部分,即在内核导出的符号表中查找指定的符号己经结束。如果指定的符号没有出现在内核导出的符号表中,那么将进入find_symbol函数的第二部分。

下面开始介绍find_symbol的第二部分,在系统己经加载的模块导出的符号表中查找符号。内核为达成此目的,需要在加载一个内核模块时完成下面两件事。

第一,模块成功加载进系统之后,需要将表示该模块的struct module类型变量mod加入到modules中,后者是一个全局的链表变量,用来记录系统中所有己加载的模块。

list_for_each_entry_rcu(mod, &modules, list) ->list_entry_rcu

第二,模块导出的符号信息记录在mod的相关成员变量中,这个过程的详细描述参见本章前面的“模块导出的符号”部分。

each_symbol用来在系统所有己加载的模块导出的符号中查找某一指定符号,其核心代码片段如下:

list_for_each_entry_rcu(mod, &modules, list) {
    struct symsearch arr[] = {//构造一个struct symsearch类型的数组arr
      { mod->syms, mod->syms + mod->num_syms, mod->crcs,
        NOT_GPL_ONLY, false },
      { mod->gpl_syms, mod->gpl_syms + mod->num_gpl_syms,
        mod->gpl_crcs,
        GPL_ONLY, false },
      { mod->gpl_future_syms,
        mod->gpl_future_syms + mod->num_gpl_future_syms,
        mod->gpl_future_crcs,
        WILL_BE_GPL_ONLY, false },
#ifdef CONFIG_UNUSED_SYMBOLS
      { mod->unused_syms,
        mod->unused_syms + mod->num_unused_syms,
        mod->unused_crcs,
        NOT_GPL_ONLY, true },
      { mod->unused_gpl_syms,
        mod->unused_gpl_syms + mod->num_unused_gpl_syms,
        mod->unused_gpl_crcs,
        GPL_ONLY, true },
#endif
    };
    if (mod->state == MODULE_STATE_UNFORMED)
      continue;
    if (each_symbol_in_section(arr, ARRAY_SIZE(arr), mod, fn, data))
      return true;

相对于find_symbol的第一部分〈在内核导出的符号表中查找某一符号),第二部分唯一的区别在于构造的arr数组。函数在全局链表modules中遍历所有己加载的内核模块,对其中的每一模块都构造一个新的arr数组,然后在其中查找特定的符号。

目录
相关文章
|
5月前
|
小程序
小程序的分包加载具体流程
小程序的分包加载具体流程
175 0
|
7月前
|
小程序
小程序如何进行分包加载
小程序如何进行分包加载
80 0
|
10月前
|
存储 Linux C语言
模块的加载过程一
模块的加载过程一
102 0
|
10月前
|
编译器
模块的加载过程三(下)
模块的加载过程三(下)
98 0
|
10月前
|
Linux
模块的加载过程二(上)
模块的加载过程二
67 0
|
10月前
|
Linux 索引
模块的加载过程三
模块的加载过程三
58 0
|
10月前
|
Linux
模块的加载过程四
模块的加载过程四
84 0
|
Java 数据库
项目的模块以及每一个模块的作用
项目的模块以及每一个模块的作用
项目的模块以及每一个模块的作用
|
缓存 JavaScript 开发者
require 函数加载模块原理(被加载的模块会先执行一次)|学习笔记
快速学习 require 函数加载模块原理(被加载的模块会先执行一次)
396 0
require 函数加载模块原理(被加载的模块会先执行一次)|学习笔记
|
JavaScript 开发者
require 函数加载模块过程|学习笔记
快速学习 require 函数加载模块过程
66 0