说明:以下内容基于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);
/* Make sure there is no pending stuff from the initcall sequence */
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; /*申明外部变量,在ld的脚本文件中定义*/
#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);
/*上述宏定义名为"__initcall_函数名"的函数指针,且将函数指针放在function_ptrs节
这个函数指针,指向fn(fn函数则放在code_segment节中)*/
#define __init __attribute__ ((__section__ ("code_segment")))
static int __init /*函数放在code_segment节*/
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;/*32位机器上,函数指针占4bytes,增加一次就是指针便宜4bytes*/
} 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符号为当前位置,即.代表当前位置*/
__initcall_start = .;
function_ptrs : { *(function_ptrs) }
__initcall_end = .;
/*上述3行代码代表function_ptrs节位于__initcall_start和__initcall_end之间*/
code_segment : { *(code_segment) }
这段代码copy到linker.lds文件的
__bss_start = .;
语句之前。
完整的linker.lds文件如下:
/* Script for -z combreloc: combine and sort reloc sections */
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");
/* Do we need any of these for elf?
__DYNAMIC = 0; */
SECTIONS
{
/* Read-only sections, merged into text segment: */
. = 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 sections are handled specially by elf32.em. */
*(.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) }
/* Adjust the address for the data segment. We want to adjust up to
the same address within the page on the next page up. */
. = ALIGN (0x1000) - ((0x1000 - .) & (0x1000 - 1)); . = DATA_SEGMENT_ALIGN (0x1000, 0x1000);
/* Ensure the __preinit_array_start label is properly aligned. We
could instead move the label definition inside the section, but
the linker would then create the section even if it turns out to
be empty, which isn't pretty. */
. = 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
:
{
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin*.o(.ctors))
/* We don't want to include the .ctor section from
from the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
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符号为当前位置,即.代表当前位置*/
__initcall_start = .;
function_ptrs
: { *(function_ptrs) }
__initcall_end = .;
/*上述3行代码代表function_ptrs节位于__initcall_start和__initcall_end之间*/
code_segment
: { *(code_segment) }
__bss_start = .;
.bss
:
{
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
/* Align here to ensure that the .bss section occupies space up to
_end. Align after .bss to ensure correct alignment even if the
.bss section disappears because there are no input sections. */
. = ALIGN(32 / 8);
}
. = ALIGN(32 / 8);
_end = .;
PROVIDE (end = .);
. = DATA_SEGMENT_END (.);
/* Stabs debugging sections. */
.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) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug
0 : { *(.debug) }
.line
0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.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) }
/* SGI/MIPS DWARF 2 extensions */
.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、执行程序
可以看到如下结果:
[zhouys@Cluster1 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
源码