模块的加载过程二(上)

简介: 模块的加载过程二

先上图:

HDR视图的第二次改写

在这次改写中,HDR视图中绝大多数的section会被搬移到新的内存空间中,之后会根据这些section新的内存地址再次改写图1·2中的HDR视图,使其中section header table中各entry的sh_addr指向新的也是最终的内存地址。

load_module->layout_and_allocate->layout_sections

static void layout_sections(struct module *mod, struct load_info *info)
{
  static unsigned long const masks[][2] = {
    /* NOTE: all executable code must be the first section
     * in this array; otherwise modify the text_size
     * finder in the two loops below */
    { SHF_EXECINSTR | SHF_ALLOC, ARCH_SHF_SMALL },
    { SHF_ALLOC, SHF_WRITE | ARCH_SHF_SMALL },
    { SHF_WRITE | SHF_ALLOC, ARCH_SHF_SMALL },
    { ARCH_SHF_SMALL | SHF_ALLOC, 0 }
  };//标记了SHF_ALLOC的section定义了四种类型:
  //code、only-data、read-write data和small data。
  unsigned int m, i;
  //遍历Section header table
  for (i = 0; i < info->hdr->e_shnum; i++)
    info->sechdrs[i].sh_entsize = ~0UL;
  pr_debug("Core section allocation order:\n");
  for (m = 0; m < ARRAY_SIZE(masks); ++m) {
    for (i = 0; i < info->hdr->e_shnum; ++i) {
      Elf_Shdr *s = &info->sechdrs[i];
      const char *sname = info->secstrings + s->sh_name;
      if ((s->sh_flags & masks[m][0]) != masks[m][0]
          || (s->sh_flags & masks[m][1])
          || s->sh_entsize != ~0UL
          || strstarts(sname, ".init"))
        continue;
      s->sh_entsize = get_offset(mod, &mod->core_size, s, i);
      pr_debug("\t%s\n", sname);
    }
    switch (m) {
    case 0: /* executable */
      mod->core_size = debug_align(mod->core_size);
      mod->core_text_size = mod->core_size;
      break;
    case 1: /* RO: text and ro-data */
      mod->core_size = debug_align(mod->core_size);
      mod->core_ro_size = mod->core_size;
      break;
    case 3: /* whole core */
      mod->core_size = debug_align(mod->core_size);
      break;
    }
  }
  pr_debug("Init section allocation order:\n");
  for (m = 0; m < ARRAY_SIZE(masks); ++m) {
    for (i = 0; i < info->hdr->e_shnum; ++i) {
      Elf_Shdr *s = &info->sechdrs[i];
      const char *sname = info->secstrings + s->sh_name;
      if ((s->sh_flags & masks[m][0]) != masks[m][0]
          || (s->sh_flags & masks[m][1])
          || s->sh_entsize != ~0UL
          || !strstarts(sname, ".init"))
        continue;
      s->sh_entsize = (get_offset(mod, &mod->init_size, s, i)
           | INIT_OFFSET_MASK);
      pr_debug("\t%s\n", sname);
    }
    switch (m) {
    case 0: /* executable */
      mod->init_size = debug_align(mod->init_size);
      mod->init_text_size = mod->init_size;
      break;
    case 1: /* RO: text and ro-data */
      mod->init_size = debug_align(mod->init_size);
      mod->init_ro_size = mod->init_size;
      break;
    case 3: /* whole init */
      mod->init_size = debug_align(mod->init_size);
      break;
    }
  }
}

在为那些需要移动的section分配新的内存空间地址之前,内核需要决定出HDR视图中哪些section需要移动,如果移动的话要移动到什么位置。内核代码中layout_sections函数用来做这件事,在layout_sections函数中,内核会遍历HDR视图中的每一个section,对每一个标记有SHF_ALLOC的section,将其划分到两大类section当中:CORE和INIT。为了完成这种分类,layout_sections函数首先为标记了SHF_ALLOC的section定义了四种类型:code、only-data、read-write data和small-datao任何一个标记了SHF_ALLOC的section必定属于这四类中的一类。之后,对应每一个分类,函数都会遍历Section header table中的所有项,将section name不是以".init"开始的section划归为CORE section,并且修改HDR视图中Section header table中对应entry的sh_entsize,用以记录当前section在COREsection中的偏移量。

