Linux内核模块的加载过程

前段时间为了解决内核模块无法卸载的问题,对模块的加载过程详细地学习了一番。加载模块时常用的命令是insmod和modprobe,这两个命令主要是通过系统调用sys_init_module()来完成主要的工作,用户层做的更多的是对参数的处理,以及将插入的模块加入到内存中。系统调用sys_init_module()将大部分工作委托给load_module()函数来完成,load_module()中的操作,大部分是围绕着ELF文件的格式来完成的,所以如果对ELF文件了解的话,看load_module()的过程很容易。 下面将我对load_module()的一些理解贴出来和大家分享一下,注释比较详细,就不多说了:

/* Allocate and load the module: note that size of section 0 is always
  zero, and we rely on this for optional sections. */
/*
 * load_module()负责最艰苦的模块加载全过程。sys_init_module()调用load_module(),
 * 后者将在内核空间利用vmalloc分配一块大小同样为len的地址空间。然后通过
 * copy_from_user函数的调用将用户空间的文件数据复制到内核空间中,从而在内核空间
 * 构造出内核模块的一个ELF静态的内存视图。接下来的操作都将以此视图为基础,为使
 * 叙述简单起见,我们称该视图为HDR视图。HDR视图所占用的内存空间在load_module结束时
 * 通过vfree予以释放。
 */
static noinline struct module *load_module(void __user *umod,
      unsigned long len,
      const char __user *uargs)
{
 /*
  * ELF文件头地址。
  */
 Elf_Ehdr *hdr;
 /*
  * 段首部表地址
  */
 Elf_Shdr *sechdrs;
 char *secstrings, *args, *modmagic, *strtab = NULL;
 char *staging;
 unsigned int i;
 unsigned int symindex = 0;
 unsigned int strindex = 0;
 unsigned int modindex, versindex, infoindex, pcpuindex;
 struct module *mod;
 long err = 0;
 void *percpu = NULL, *ptr = NULL; /* Stops spurious gcc warning */
 unsigned long symoffs, stroffs, *strmap;

mm_segment_t old_fs;

DEBUGP("load_module: umod=%p, len=%lu, uargs=%p\n",
        umod, len, uargs);
 /*
  * 如果len小于ELF文件首部长度,则返回ENOEXEC错误。
  */
 if (len < sizeof(*hdr))
  return ERR_PTR(-ENOEXEC);

/* Suck in entire file: we'll want most of it. */
 /* vmalloc barfs on "unusual" numbers.  Check here */
 /*
  * 64 * 1024 * 1024应该是模块文件的最大大小。
  */
 if (len > 64 * 1024 * 1024 || (hdr = vmalloc(len)) == NULL)
  return ERR_PTR(-ENOMEM);

/*
  * 将模块文件从用户空间拷贝到分配的hdr中。
  */
 if (copy_from_user(hdr, umod, len) != 0) {
  err = -EFAULT;
  goto free_hdr;
 }

/* Sanity checks against insmoding binaries or wrong arch,
          weird elf version */
 /*
  * 检查文件标识是否是ELFMAG,检查模块目标文件是否是可重定向文件,
  * 检查目标文件的体系结构类型,检查ELF首部中段首部表中表项的大小,
  * 如果其中一项检查失败,则返回ENOEXEC。
  */
 if (memcmp(hdr->e_ident, ELFMAG, SELFMAG) != 0
    || hdr->e_type != ET_REL
    || !elf_check_arch(hdr)
    || hdr->e_shentsize != sizeof(*sechdrs)) {
  err = -ENOEXEC;
  goto free_hdr;
 }

/*
  * hdr->e_shnum * sizeof(Elf_Shdr)计算的是ELF文件中段首部表的大小,
  * 加上偏移的值如果大于len,则说明模块目标文件被截断了,跳转到
  * truncated标签处处理
  */
 if (len < hdr->e_shoff + hdr->e_shnum * sizeof(Elf_Shdr))
  goto truncated;

/* Convenience variables */
 /*
  * 计算段首部表的地址.
  */
 sechdrs = (void *)hdr + hdr->e_shoff;
 /*
  * 计算段名称字符串表的地址,其中hdr->e_shstrndx是段名称字符串表在段首部表中
  * 的索引,sh_offset是当前段相对于文件头的偏移。
  */
 secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
 /*
  * 将第一个段在执行时的虚拟地址设为0,不使用段首部表中的第一个表项。
  */
 sechdrs[0].sh_addr = 0;

/*
  * 开始遍历段首部表, hdr->e_shnum是段首部表表项的数量
  */
 for (i = 1; i < hdr->e_shnum; i++) {
  /*
  * 如果索引为i的段需要在文件中占据空间,但是文件长度小于
  * 段的偏移加上段大小(也就是说文件长度不够),则跳转到
  * truncated标签处处理
  */
  if (sechdrs[i].sh_type != SHT_NOBITS
      && len < sechdrs[i].sh_offset + sechdrs[i].sh_size)
   goto truncated;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/bf168a7a8c38771e643a5f406db5752c.html