do_initcalls()将按顺序从由__initcall_start开始,到__initcall_end结束的section中以函数指针的形式取出这些编译到内核的驱动模块中初始化函数起始地址,来依次完成相应的初始化。而这些初始化函数由__define_initcall(level,fn)指示编译器在编译的时候,将这些初始化函数的起始地址值按照一定的顺序放在这个section中。
由于内核某些部分的初始化需要依赖于其他某些部分的初始化的完成,因此这个顺序排列常常非常重要。先看看调用源码:
static void __init do_initcalls(void) { int level; for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) do_initcall_level(level);//依次调用不同等级的初始化函数 }
extern initcall_t __initcall_start[]; extern initcall_t __initcall0_start[]; extern initcall_t __initcall1_start[]; extern initcall_t __initcall2_start[]; extern initcall_t __initcall3_start[]; extern initcall_t __initcall4_start[]; extern initcall_t __initcall5_start[]; extern initcall_t __initcall6_start[]; extern initcall_t __initcall7_start[]; extern initcall_t __initcall_end[]; static initcall_t *initcall_levels[] __initdata = { __initcall0_start, __initcall1_start, __initcall2_start, __initcall3_start, __initcall4_start, __initcall5_start, __initcall6_start, __initcall7_start, __initcall_end, }; /* Keep these in sync with initcalls in include/linux/init.h */ static char *initcall_level_names[] __initdata = { "early", "core", "postcore", "arch", "subsys", "fs", "device", "late", }; static void __init do_initcall_level(int level) { extern const struct kernel_param __start___param[], __stop___param[]; initcall_t *fn; strcpy(static_command_line, saved_command_line); parse_args(initcall_level_names[level], static_command_line, __start___param, __stop___param - __start___param, level, level, &repair_env_string); for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++) do_one_initcall(*fn); }
调用其实很简单,下面看看linux是怎么样构造这个section的:
1、分析 __define_initcall(level,fn) 宏定义
1) 这个宏的定义位于inlclude\linux\init.h中:
#define __define_initcall(level,fn,id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" level ".init"))) = fn
其中 initcall_t 是一个函数指针类型:
typedef int (*initcall_t)(void);
而属性 __attribute__((__section__())) 则表示把对象放在一个这个由括号中的名称所指代的section中。
所以这个宏定义的的含义是:
1) 声明一个名称为__initcall_##fn##id的函数指针(其中##表示替换连接,);linux3.6比2.6多添加了一个id作为扩展。
2) 将这个函数指针初始化为fn;
3) 编译的时候需要把这个函数指针变量放置到名称为 ".initcall" level ".init" 的section中(比如level="1",代表这个section的名称是 ".initcall1.init")。
2) 举例:__define_initcall(6, pci_init,1s)
上述宏调用的含义是:1) 声明一个函数指针__initcall_pic_init_is = pci_init; 这个指针变量__initcall_pic_init_1s 需要放置到名称为 .initcall6.init的section中( 其实质就是将 这个函数pic_init的首地址放置到了这个section中)。
3) 这个宏一般并不直接使用,而是被定义成下述其他更简单的若干个衍生宏
这些衍生宏宏的定义也位于 inlclude\linux\Init.h 中:
#define early_initcall(fn) __define_initcall("early",fn,early) /* * A "pure" initcall has no dependencies on anything else, and purely * initializes variables that couldn't be statically initialized. * * This only exists for built-in code, not for modules. * Keep main.c:initcall_level_names[] in sync. */ #define pure_initcall(fn) __define_initcall("0",fn,0) #define core_initcall(fn) __define_initcall("1",fn,1) #define core_initcall_sync(fn) __define_initcall("1s",fn,1s) #define postcore_initcall(fn) __define_initcall("2",fn,2) #define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s) #define arch_initcall(fn) __define_initcall("3",fn,3) #define arch_initcall_sync(fn) __define_initcall("3s",fn,3s) #define subsys_initcall(fn) __define_initcall("4",fn,4) #define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s) #define fs_initcall(fn) __define_initcall("5",fn,5) #define fs_initcall_sync(fn) __define_initcall("5s",fn,5s) #define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs) #define device_initcall(fn) __define_initcall("6",fn,6) #define device_initcall_sync(fn) __define_initcall("6s",fn,6s) #define late_initcall(fn) __define_initcall("7",fn,7) #define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
因此通过宏 core_initcall() 来声明的函数指针,将放置到名称为.initcall1.init的section中,而通过宏 postcore_initcall() 来声明的函数指针,将放置到名称为.initcall2.init的section中,依次类推。
2、与初始化调用有关section--initcall.init被分成了7或14个子section
1) 它们依次是.initcall1.init、.initcall2.init、...、.initcall7.init,linux3.0之后增加了一个扩展s.
2) 按照先后顺序依次排列
3) linux各个版本定义的形式也可能所改变,但作用是一致的。
linux3.0 ARM平台在include\asm-generic\vmlinux.lds.h里面有
#define INIT_CALLS_LEVEL(level) \ VMLINUX_SYMBOL(__initcall##level##_start) = .; \ *(.initcall##level##.init) \ *(.initcall##level##s.init) \ #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) = .;
而在makefile 中有
LDFLAGS_vmlinux += -T arch/$(ARCH)/kernel/vmlinux.lds.s
4) 在这7或14个section总的开始位置被标识为__initcall_start,而在结尾被标识为__initcall_end。
3、 内核初始化函数do_basic_setup(): do_initcalls() 将从.initcall.init 中,也就是这几个section中依次取出所有的函数指针,并调用这些函数指针所指向的函数,来完成内核的一些相关的初始化。
4、 总结
1) __define_initcall(level,fn)的作用就是指示编译器把一些初始化函数的指针(即:函数起始地址)按照顺序放置一个名为 .initcall.init 的section中,这个section又被分成了若干个子section,它们按顺序排列。在内核初始化阶段,这些放置到这个section中的函数指针将供do_initcalls() 按顺序依次调用,来完成相应初始化。
2) 函数指针放置到的子section由宏定义的level确定,对应level较小的子section位于较前面。而位于同一个子section内的函数指针顺序不定,由编译器按照编译的顺序随机指定。
3) 因此,如果你希望某个初始化函数在内核初始化阶段就被调用,那么你 就应该使用宏__define_initcall(level,fn) 或 其几个衍生宏 把这个函数fn的对应的指针放置到按照初始化的顺序放置到相关的 section 中。如果某个初始化函数fn_B需要依赖于另外一个初始化函数fn_A的完成,那么你应该把fn_B放在比fn_A对应的level值较大的子section中, 这样,do_initcalls()将在fn_A之后调用fn_B。
如果你希望某个初始化函数在内核初始化阶段就被调用,那么你就应该使用宏__define_initcall(level,fn) 或 其7个衍生宏来把这个初始化函数fn的起始地址按照初始化的顺序放置到相关的section 中。 内核初始化时的do_initcalls()将从这个section中按顺序找到这些函数来执行。如果你希望某个初始化函数在内核初始化阶段就被调用,那么你就应该使用宏__define_initcall(level,fn) 或 其几个衍生宏来把这个初始化函数fn的起始地址按照初始化的顺序放置到相关的section 中。 内核初始化时的do_initcalls()将从这个section中按顺序找到这些函数来执行。所以在源码中只要是使用这几个宏或者是衍生宏声明的函数,只要被编译到内核中,就会在do_initcalls被调用,从而初始化设备。