前段时间为了解决内核模块无法卸载的问题,对模块的加载过程详细地学习了一番。加载模块时常用的命令是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;