粗略的看了下,kernel大致的初始化流程为:
setup.c kernel/\arch\parisc\kernel start_parisc // init arm
main .c kernel/init/ start_kernel // init
main .c kernel/init/ rest_init
main .c kernel/init/ kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
wait untill kernel_thread is ready
main .c kernel_init
main .c do_basic_setup
main .c do_initcalls
main .c do_one_initcall
在do_initcalls函数中,会有这样一个调用:
for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn);
那么 __early_initcall_end和 __initcall_end是怎么来的呢?还得从 module_init(x)这个宏说起。
每个模块最后都会写上这个宏,来指明自己的初始化函数,是怎么样生效的呢?
打开 init.h ,看到如下定义:
#define __init __section(.init.text) __cold notrace // __init开头的函数均会被放置到init.txt,并且只会被调用一次
#define device_initcall(fn) __define_initcall("6",fn,6) // device的level为6
#define __define_initcall(level,fn) static initcall_t __initcall_##fn __attribute_used__ __attribute__((__section__(".initcall" level ".init"))) = fn
#define module_init(x) __initcall(x);
#define __initcall(fn) device_initcall(fn)
由上面的宏定义关系不难得出module_init的真正表达式:
module_init(x) = static initcall_t __initcall_x6 __attribute_used__ __attribute__((__section__(".initcall" 6".init"))) = x
即定义了initcall_t类型的变量 _initcall_x 保存了X的地址,并 将其存放在 .initcall6.init,运行时由连接器vmlinux.lds 装入指定内存。
或许有些读者想到了,__early_initcall_end和 __initcall_end 存放的就是指定的内存地址,实现方法见vmlinux.lds.h:
#define INIT_CALLS \
VMLINUX_SYMBOL(__initcall_start) = .; \
INITCALLS \
VMLINUX_SYMBOL(__initcall_end) = .;
其含义时,是让__initcall_start指向代码节.initcall.init的节首,而__initcall_end指向.initcall.init的节尾.
而INITCALLS的首地址在上面被,__early_initcall_end保存,故在上述do_initcalls的循环中,vmlinux将可执行的init文件都装入指定的内存地址,按照初始化的优先级,依次调用各自模块的init函数,从而初始化设备。