通过 insmod
命令加载 ko 内存模块,加载该内存模块的时候,该内存模块会自动执行module_init()
函数,进行初始化操作。
#ifndef MODULE
// 省略
#define module_init(x) __initcall(x);
// 省略
#else
#define module_init(initfn) \
int init_module(void) __attribute__((alias(#initfn)));
// 省略
#endif
insmod
会通过文件系统将 .ko 模块读到用户空间的一块内存中,然后执行系统调用sys_init_module()
解析模组,代码如下:
1 SYSCALL_DEFINE3(init_module, void __user *, umod,
2 unsigned long, len, const char __user *, uargs)
3 {
4 int err;
5 struct load_info info = { };
6
7 err = may_init_module();
8 if (err)
9 return err;
10
11 pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
12 umod, len, uargs);
13
14 err = copy_module_from_user(umod, len, &info);
15 if (err)
16 return err;
17
18 return load_module(&info, uargs, 0);
19 }
• 第 14 行:通过 vmalloc 在 vmalloc 区分配内存空间,将内核模块 copy 到此空间,
info->hdr
直接指向此空间首地址,也就是 ko 的 elf header 。• 第 18 行:然后通过
load_module()
进行模块加载的核心处理,在这里完成了模块的搬移,重定向等艰苦的过程。
下面是 load_module()
的详细过程,代码已经简化,主要包含 setup_load_info()
和 layout_and_allocate()
。
1 /* 分配并加载模块 */
2 static int load_module(struct load_info *info, const char __user *uargs,
3 int flags)
4 {
5 struct module *mod;
6 long err = 0;
7 char *after_dashes;
8 ...
9 err = setup_load_info(info, flags);
10 ...
11 mod = layout_and_allocate(info, flags);
12 ...
13 }
• 第 9 行:
setup_load_info()
加载struct load_info
和struct module
,rewrite_section_headers
,将每个 section 的sh_addr
修改为当前镜像所在的内存地址,section 名称字符串表地址的获取方式是从 ELF 头中的e_shstrndx
获取到节区头部字符串表的标号,找到对应 section 在 ELF 文件中的偏移,再加上 ELF 文件起始地址就得到了字符串表在内存中的地址。• 第 11 行:在
layout_and_allocate()
中,layout_sections()
负责将 section 归类为 core 和 init 这两大类, 为 ko 的第二次搬移做准备。move_module()
把 ko 搬移到最终的运行地址。内核模块加载代码搬运过程到此就结束了。
总结:首先 insmod
会通过文件系统将 .ko 模块读到用户空间的一块内存中,然后执行系统调用sys_init_module()
解析模组, 这时,内核在 vmalloc 区分配与 ko 文件大小相同的内存来暂存ko 文件,暂存好之后解析 ko 文件,将文件中的各个 section 分配到 init 段和 core 段,在 modules 区为 init 段和 core 段分配内存,并把对应的 section 复制到 modules 区最终的运行地址,经过 relocate函数地址等操作后,就可以执行 ko 的 init 操作了,这样一个 ko 的加载流程就结束了。同时,init段会被释放掉,仅留下 core 段来运行。
参考自野火驱动开发教学文档