日期:2014-05-16  浏览次数:20933 次

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;

		/* 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"