Chinaunix首页 | 论坛 | 博客
  • 博客访问: 282581
  • 博文数量: 150
  • 博客积分: 2396
  • 博客等级: 大尉
  • 技术积分: 1536
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-19 09:55
文章分类

全部博文(150)

文章存档

2021年(1)

2015年(9)

2014年(7)

2013年(50)

2012年(33)

2011年(1)

2010年(13)

2009年(36)

我的朋友

分类: LINUX

2010-05-13 07:42:24

initcall简介

linux kernel中包含了许多独立的features,这些features可以在配置内核时enable或者disable。而这些features一般都会有自己的初始化函数,按照传统的写法,对这些的features的初始化会这样做:

  1. features_init()
  2. {
  3. #ifdef FEATURE_A
  4.     feature_a_init();
  5. #endif
  6. #ifdef FEATURE_B
  7.     feature_b_init();
  8. #endif
  9. #ifdef FEATURE_C
  10.     feature_c_init();
  11. #endif
  12.     ......
  13. }

这种做法的问题在于,linux kernel拥有无数features,这就意味着这里要有无数的”#ifdef … #endif”。这一方面使得代码看起来很dirty,另一方面,每个features的作者都需要在这里额外的维护一小段代码,这降低了整个代码的可维护性和正交性。

为了解决这个问题,Linux Kernel 2.3.13引入了initcall的概念。

initcall在概念上很简单。

  1. kernel image使用ELF格式
  2. 每个feature维护一个指向自己初始化函数的函数指针,使用gcc的扩展关键字__attribute__可以把这个指针放到一个特定的ELF Section中,比方说.initcall。在编译时,只有enable了的feature才会被编译,它维护的函数指针才会放到最后生成的ELF文件的.initcall中。
  3. 使用ld script,可以“手工”向ELF文件中插入symbol。可以通过适当插入symbol的来让我们的C code知道.initcall这个section的起始地址和结束地址,这样我们的C code就可以访问这个section中的数据。
  4. 现在我们可以访问到.initcall中的函数指针,遍历调用他们即可。

