函数中包含了很多初始化系统所要做的工作,我们关心的是它的最后一个函数调用,rest_init。我们步步跟进:
main.c [\init]
好吧,我们只抓最主要的,为什么呢?因为其他函数我还不明白:-)我们只关注协议栈相关的,所以有本好书来指导还是相当有好处的。
在rest_init函数里面最主要的工作就是启动了一个线程kernel_init。不过在《书》中的2.6.18版本这个线程的名字是init,调用如下:
好吧,我们来看一下kernel_init线程执行了哪些。(又贴代码了,真的是代码工啊,用代码说话^_^)
关键部分是do_basic_setup函数:
很快就到尽头了,下面一个是do_initcalls:
终于到了,这个for循环就是我们的主角。先来看看两个循环变量的定义:
这两个变量在.c和.h文件中是找不到的,因为他们定义在链接脚本中,关于链接脚本可以参考《GNU_链接脚本分析》,另外建议你同时参看我的博文《ELF文件格式学习》。
Linux创建内核的链接脚本是arch\x86\kernel目录下的vmlinux.lsd.S,而在2.6.18中,上述变量是定义在该链接脚本中的,2.6.18中的目录是arch\i386\kernel,后来的Linux版本把i386和x86_64目录合并成x86目录。而且2.6.18中没有__early_initcall_end这个变量。
vmlinux.lsd.S [arch\i386\kernel] (2.6.18)
而在2.6.36中则不同,它定义了一个链接脚本的头文件vmlinux.lds.h,在目录include\asm-generic下。这个头文件是通用的,其他的平台如arm的vmlinux.lds.S [arch\arm\kernel],也会用到里面定义的变量/宏。
vmlinux.lds.h [include\asm-generic]
在INITCALLS 宏中定义了__early_initcall_end 这个变量,而INIT_CALLS宏中又包含了INITCALLS 宏以及__initcall_start和__initcall_end变量,__initcall_start用在main.c的do_pre_smp_initcalls函数中。
而INIT_CALLS宏又被包含在宏INIT_DATA_SECTION(initsetup_align)中,所以在内核链接脚本vmlinux.lds.S中只能看到宏INIT_DATA_SECTION(initsetup_align)。
vmlinux.lds.h [include\asm-generic]
vmlinux.lds.S [arch\x86\kernel]
好了,找到这两个变量定义在vmlinux.lsd.h中,do_initcalls函数会调用在这两个变量之间定义的函数。这里有一个问题:这两个变量之间的*(.initcall*.init)中存放着什么函数调用呢??
在解决这个问题之前,我们先来总结一下上述的调用过程:
start_kernelàrest_initàkernel_thread(kernel_init)à do_basic_setupàdo_initcallsèfor (fn = __early_initcall_end; fn < __initcall_end; fn++)
下面我们来看解决上面的问题。我们从include\linux\init.h入手。先抓一些亮眼的东西。
init.h[include\linux]
这里的定义貌似在__early_initcall_end变量定义的时候看到过啊,没错!凡是通过这些宏定义的其实都是放在__early_initcall_end和__initcall_end之间的函数。我们来看看__define_initcall的具体定义。
这里出现了一个__attribute__,顺带讲一下,这个是GCC的扩展,上述的语句表示将fn函数放到名为”.initcall.level.init”的section中,如果对于section不清楚的话,可以参考我的博文《ELF文件格式》,对其有个大概了解。
好了顺带讲一下另外一个宏定义:
module_init这个宏相信在许多驱动程序中都是司空见惯的,其实它都指向device_initcall这个宏,也就是说会被放到initcall.6.init这个section中。