Kernel之init相关
Kernel之init相关
[TOC]
主要记录一些整体的概念、框架和简单介绍,不涉及具体的原理和实现细节
背景
在看驱动代码的时候经常会看到module_init
、subsys_initcall
等xxx_init相关的代码,以前只知道是该驱动最开始入口函数的地方,并没有深究到底层去,最近刚好又碰到,就想看看底层是什么样的,于是就有了此文。
xxx_init相关初始化函数
这里主要列举了module_init
和subsys_initcall
相关实现,其他类似
subsys_initcall
:#ifndef MODULE /*...*/ /* 不是模块时 */ #define subsys_initcall(fn) __define_initcall(fn, 4) /*...*/ #endif /* 为模块时 */ #define subsys_initcall(fn) module_init(fn)
module_init
:#ifndef MODULE /*...*/ /* 不是模块时 */ #define module_init(x) __initcall(x); /*...*/ #else /* MODULE */ /*...*/ /* 为模块时 */ /* Each module must use one module_init(). */ #define module_init(initfn) \ static inline initcall_t __maybe_unused __inittest(void) \ { return initfn; } \ int init_module(void) __copy(initfn) __attribute__((alias(#initfn))); /*...*/ #endif
从上面可以看出:#define __initcall(fn) device_initcall(fn) #define device_initcall(fn) __define_initcall(fn, 6)
在驱动为模块时,subsys_initcall
和module_init
是一样的,都是module_init
在驱动不是模块时,编译进内核,最后都是到__define_initcall(fn, id)
,
xxx_init
相关初始化函数在这两种形态下的实现是不一样的,主要跟对应模块的运行方式有关:
- 编译成可动态加载的模块,并通过insmod来动态加载,再进行初始化。
- 静态编译链接进内核的模块,在系统启动过程中进行初始化。
有些模块是必须要编译到内核,不能动态加载的,比如启动相关的模块,vfs等
后面,我们就分这2块分别讨论:
非模块
在上面的讨论中,驱动不是模块时,最终init都会到__define_initcall(fn, id)
,只不过id不一样,module_init
对应的id为6,subsys_initcall
为4
这个id会有什么影响呢?下面继续分析__define_initcall
__define_initcall
:
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
#define ___define_initcall(fn, id, __sec) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(#__sec ".init"))) = fn;
/*
* Used for initialization calls..
*/
typedef int (*initcall_t)(void);
typedef void (*exitcall_t)(void);
__define_initcall
主要是定义初始化函数,并使用__attribute__
和__section__
将对应的初始化函数fn
放在相应的.initcall##id
段,如这里对应的就是.initcall4
和.initcall6
那.initcall##id
段是些啥呢?其实就是Kernel初始化init执行的顺序,看下图就很明了了:
各个子区段之间的顺序是确定的,即先调用.initcall1.init中的函数指针,再调用.initcall2.init中的函数指针,等等,这样就保证了初始化一定的调用顺序
而在每个子区段中的函数指针的顺序是和链接顺序相关的,是不确定的。
Kernel启动相关流程:
模块
编译成module的模块都会自动产生一个*.mod.c的文件,里面有很重要的一段__section
,用.gnu.linkonce.this_module
标记:
...
__visible struct module __this_module
__section(.gnu.linkonce.this_module) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};
...
定义了一个类型为module的全局变量__this_module,其成员init即为init_module
在insmod
或modprobe
模块时,最终会调用系统调用sys_init_module
,
对应的内核函数(kernel/module.c):
SYSCALL_DEFINE3(init_module, void __user *, umod,
unsigned long, len, const char __user *, uargs)
load_module
函数里面会加载模块的ko文件,并解释各个section,重定位,
其中,setup_load_info
函数中会查找.gnu.linkonce.this_module
段
info->index.mod = find_sec(info, ".gnu.linkonce.this_module");
if (!info->index.mod) {
pr_warn("%s: No module found in object\n",
info->name ?: "(missing .modinfo name field)");
return -ENOEXEC;
}
找到对应的module数据:
/* Module has been copied to its final place now: return it. */
mod = (void *)info->sechdrs[info->index.mod].sh_addr;
后面会调用do_init_module
函数去进行初始化:
/* Start the module */
if (mod->init != NULL)
ret = do_one_initcall(mod->init);
延伸:Kernel中__init等宏定义
相关宏定义:include/linux/init.h
include/linux/compiler_attributes.h
#define __init __section(.init.text) __cold __latent_entropy __noinitretpoline
#define __initdata __section(.init.data)
#define __initconst __section(.init.rodata)
#define __aligned(x) __attribute__((aligned(x))) ///指定为x字节对齐. x是一个2的幂次方
#define __section(S) __attribute__((__section__(#S))) ///将放在#S段
#define __used __attribute__((__used__)) ///避免被链接器因为未用过而被优化掉
GNU C
的一大特色就是__attribute__
机制。__attribute__
可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute )
__section
主要告诉链接器应该把这个函数或者数据放置在哪个位置,一般是指放置到内核镜像的哪个位置上。
内核相当于一个非常大的可执行程序,里面的代码、数据等都是分段存放,通常编译器将函数放在.text
段,变量放在.data
或 .bss
段。具体段的存放规则是由vmlinux.lds
文件定义,vmlinux.lds
文件相关在后面章节有具体介绍,在代码中通常使用__section
来声明属于哪个段,如前面列举的那些宏定义
内核把段分的非常细致,是因为它会在运行过程中去定位相应的数据和代码,这样将更加方便处理。就像__init 修饰的所有代码都放在.init.text段,它只在启动阶段会被内核调用到,当初始化结束后就会释放这部分内存,以便充分利用内存,这个就是属于内存管理的部分了。
延伸:链接脚本
Kernel的链接脚本("ld script"
): vmlinux.lds.S
和vmlinux.lds
vmlinux.lds
文件是由原始文件的汇编文件vmlinux.lds.S
编译得到,所以未编译的内核源码里一般只有vmlinux.lds.S
文件
同一架构下vmlinux.lds文件一般会有2个:
./arch/x86/boot/compressed/vmlinux.lds
: -用于生成压缩的内核image./arch/x86/kernel/vmlinux.lds
: -用于生成未压缩的内核image
一般都在架构的对应的目录下,即arch/xxx/kernel/
和arch/xxx/boot/compressed/
vmlinux.lds
文件内容举例:
```
反汇编对比:
```shell
objdump --headers vmlinux