mod->core_size = debug_align(mod->core_size);

同时用struct module结构中的成员变量core size记录下到当前正在操作的section为止CORE section的空间大小。

mod->core_size = debug_align(mod->core_size);

对于CORE section中的code section,内核用struct module结构中的core text size来记录。

mod->core_text_size = mod->core_size

对于INIT section的分类,和CORE section的划分基本一样,不同的地方在于属于INIT section的section,其name必须以与nit"开始,内核用struct module结构中的成员变量init size来记录当前INITsection空间的大小。

mod->init_size = debug_align(mod->init_size);

对于INIT section中的code section,内核用struct module结构中的init text size来记录。在对section进行搬移之前,接下来会有个对符号表的处理,内核代码中通过调用layout_symtab函数来完成。Linux的内核源码中根据是否启用了内核配置选项CONFIG_KALLSYMS给出了layout_symtab函数的两种不同的定义。

如果没有启用CONFIG_KALLSYMS,那么layout_symtab函数就是个空函数,不做任何事情。

在启用了CONFIG_KALLSYMS选项的LINUX源码树基础上编译内核模块,会导致内核模块也会保留模块中的所有符号,这些符号都放在ELF符号表section中。由于在内核模块的ELF文件中,符号表所在的section没有SHFALLOC标志,所以上面提到的layout_secttons函数不会把符号表section划到CORE section或者是INITsecuon中,这也是为什么要通过另外一个函数layout_symtab来把符号表搬移到CORE section内存区中的原因。

内核会遍历HDR视图中的每一个section,对每一个标记有SHF_ALLOC的section,将其划分到两大类section当中:CORE和INIT。为了完成这种分类,layout_sections函数首先为标记了SHF_ALLOC的section定义了四种类型:code、only-data、read-write data和small-datao任何一个标记了SHF_ALLOC的section必定属于这四类中的一类。之后,对应每一个分类,函数都会遍历Section header table中的所有项,将section name不是以".init"开始的section划归为CORE section,并且修改HDR视图中Section header table中对应entry的sh_entsize,用以记录当前section在COREsection中的偏移量。

load_module->layout_and_allocate->move_module

static int move_module(struct module *mod, struct load_info *info)
{
  int i;
  void *ptr;
  /* Do the allocs. */
  ptr = module_alloc_update_bounds(mod->core_size);
  /*
   * The pointer to this block is stored in the module structure
   * which is inside the block. Just mark it as not being a
   * leak.
   */
  kmemleak_not_leak(ptr);
  if (!ptr)
    return -ENOMEM;
  memset(ptr, 0, mod->core_size);
  mod->module_core = ptr;
  if (mod->init_size) {
    ptr = module_alloc_update_bounds(mod->init_size);
    /*
     * The pointer to this block is stored in the module structure
     * which is inside the block. This block doesn't need to be
     * scanned as it contains data and code that will be freed
     * after the module is initialized.
     */
    kmemleak_ignore(ptr);
    if (!ptr) {
      module_memfree(mod->module_core);
      return -ENOMEM;
    }
    memset(ptr, 0, mod->init_size);
    mod->module_init = ptr;
  } else
    mod->module_init = NULL;
  /* Transfer each section which specifies SHF_ALLOC */
  pr_debug("final section addresses:\n");
  for (i = 0; i < info->hdr->e_shnum; i++) {
    void *dest;
    Elf_Shdr *shdr = &info->sechdrs[i];
    if (!(shdr->sh_flags & SHF_ALLOC))
      continue;
    if (shdr->sh_entsize & INIT_OFFSET_MASK)
      dest = mod->module_init
        + (shdr->sh_entsize & ~INIT_OFFSET_MASK);
    else
      dest = mod->module_core + shdr->sh_entsize;
    if (shdr->sh_type != SHT_NOBITS)
      memcpy(dest, (void *)shdr->sh_addr, shdr->sh_size);
    /* Update sh_addr to point to copy in image. */
    shdr->sh_addr = (unsigned long)dest;
    pr_debug("\t0x%lx %s\n",
       (long)shdr->sh_addr, info->secstrings + shdr->sh_name);
  }
  return 0;
}