下面我们用一个简单的例子来演示一下initcall的实现。首先为了能在C code中定位.initcall的起始、终止地址,我们不能使用默认的ld script,要对他做一点点修改。可以用”ld -verbose”命令获得ld默认使用的ld script,对它做简单修改后我们得到:

  1. /* Script for -z combreloc: combine and sort reloc sections */
  2. OUTPUT_FORMAT("elf32-i386", "elf32-i386",
  3. "elf32-i386")
  4. OUTPUT_ARCH(i386)
  5. ENTRY(_start)
  6. SEARCH_DIR("/usr/i386-redhat-linux/lib"); SEARCH_DIR("/usr/local/lib"); SEARCH_DIR("/lib"); SEARCH_DIR("/usr/lib");
  7. /* Do we need any of these for elf?
  8. __DYNAMIC = 0; */
  9. SECTIONS
  10. {
  11. /* Read-only sections, merged into text segment: */
  12. PROVIDE (__executable_start = 0x08048000); . = 0x08048000 + SIZEOF_HEADERS;
  13. .interp : { *(.interp) }
  14. .hash : { *(.hash) }
  15. .dynsym : { *(.dynsym) }
  16. .dynstr : { *(.dynstr) }
  17. .gnu.version : { *(.gnu.version) }
  18. .gnu.version_d : { *(.gnu.version_d) }
  19. .gnu.version_r : { *(.gnu.version_r) }
  20. .rel.dyn :
  21. {
  22. *(.rel.init)
  23. *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*)
  24. *(.rel.fini)
  25. *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*)
  26. *(.rel.data.rel.ro*)
  27. *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*)
  28. *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*)
  29. *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*)
  30. *(.rel.ctors)
  31. *(.rel.dtors)
  32. *(.rel.got)
  33. *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*)
  34. }
  35. .rela.dyn :
  36. {
  37. *(.rela.init)
  38. *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
  39. *(.rela.fini)
  40. *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
  41. *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
  42. *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
  43. *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
  44. *(.rela.ctors)
  45. *(.rela.dtors)
  46. *(.rela.got)
  47. *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
  48. }
  49. .rel.plt : { *(.rel.plt) }
  50. .rela.plt : { *(.rela.plt) }
  51. .init :
  52. {
  53. KEEP (*(.init))
  54. } =0x90909090
  55. .plt : { *(.plt) }
  56. .text :
  57. {
  58. *(.text .stub .text.* .gnu.linkonce.t.*)
  59. /* .gnu.warning sections are handled specially by elf32.em. */
  60. *(.gnu.warning)
  61. } =0x90909090
  62. __initcall_begin = .;
  63. .initcall : { *(.initcall) }
  64. __initcall_end = .;
  65. .fini :
  66. {
  67. KEEP (*(.fini))
  68. } =0x90909090
  69. PROVIDE (__etext = .);
  70. PROVIDE (_etext = .);
  71. PROVIDE (etext = .);
  72. .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
  73. .rodata1 : { *(.rodata1) }
  74. .eh_frame_hdr : { *(.eh_frame_hdr) }
  75. .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) }
  76. .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table) }
  77. /* Adjust the address for the data segment. We want to adjust up to
  78. the same address within the page on the next page up. */
  79. . = ALIGN (0x1000) - ((0x1000 - .) & (0x1000 - 1)); . = DATA_SEGMENT_ALIGN (0x1000, 0x1000);
  80. /* Exception handling */
  81. .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) }
  82. .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table) }
  83. /* Thread Local Storage sections */
  84. .tdata : { *(.tdata .tdata.* .gnu.linkonce.td.*) }
  85. .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
  86. /* Ensure the __preinit_array_start label is properly aligned. We
  87. could instead move the label definition inside the section, but
  88. the linker would then create the section even if it turns out to
  89. be empty, which isn't pretty. */
  90. . = ALIGN(32 / 8);
  91. PROVIDE (__preinit_array_start = .);
  92. .preinit_array : { *(.preinit_array) }
  93. PROVIDE (__preinit_array_end = .);
  94. PROVIDE (__init_array_start = .);
  95. .init_array : { *(.init_array) }
  96. PROVIDE (__init_array_end = .);
  97. PROVIDE (__fini_array_start = .);
  98. .fini_array : { *(.fini_array) }
  99. PROVIDE (__fini_array_end = .);
  100. .ctors :
  101. {
  102. /* gcc uses crtbegin.o to find the start of
  103. the constructors, so we make sure it is
  104. first. Because this is a wildcard, it
  105. doesn't matter if the user does not
  106. actually link against crtbegin.o; the
  107. linker won't look for a file to match a
  108. wildcard. The wildcard also means that it
  109. doesn't matter which directory crtbegin.o
  110. is in. */
  111. KEEP (*crtbegin*.o(.ctors))
  112. /* We don't want to include the .ctor section from
  113. from the crtend.o file until after the sorted ctors.
  114. The .ctor section from the crtend file contains the
  115. end of ctors marker and it must be last */
  116. KEEP (*(EXCLUDE_FILE (*crtend*.o ) .ctors))
  117. KEEP (*(SORT(.ctors.*)))
  118. KEEP (*(.ctors))
  119. }
  120. .dtors :
  121. {
  122. KEEP (*crtbegin*.o(.dtors))
  123. KEEP (*(EXCLUDE_FILE (*crtend*.o ) .dtors))
  124. KEEP (*(SORT(.dtors.*)))
  125. KEEP (*(.dtors))
  126. }
  127. .jcr : { KEEP (*(.jcr)) }
  128. .data.rel.ro : { *(.data.rel.ro.local) *(.data.rel.ro*) }
  129. .dynamic : { *(.dynamic) }
  130. .got : { *(.got) }
  131. .got.plt : { . = DATA_SEGMENT_RELRO_END (. + 12); *(.got.plt) }
  132. .data :
  133. {
  134. *(.data .data.* .gnu.linkonce.d.*)
  135. SORT(CONSTRUCTORS)
  136. }
  137. .data1 : { *(.data1) }
  138. _edata = .;
  139. PROVIDE (edata = .);
  140. __bss_start = .;
  141. .bss :
  142. {
  143. *(.dynbss)
  144. *(.bss .bss.* .gnu.linkonce.b.*)
  145. *(COMMON)
  146. /* Align here to ensure that the .bss section occupies space up to
  147. _end. Align after .bss to ensure correct alignment even if the
  148. .bss section disappears because there are no input sections. */
  149. . = ALIGN(32 / 8);
  150. }
  151. . = ALIGN(32 / 8);
  152. _end = .;
  153. PROVIDE (end = .);
  154. . = DATA_SEGMENT_END (.);
  155. /* Stabs debugging sections. */
  156. .stab 0 : { *(.stab) }
  157. .stabstr 0 : { *(.stabstr) }
  158. .stab.excl 0 : { *(.stab.excl) }
  159. .stab.exclstr 0 : { *(.stab.exclstr) }
  160. .stab.index 0 : { *(.stab.index) }
  161. .stab.indexstr 0 : { *(.stab.indexstr) }
  162. .comment 0 : { *(.comment) }
  163. /* DWARF debug sections.
  164. Symbols in the DWARF debugging sections are relative to the beginning
  165. of the section so we begin them at 0. */
  166. /* DWARF 1 */
  167. .debug 0 : { *(.debug) }
  168. .line 0 : { *(.line) }
  169. /* GNU DWARF 1 extensions */
  170. .debug_srcinfo 0 : { *(.debug_srcinfo) }
  171. .debug_sfnames 0 : { *(.debug_sfnames) }
  172. /* DWARF 1.1 and DWARF 2 */
  173. .debug_aranges 0 : { *(.debug_aranges) }
  174. .debug_pubnames 0 : { *(.debug_pubnames) }
  175. /* DWARF 2 */
  176. .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
  177. .debug_abbrev 0 : { *(.debug_abbrev) }
  178. .debug_line 0 : { *(.debug_line) }
  179. .debug_frame 0 : { *(.debug_frame) }
  180. .debug_str 0 : { *(.debug_str) }
  181. .debug_loc 0 : { *(.debug_loc) }
  182. .debug_macinfo 0 : { *(.debug_macinfo) }
  183. /* SGI/MIPS DWARF 2 extensions */
  184. .debug_weaknames 0 : { *(.debug_weaknames) }
  185. .debug_funcnames 0 : { *(.debug_funcnames) }
  186. .debug_typenames 0 : { *(.debug_typenames) }
  187. .debug_varnames 0 : { *(.debug_varnames) }
  188. /DISCARD/ : { *(.note.GNU-stack) }
  189. }

