说明:以下内容基于linux2.4.0
一、initcall机制原理 在linux初始化的过程中,内核采用了一种initcall的机制,它利用gcc的扩展功能以及ld的连接控制脚本实现了在内核初始化的过程中通过简单的循环就实现了相关驱动的初始化。核心代码的/init/main.c里面有do_initcalls函数如下: static void __init do_initcalls(void) { initcall_t *call; call = &__initcall_start; do { (*call)(); call++; } while (call < &__initcall_end); flush_scheduled_tasks(); } 其中__initcall_start和__initcall_end在源码中并无定义,只是在include/linux/init.h中申明为外部变量: extern initcall_t __initcall_start, __initcall_end; i386平台下,连接控制脚本为vmlinux.lds, 这2各变量的定义是在/arch/i386/vmlinux.lds中,代码如下: __initcall_start = .; .initcall.init : { *(.initcall.init) } __initcall_end = .; 其含义是指示连接程序让__initcall_start指向代码节.initcall.init的节首,而__initcall_end指向.initcall.init的节尾。 则在内核中,只要把需要初始化调用的函数的指针放在__initcall_start和__initcall_end之间的节内,函数就会在内核初始化时被调用。 类似的内核引用的外部变量还有:… 二、实践: 实际过程分如下几步: 1、编写主程序doinitcall.c:代码如下并有详细注释: #include <stdio.h> typedef int (*initcall_t)(void); extern initcall_t __initcall_start, __initcall_end; #define __initcall(fn) / static initcall_t __initcall_##fn __init_call = fn #define __init_call __attribute__ ((unused,__section__ ("function_ptrs"))) #define module_init(x) __initcall(x); #define __init __attribute__ ((__section__ ("code_segment"))) static int __init my_init1 (void) { printf ("my_init () #1/n"); return 0; } static int __init my_init2 (void) { printf ("my_init () #2/n"); return 0; } module_init (my_init1); module_init (my_init2); void do_initcalls (void) { initcall_t *call_p; 定义函数指针变量 call_p = &__initcall_start; do { fprintf (stderr, "call_p: %p/n", call_p); (*call_p)(); ++call_p; } while (call_p < &__initcall_end); } int main (void) { fprintf (stderr, "in main()/n"); do_initcalls (); return 0; } 2、导出默认的连接控制脚本文件:保存为linker.lds 通过命令gcc -Wl,--verbose可以获得默认的连接控制脚本, 即选择 "=======..."之间的文本,保存为linker.lds文件 3、在linker.lds文件中增加本例需要控制的语句: 将下面 __initcall_start = .; function_ptrs : { *(function_ptrs) } __initcall_end = .; code_segment : { *(code_segment) } 这段代码copy到linker.lds文件的 __bss_start = .; 语句之前。 完整的linker.lds文件如下: OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") OUTPUT_ARCH(i386) ENTRY(_start) SEARCH_DIR("/usr/i386-redhat-linux/lib"); SEARCH_DIR("/usr/lib"); SEARCH_DIR("/usr/local/lib"); SEARCH_DIR("/lib"); SECTIONS { . = 0x08048000 + SIZEOF_HEADERS; .interp : { *(.interp) } .hash : { *(.hash) } .dynsym : { *(.dynsym) } .dynstr : { *(.dynstr) } .gnu.version : { *(.gnu.version) } .gnu.version_d : { *(.gnu.version_d) } .gnu.version_r : { *(.gnu.version_r) } .rel.dyn : { *(.rel.init) *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*) *(.rel.fini) *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*) *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*) *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*) *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*) *(.rel.ctors) *(.rel.dtors) *(.rel.got) *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*) } .rela.dyn : { *(.rela.init) *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) *(.rela.fini) *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) *(.rela.ctors) *(.rela.dtors) *(.rela.got) *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) } .rel.plt : { *(.rel.plt) } .rela.plt : { *(.rela.plt) } .init : { KEEP (*(.init)) } =0x90909090 .plt : { *(.plt) } .text : { *(.text .stub .text.* .gnu.linkonce.t.*) *(.gnu.warning) } =0x90909090 .fini : { KEEP (*(.fini)) } =0x90909090 PROVIDE (__etext = .); PROVIDE (_etext = .); PROVIDE (etext = .); .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } .rodata1 : { *(.rodata1) } .eh_frame_hdr : { *(.eh_frame_hdr) } .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) } .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table) } . = ALIGN (0x1000) - ((0x1000 - .) & (0x1000 - 1)); . = DATA_SEGMENT_ALIGN (0x1000, 0x1000); . = ALIGN(32 / 8); PROVIDE (__preinit_array_start = .); .preinit_array : { *(.preinit_array) } PROVIDE (__preinit_array_end = .); PROVIDE (__init_array_start = .); .init_array : { *(.init_array) } PROVIDE (__init_array_end = .); PROVIDE (__fini_array_start = .); .fini_array : { *(.fini_array) } PROVIDE (__fini_array_end = .); .data : { *(.data .data.* .gnu.linkonce.d.*) SORT(CONSTRUCTORS) } .data1 : { *(.data1) } .tdata : { *(.tdata .tdata.* .gnu.linkonce.td.*) } .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) } .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table) } .dynamic : { *(.dynamic) } .ctors : { KEEP (*crtbegin*.o(.ctors)) KEEP (*(EXCLUDE_FILE (*crtend*.o ) .ctors)) KEEP (*(SORT(.ctors.*))) KEEP (*(.ctors)) } .dtors : { KEEP (*crtbegin*.o(.dtors)) KEEP (*(EXCLUDE_FILE (*crtend*.o ) .dtors)) KEEP (*(SORT(.dtors.*))) KEEP (*(.dtors)) } .jcr : { KEEP (*(.jcr)) } .got : { *(.got.plt) *(.got) } _edata = .; PROVIDE (edata = .); __initcall_start = .; function_ptrs : { *(function_ptrs) } __initcall_end = .; code_segment : { *(code_segment) } __bss_start = .; .bss : { *(.dynbss) *(.bss .bss.* .gnu.linkonce.b.*) *(COMMON) . = ALIGN(32 / 8); } . = ALIGN(32 / 8); _end = .; PROVIDE (end = .); . = DATA_SEGMENT_END (.); .stab 0 : { *(.stab) } .stabstr 0 : { *(.stabstr) } .stab.excl 0 : { *(.stab.excl) } .stab.exclstr 0 : { *(.stab.exclstr) } .stab.index 0 : { *(.stab.index) } .stab.indexstr 0 : { *(.stab.indexstr) } .comment 0 : { *(.comment) } .debug 0 : { *(.debug) } .line 0 : { *(.line) } .debug_srcinfo 0 : { *(.debug_srcinfo) } .debug_sfnames 0 : { *(.debug_sfnames) } .debug_aranges 0 : { *(.debug_aranges) } .debug_pubnames 0 : { *(.debug_pubnames) } .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } .debug_abbrev 0 : { *(.debug_abbrev) } .debug_line 0 : { *(.debug_line) } .debug_frame 0 : { *(.debug_frame) } .debug_str 0 : { *(.debug_str) } .debug_loc 0 : { *(.debug_loc) } .debug_macinfo 0 : { *(.debug_macinfo) } .debug_weaknames 0 : { *(.debug_weaknames) } .debug_funcnames 0 : { *(.debug_funcnames) } .debug_typenames 0 : { *(.debug_typenames) } .debug_varnames 0 : { *(.debug_varnames) } /DISCARD/ : { *(.note.GNU-stack) } } 4、编译程序 命令: gcc -Tlinker.lds -o doinitcall doinitcall.c 其中: -T选项告诉ld要用的连接控制脚本文件,做为链接程序的依据。格式如下: -T commandfile 或 --script=commandfile 5、执行程序 可以看到如下结果: [[email protected] zhouys]$ ./doinitcall in main() call_p: 0x804961c my_init () #1 call_p: 0x8049620 my_init () #2 三、参考资料: 1、 Understanding The Linux Kernel Initcall Mechanism 2 、linux2.4.0 源码