这里之所以要对HDR视图中的某些section做这样的搬移,是因为在模块加载过程结束时,系统会释放掉IIDR视图所在的内存区域,不仅如此,在模块初始化工作完成后,INITsection所在的内存区域也会被释放掉。由此可见,当一个模块被成功加载进系统,初始化工作完成之后,最终留下的仅仅是CORE section中的内容,因此CORE section中的数据应是模块在系统中整个存活期会使用到的数据。

如此处理之后,我们在图1·2的基础上得到了图1·4:

模块导出的符号

如果一个内核模块向外界导出了自己的符号,那么将由模块的编译工具链负责生成这些导出符号section,而且这些section都带有SHFALLOC标志,所以在模块加载过程中会被搬移到CORE section区域中。如果模块没有向外界导出任何符号,那么在模块的ELF文件中,将不会产生这些section。

显然,内核需要对模块导出的符号进行管理,以便在处理其他模块中那些“未解决的引用”符号时能够找到这些符号。内核对模块导出的符号的管理使用到了structmodule结构中如下的成员变量:

struct module {
  。。。。
#ifdef CONFIG_UNUSED_SYMBOLS
  /* unused exported symbols. */
  const struct kernel_symbol *unused_syms;
  const unsigned long *unused_crcs;
  unsigned int num_unused_syms;
  /* GPL-only, unused exported symbols. */
  unsigned int num_unused_gpl_syms;
  const struct kernel_symbol *unused_gpl_syms;
  const unsigned long *unused_gpl_crcs;
#endif
#ifdef CONFIG_MODULE_SIG
  /* Signature was verified. */
  bool sig_ok;
#endif
  /* symbols that will be GPL-only in the near future. */
  const struct kernel_symbol *gpl_future_syms;
  const unsigned long *gpl_future_crcs;
  unsigned int num_gpl_future_syms;
  。。。。
}

在把HDR视图中的section搬移到最终的CORE section和INIT section之后,内核通过对HDR视图中Section header table的查找,获得“__ksymtab",“__ksymtab_gpl”和”__ksymtab_gpl_future”section在CORE section中的地址,将其记录在mod->syms、mod->gpl_syms和mod->gpl_future_syms中。

  • find_symbol

在模块加载过程中,find_symbol是个非常重要的函数,顾名思义,它用来查找一个符号。该函数的原型如下:

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;
  if (each_symbol_section(find_symbol_in_section, &fsa)) {
    if (owner)
      *owner = fsa.owner;
    if (crc)
      *crc = fsa.crc;
    return fsa.sym;
  }
  pr_debug("Failed to find symbol %s\n", name);
  return NULL;
}


目录
相关文章
|
1月前
|
缓存 监控 前端开发
如何确保动态导入的模块被正确加载?
通过以上这些方法的综合运用,可以有效地确保动态导入的模块被正确加载,提高应用的稳定性、性能和用户体验。在实际开发过程中,要根据项目的具体情况和需求,灵活运用这些方法,并不断进行测试和优化。
34 4
|
2月前
|
监控 开发者
确保动态导入模块按正确顺序加载
【10月更文挑战第12天】 在复杂应用开发中,确保动态导入模块按正确顺序加载至关重要,直接影响应用性能、功能和稳定性。本文深入探讨了动态模块加载顺序的影响因素、解决方案及实践案例,提供了详细的策略和方法,帮助开发者有效管理模块加载顺序,提升应用质量。
|
4月前
|
数据库连接 数据库
实现加载驱动、得到数据库对象、关闭资源的代码复用,将代码提取到相应的工具包里边。优化程序
该博客文章展示了如何通过创建工具类`Connectiontools`实现数据库连接、语句执行以及资源关闭的代码复用,以优化程序并提高数据库操作的效率和安全性。
|
7月前
|
小程序
小程序的分包加载具体流程
小程序的分包加载具体流程
246 0
|
编译器
模块的加载过程三(下)
模块的加载过程三(下)
183 0
|
程序员 Linux
模块的加载过程二(下)
模块的加载过程二(下)
154 0
|
Linux 索引
模块的加载过程三
模块的加载过程三
98 0
|
Linux
模块的加载过程四
模块的加载过程四
147 0
|
存储 Linux C语言
模块的加载过程一
模块的加载过程一
169 0
|
JavaScript 开发者
Vite 在运行过程中是如何发现新增依赖的?
Vite 在运行过程中是如何发现新增依赖的?
236 0

热门文章

最新文章