initcall调用顺序

时间:2022-11-18 06:50:03

在解释initcall调用顺序, 先要理一下编译链接的知识.

  每个.o文件都有自己的代码段, 数据段(存放初始化的全局变量), bss段(即未初始化的数据段) 在ld链接器将各.o文件的代码段和数据段组织在一起. 一般把各段都放在一起, 如代码段都放在一起:

  .text :  { *(.text)};
  这样各.o文件的代码段都会放在一起.放的顺序是按ld后面接着的文件顺序.如ld a.o b.o

作为链接的结果要有一个入口,由entry标示.entry标示的入口一般在代码段里, 如
ENTRY(_start)
在代码段里
.text : {
_start = .;
}

对于链接器,输入文件是各.o文件,输出文件是可执行文件,输入文件按段(section)来组织.section有几个重要组成部分,一是. 这个把当前地址重新定位,即可执行文件在内存中运行的位置,一般vmlinux.lds.S把.定义为0x300000000+0x800,即3G偏下的0x800位置,对于1G物理内存是这样的,在6795平台,因为是2G内存,初设置为其他值. 

链接脚本也是一种语言,需要稍微了解一下.整个脚本的最终目的是对最终可执行文件在内存各代码段等的合理布局.也有一些说明,如对这个文件指明是在arm体系上运行, 就用OUTPUT_ARCH(arm).

/DISCARD/ 表示符合条件的输入段,都不会输出到输出的可执行文件中

不想当架构师的不是好程序员
关于链接脚本的详细语法,参见: http://www.cnblogs.com/china_blue/archive/2010/04/07/1705976.html
----------
下面来说说initcall调试顺序. 
链接脚本文件也像c语言一样,可以包含.h文件.
在6795中,vmlinux.lds.s inicall部分通过引用.h文件中的INIT_CALLS来完成init.data段的定义. 如下
kernel-3.0/arch/arm/kernel/vmlinux.lds.s
.init.data : {
#ifndef CONFIG_XIP_KERNEL
INIT_DATA
#endif
INIT_SETUP(16)
INIT_CALLS
CON_INITCALL
SECURITY_INITCALL
INIT_RAM_FS
}
INIT_CALLS在kernel-3.0/include/asm-generic/vmlinux.lds.h中定义:
#define INIT_CALLS \
VMLINUX_SYMBOL(__initcall_start) = .; \
*(.initcallearly.init) \
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) \
VMLINUX_SYMBOL(__initcall_end) = .;

#define INIT_CALLS_LEVEL(level) \
VMLINUX_SYMBOL(__initcall##level##_start) = .; \
*(.initcall##level##.init) \
*(.initcall##level##s.init) \

#define VMLINUX_SYMBOL(x) x

##一般只用在宏定义中,将两个字串与替换者连在一起,组成一个新的字符串. 
INIT_CALLS最终被替换为: 

__initcall_start = .;
*(.initcallearly.init)
__initcall0_start = .;
*(.initcall0.init)
*(.initcall0s.init)

__initcall1_start = .;
*(.initcall1.init)
*(.initcall1s.init)

__initcall2_start = .;
*(.initcall2.init)
*(.initcall2s.init)

__initcall3_start = .;
*(.initcall3.init)
*(.initcall3s.init)
......

__initcallx_start 地址会被依次定义,这个地址在do_initcalls()时依次遍历.编号越小,越在内存布局中靠前的位置,越被先遍历到.
内核的各.o文件init函数会被定义为initcallx.init这样的函数. 这个编号决定了这个init被调用的时间.可以按时间先后,依次列为第0时间,第1时间,第2时间...
内核中各init函数如何被定义为initcallx.init函数.以常见的module_init为例.

#define module_init(x) __initcall(x);

#define __initcall(fn) device_initcall(fn)

#define device_initcall(fn) __define_initcall(fn, 6)

#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn

以ltr559.c为例,
这样module_init(ltr559_init)最终定义为:
static initcall_t __initcall_ltr559_init6 __used
__attribute__((__section__(".initcall6.init"))) = ltr559_init

__attribute__((__section__(".initcall6.init") 告诉编译器把ltr559_init放在名为.initcall6.init代码段中. 
即所有传入module_init所有初始化函数都会放在.initcall6.init代码段中. 
这个代码段根据vmlinux.lds.S指定在最终可执行文件的内存布局的第6区域.__initcall6_start是这个区域的起始位置.这个区域的函数由以下函数依次遍历执行:
static void __init do_pre_smp_initcalls(void)
{
initcall_t *fn;

for (fn = __initcall_start; fn < __initcall0_start; fn++)
do_one_initcall(*fn);
}
所有在.initcall6.init区域的函数在第6时间执行. 

core_init被放在第1时间, arch_initcall被放在第3时间,  fs_initcall被放在第5时间. 这些在kernel-3.0/include/linux/init.h中定义, 如下:

#define pure_initcall(fn) __define_initcall(fn, 0)

#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)

module_initcall即device_initcall在第6时间, 比较靠后的了.