ipv4内核初始化相关

| 2009年11月3日

所在文件: net/ipv4/af_inet.c 初始化函数定义:

static int __init inet_init(void)

初始化函数调用:

fs_initcall(inet_init); //#define fs_initcall(fn)                 __define_initcall("5",fn,5)

这里的fs_initcall和module_init这样的函数是一样的功能,就是给系统内核添加一个功能函数。

这个宏的定义位于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的函数指针(其中##表示替换连接,);
  2. 将这个函数指针初始化为fn;
  3. 编译的时候需要把这个函数指针变量放置到名称为 “.initcall” level “.init"的section中(比如level=“1”,代表这个section的名称是 “.initcall1.init”)。

这些衍生宏宏的定义也位于 inlclude\linux\Init.h 中:

#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中,依次类推。 在:include/asm-generic/vmlinux.lds.h:

#define INITCALLS                                                       \
*(.initcallearly.init)                                          \
VMLINUX_SYMBOL(__early_initcall_end) = .;    \ //注意这里的__early_initcall_end标志
*(.initcall0.init)                                              \
*(.initcall0s.init)                                             \
*(.initcall1.init)                                              \
*(.initcall1s.init)                                             \
*(.initcall2.init)                                              \
*(.initcall2s.init)                                             \
*(.initcall3.init)                                              \
*(.initcall3s.init)                                             \
*(.initcall4.init)                                              \
*(.initcall4s.init)                                             \
*(.initcall5.init)                                              \
*(.initcall5s.init)                                             \
*(.initcallrootfs.init)                                         \
*(.initcall6.init)                                              \
*(.initcall6s.init)                                             \
*(.initcall7.init)                                              \
*(.initcall7s.init)
#define INIT_CALLS                                                      \
VMLINUX_SYMBOL(__initcall_start) = .;                   \
INITCALLS                                               \
VMLINUX_SYMBOL(__initcall_end) = .;    //还有这里的__initcall_end

最终跟踪之后这个初始化的段会在arch/x86/kernel/vmlinux.lds.S这样的体系结构中内核二进制文件结构组织的配置文件中。 而在内核Makefile文件中有这样的编译语句:

vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
。。。
vmlinux-lds  := arch/$(SRCARCH)/kernel/vmlinux.lds
。。。

而在init/main.c 中:

static void __init do_initcalls(void)
{
initcall_t *call;

for (call = __early_initcall_end; call < __initcall_end; call++)
do_one_initcall(*call);

/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}

该函数的调用关系如下:

start_kernel-->rest_init->kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
|
->kernel_init(void * unused) ->do_initcalls(void)

也就是说对于所有的内核模块或是其它的以类似该形式加入到内核中的程序,都最终在内核所在的二进制文件中是有一个固定的段来存放的,而且内核在初始化的过程中也是找到这些段的地址让后做相应的加载和执行。

看完本文有收获?请分享给更多人

关注「黑光技术」,关注大数据+微服务