做底层驱动的人都知道,在driver文件中会经常看见“__init“修饰的代码,那么__init标记有什么意义?
下面我们就来看看。
在GCC拓展的特殊属性中section时会提及这个__init,他所修饰的所有代码都会放到.init.text节中,当初始化结束后就可以释放这部分内存,这样就减少了内存的占用空间。
那么另外一个问题就来了,什么时候调用到该函数?
要回答这个问题首先要先讲解这个宏:subsys_initcall,在文件kernel/include/linux/init.h中。定义方式如下:
#define subsys_initcall(fn) __define_initcall("4",fn,4)
又带来了一个新的宏__define_initcall,定义方式如下:
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
那么 __define_initcall是用来修饰将指定的函数指针fn存放到".initcall.init"节中。因此可以断定subsys_initcall宏将fn存放到“.initcall.init”节中的“..initcall4.init”里。
我们需要理解一下.initcall.init和.init.text和".initcall4.init"之类的符号的含义,这就需要我们了解和内核可执行文件的相关概念。
什么是内核可执行文件?
可执行文件映像中包含了进程执行的代码和数据,同时也包含了操作系统用来将映像正确装入内存并执行的信息。具体信息大家可以上网搜索,这里就一笔带过。
这些信息包含了如下文本段、数据段、init数据段、bass段等。这些数据都是由一个称为“链接器脚本”的文件链接并装入的,这个文件的功能时间这些输
入信息的各段装入到指定的地址处。vmlinux.lds就是存在"arch/xxx/"目录中哦你的内核连接其脚本,他负责链接内核的各个段并将他们装
入到内存中特定偏移量处。
下面让我们看一下这个链接器脚本文件,路径是"arch/arm/kernel",名称为vmlinux.lds.
找到关键程序:INIT_SETUP(16)--->init_main.c的__setup_start指向的.init.setup节的开始
INIT_CALLS---->init_main.c的__initcall_start指向的.initcallearly.init节的开始
CON_INITCALL---->./drivers/char/tty_io.c的__con_initcall_start指向的.con_initcall.init节的开始。
其他的宏就不讲解了。
其中INIT_CALL是主要的,分为了九段:
INIT_CALLS_LEVEL(0) \
INIT_CALLS_LEVEL(1) \
INIT_CALLS_LEVEL(2) \
INIT_CALLS_LEVEL(3) \
INIT_CALLS_LEVEL(4) \
INIT_CALLS_LEVEL(5) \
INIT_CALLS_LEVEL(rootfs) \
INIT_CALLS_LEVEL(6) \
INIT_CALLS_LEVEL(7)
而INIT_CALL_LEVEL宏定义如下:
#define INIT_CALLS_LEVEL(level) \
VMLINUX_SYMBOL(__initcall##level##_start) = .; \
*(.initcall##level##.init) \
*(.initcall##level##s.init)
实
际就是.initcall0.init~.initcall7.init。那么subsys_initcall将值定的话函数指针放在
了".initcall4.init"子段中。例如:device_initcall将函数指针存放到了".initcall6.init"
中;core_initcall将函数指针存放到了".initcall1.init"子段中。
各个子段的顺序是确定的,是先调用".initcall4.init"中的函数指针再去执行“.initcall5.init”中的函数指针。
那么__init修饰的初始化函数在内核初始化过程中调用的顺序和.initcall.init里面的函数指针的顺序有关,因此不同的初始化函数是被放在不同的子段中,因此就决定了他们的调用顺序。