日期:2014-05-16 浏览次数:20933 次
前段时间为了解决内核模块无法卸载的问题,对模块的加载过程详细地学习了一番。加载模块时常用的命令是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; /* Mark all sections sh_addr with their address in the temporary image. */ /* * 将段在执行时的虚拟地址设为他们在临时内存映像中的地址. */ sechdrs[i].sh_addr = (size_t)hdr + sechdrs[i].sh_offset; /* Internal symbols and strings. */ /* * 如果索引为i的段是符号表,则做相应的处理.目前目标文件只能有一个符号表, * 这个限制以后可能会有变化,所以下面的语句只会执行一次。 */ if (sechdrs[i].sh_type == SHT_SYMTAB) { /* * 用来保存符号表在段首部表中的索引 */ symindex = i; /* * strindex存储的是与当前段段相关的字符串表段的索引。 */ strindex = sechdrs[i].sh_link; /* * strtab存储的是与当前段相关的字符串表段的地址。 */ strtab = (char *)hdr + sechdrs[strindex].sh_offset; } #ifndef CONFIG_MODULE_UNLOAD /* Don't load .exit sections */ /* * 如果当前段是".exit"段(前缀是".exit"),则在段的标志中移除SHF_ALLOC * 标志,意思是当前段在执行过程中不需要占用内存。 */ if (strstarts(secstrings+sechdrs[i].sh_name, ".exit"