Linux TCP/IP协议栈学习笔记

2014-11-24 10:22:02 · 作者: · 浏览: 1

函数中包含了很多初始化系统所要做的工作,我们关心的是它的最后一个函数调用,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版本把i386x86_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下。这个头文件是通用的,其他的平台如armvmlinux.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.cdo_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中。