文件比较长,但是这blog的文件上传功能貌似有点问题,所以还是直接贴上来了。不过这个文件中只有62-64这3行是我添加的,具体的说是以下三行:

__initcall_begin = .;
.initcall : { *(.initcall) }
__initcall_end = .;

其他全部是ld默认script的内容。ld script的内容这里不说太多,不会的可以查阅ld的info page。

然后是一个简单的c程序initcall.c:

下载: initcall.c
  1. #include <stdio.h>
  2. #define __init __attribute__ ((section (".initcall")))
  3. typedef int (*initcall_t)(void);
  4. int ic1(void)
  5. {
  6.     return 1;
  7. }
  8. int ic2(void)
  9. {
  10.     return 2;
  11. }
  12. int ic3(void)
  13. {
  14.     return 3;
  15. }
  16. initcall_t __init ic_1 = ic1;
  17. initcall_t __init ic_2 = ic2;
  18. initcall_t __init ic_3 = ic3;
  19. extern initcall_t __initcall_begin, __initcall_end;
  20. int main(void)
  21. {
  22.     initcall_t *initcall;
  23.     for(initcall=&__initcall_begin; initcall<&__initcall_end; initcall++) {
  24.         printf("%d\n", (*initcall)());
  25.     }
  26.     return 0;
  27. }

编译:

gcc -Tinitcall.lds initcall.c

执行:

$ ./a.out
1
2
3

参考资料:

  1. info gcc “C Extensions” “Variable Attributes”
  2. info ld Scripts Assignments

 

//======================================================

 

分析kernel的initcall函数
Author: Dongas
Data: 08-07-15

先来看看这些initcall函数的声明:
/* include/linux/init.h */
/* initcalls are now grouped by functionality into separate
* subsections. Ordering inside the subsections is determined
* by link order.
* For backwards compatibility, initcall() puts the call in
* the device init subsection.
*/

#define __define_initcall(level,fn) \
       static initcall_t __initcall_##fn __attribute_used__ \
       __attribute__((__section__(".initcall" level ".init"))) = fn

#define core_initcall(fn)        __define_initcall("1",fn)
#define postcore_initcall(fn)        __define_initcall("2",fn)
#define arch_initcall(fn)        __define_initcall("3",fn)
#define subsys_initcall(fn)            __define_initcall("4",fn)
#define fs_initcall(fn)                     __define_initcall("5",fn)
#define device_initcall(fn)           __define_initcall("6",fn)
#define late_initcall(fn)         __define_initcall("7",fn)

#define __initcall(fn) device_initcall(fn)

#define __exitcall(fn) \
       static exitcall_t __exitcall_##fn __exit_call = fn

#define console_initcall(fn) \
       static initcall_t __initcall_##fn \
       __attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn

#define security_initcall(fn) \
       static initcall_t __initcall_##fn \
       __attribute_used__ __attribute__((__section__(".security_initcall.init"))) = fn

#define module_init(x)   __initcall(x);    ß从这里知道module_init的等级为6,相对靠后
#define module_exit(x)  __exitcall(x);


可以发现这些*_initcall(fn)最终都是通过__define_initcall(level,fn)宏定义生成的。
__define_initcall宏定义如下:
#define __define_initcall(level,fn) \
       static initcall_t __initcall_##fn __attribute_used__ \
       __attribute__((__section__(".initcall" level ".init"))) = fn

这句话的意思为定义一个initcall_t型的初始化函数,函数存放在.initcall”level”.init section内。.initcall”level”.init section定义在vmlinux.lds内。
/* arch/arm/kernel/vmlinux.lds */
……
  __initcall_start = .;
   *(.initcall1.init)
   *(.initcall2.init)
   *(.initcall3.init)
   *(.initcall4.init)
   *(.initcall5.init)
   *(.initcall6.init)
   *(.initcall7.init)
  __initcall_end = .;
       ……
正好包括了上面init.h里定义的从core_initcall到late_initcall等7个level等级的.initcall”level”.init section. 因此通过不同的*_initcall声明的函数指针最终都会存放不同level等级的.initcall”level”.init section内。这些不同level的section按level等级高低依次存放。

下面我们再来看看,内核是什么时候调用存储在.initcall”level”.init section内的函数的。

内核是通过do_initcalls函数循环调用执行initcall.init section内的函数的,流程如下:
start_kernel -> rest_init -> kernel_thread -> init -> do_basic_setup -> do_initcalls


这里要分析两个函数: kernel_thread和do_initcalls,这两个函数都定义在init/main.c内
1)    kernel_thread
1.static void noinline rest_init(void)
2.    __releases(kernel_lock)
3.{
4.    system_state = SYSTEM_BOOTING_SCHEDULER_OK;
5.
6.    kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
7.    numa_default_policy();
8.    unlock_kernel();
9.
10.  /*
11.  * The boot idle thread must execute schedule()
12.  * at least one to get things moving:
13.  */
14.  __preempt_enable_no_resched();
15.  schedule();
16.  preempt_disable();
17.
18.  /* Call into cpu_idle with preempt disabled */
19.  cpu_idle();
20.}
第6行通过kernel_thread创建一个内核线程执行init函数。(其实这里创建的即Linux的1号进程(init进程), 为linux中所有其他进程的父进程,有兴趣的可以自己查资料)

2)    do_initcalls
1.static void __init do_initcalls(void)
2.{
3.    initcall_t *call;
4.    int count = preempt_count();
5.
6.    for (call = __initcall_start; call
7.           ……
8.           result = (*call)();
9.           ……
10.  }
11.}
其中, initcall_t类型如下:
typedef int (*initcall_t)(void);

__initcall_start和__initcall_end定义在vmlinux.lds内,表示initcall section的起始和结束地址。
/* arch/arm/kernel/vmlinux.lds */
……
  __initcall_start = .;
   *(.initcall1.init)
   *(.initcall2.init)
   *(.initcall3.init)
   *(.initcall4.init)
   *(.initcall5.init)
   *(.initcall6.init)
   *(.initcall7.init)
  __initcall_end = .;
       ……
因此,上面6-10行代码的作用为按initcall level等级的顺序,依次循环调用预先存储在initcall section内的所有各个级别的初始化函数。这样,kernel的initcall函数的原理我们就搞清楚了。

最后要注意的是rest_init是在start_kernel函数内最后部分才被调用执行的,rest_init前包含了kernel一系列的初始化工作。另外,这些不同level等级的initcall.init section本身有一定的执行顺序,因此如果你的驱动依赖于特定的执行顺序的话需要考虑到这一点。

阅读(1358) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~