关于do_initcalls函数的说明

来源:百度文库 编辑:神马文学网 时间:2024/04/28 00:27:14
1.
在看linux核心代码的时候看到/init/main.c 里面的do_initcalls函数
static void __init do_initcalls(void)
742 {
743         initcall_t *call;
744
745         for (call = __initcall_start; call < __initcall_end; call++)
746                 do_one_initcall(*call);
747
748         /* Make sure there is no pending stuff from the initcall sequence */
749         flush_scheduled_work();
750 }
static void __init do_one_initcall(initcall_t fn)
697 {
698         int count = preempt_count();
699         ktime_t t0, t1, delta;
700         char msgbuf[64];
701         int result;
702
703         if (initcall_debug) {
704                 print_fn_descriptor_symbol("calling  %s\n", fn);
705                 t0 = ktime_get();
706         }
707
708         result = fn();
709
710         if (initcall_debug) {
711                 t1 = ktime_get();
712                 delta = ktime_sub(t1, t0);
713
714                 print_fn_descriptor_symbol("initcall %s", fn);
715                 printk(" returned %d after %Ld msecs\n", result,
716                         (unsigned long long) delta.tv64 >> 20);
717         }
718
719         msgbuf[0] = 0;
720
721         if (result && result != -ENODEV && initcall_debug)
722                 sprintf(msgbuf, "error code %d ", result);
723
724         if (preempt_count() != count) {
725                 strlcat(msgbuf, "preemption imbalance ", sizeof(msgbuf));
726                 preempt_count() = count;
727         }
728         if (irqs_disabled()) {
729                 strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));
730                 local_irq_enable();
731         }
732         if (msgbuf[0]) {
733                 print_fn_descriptor_symbol(KERN_WARNING "initcall %s", fn);
734                 printk(" returned with %s\n", msgbuf);
735         }
736 }

__initcall_start是在arch目录中的相关CPU中的vmlinux.lds文件指定,如i386中位于arch/i386/vmlinux.lds中,至于在__initcall_start和__initcall_end之间的是由函数声明__init指定
2.
察看/arch/i386/vmlinux.lds,发现一段代码
 __initcall_start = .;
 .initcall.init : { *(.initcall.init) }
 __initcall_end = .;
 
跟我找的东西相关
使用info ld,察看相关资料,(最终发现太麻烦,到网上找了一个ld.pdf).发现有这么一
段介绍关于c++地联结构造器的使用,和这段用法相同
其含义时,是让__initcall_start指向代码节.initcall.init的节首,而__initcall_end
指向.initcall.init的节尾。
那么第一段代码从程序逻辑上得到了解释。

3。因为do_initcalls所作的是系统中有关于选择的驱动部分的初始化工作,那么具体是这些
函数指针数据怎样放到了.initcall.init节。
想起来了,还没有使用grep哈哈,在
grep -rn .initcall.init *
发现在include/linux/init.h:83:有这样一个定义
#define __init_call   __attribute__ ((unused,__section__ (".initcall.init")))
娃哈哈哈
终于让我发现了
然后又发现了
#define __initcall(fn) \
static initcall_t __initcall_##fn __init_call = fn

4。问题是什么是__attribute__??,查找man gcc,找到关于__attribute__的定义
`section ("section-name")'
   Normally, the compiler places the code it generates in the `text'
   section. Sometimes, however, you need additional sections, or you
   need certain particular functions to appear in special sections.
   The `section' attribute specifies that a function lives in a
   particular section. For example, the declaration:

     extern void foobar (void) __attribute__ ((section ("bar")));

   puts the function `foobar' in the `bar' section.

   Some file formats do not support arbitrary sections so the
   `section' attribute is not available on all platforms. If you
   need to map the entire contents of a module to a particular
   section, consider using the facilities of the linker instead.
  
他的意思就是使它建造一个在.initcall.init节的指向初始函数的指针

5。问题是##是什么意思?
查阅gcc的man page得知,她是用在可变参数使用宏定义的时候的
在这里也就是建立一个变量名称为所指向的函数的名称,并且前面加上
__initcall_.

6.然后看看成果
在/include/linux/init.c中有发现
#define module_init(x) __initcall(x);
看看很多驱动中都有类似
module_init(usb_init);
module_exit(usb_exit);
的代码,哈哈,这下明白了。


分析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”.initsection内。这些不同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.initsection本身有一定的执行顺序,因此如果你的驱动依赖于特定的执行顺序的话需要考虑到这一点。


initcall机制原理及实践

说明:以下内容基于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  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 __initmy_init2 (void){       printf ("my_init () #2\n");       return 0;} module_init (my_init1);/*定义要被调用的函数指针并放到指定的节中*/module_init (my_init2); voiddo_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);} intmain (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=commandfile5、执行程序       可以看到如下结果:[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源码

linux内核中关于网络初始化过程中do_basic_setup等的问题
linux网络初始化时,initmain.c中的start_kernel()函数中调用kernel_thread启动了init进程,该进程对应于initmain.c中的init函数。init函数中调用do_basic_setup()函数,这个函数又调用了netsocket.c中的sock_init()函数。
do_basic_setup()的功能:
总线初始化(比如pci_init())
网络初始化(初始化网络数据结构,sock_init();2.4.17中sock_init()只是初始化了网络数据结构,真正的协议初始化是运行do_initcalls()时自动加载各个协议模块的)
创建事件管理核心线程(start_context_thread())
再加载任何模块(用do_initcalls()启动任何使用__initcall标识的函数(方便核心研发者添加启动函数))
#define module_init(x) __initcall(x);这句在linuxinit.h中,通过这么定义,使得系统初始化到调用initmain.c中的do_initcalls时自动加载了模块x(进入x初始化)。而在af_inet.c中有module_init(inet_init);所以也包括初始化inet模块了。呵呵
在2.6.14.2中就比较容易理解了。看
static void __init do_basic_setup(void)
{
/* drivers will send hotplug events */
init_workqueues();
usermodehelper_init();
driver_init();
#ifdef CONFIG_SYSCTL
sysctl_init();
#endif
/* Networking initialization needs a process context */
sock_init();
do_initcalls();
}
主要根据代码看的,比较仓促,因为这不是我现在要做的主要工作,来不及周详整理了,先这样记下好了,有异议的朋友欢迎留言讨论,呵呵。

内核启动时,设备及驱动初始化的实现
Uboot完成系统的引导并将Linux内核拷贝到内存之后,bootm -> do_bootm_linux()跳转到kernel的起始位置;
      压缩过的kernel入口在arch/arm/boot/compressed/head.S,它将调用函数decompress_kernel()<./arch/arm/boot/compressed/misc.c>解压,打印“Uncompressing Linux...”,调用gunzip(),打印"done, booting the kernel."
      然后call_kernel,执行解压后的kernel,经linux/arch/arm/kernel/head.S调用start_kernel转入体系结构无关的通用C代码,在start_kernel()中完成了一系列系统初始化,设备及驱动的注册即在此时完成:

<./init/main.c>-------------------------

asmlinkage void __init start_kernel(void)
{
 char * command_line;
 extern struct kernel_param __start___param[], __stop___param[];

···········································································
 printk(KERN_NOTICE "Kernel command line: %s\n", saved_command_line);
                                                          //打印内核命令行
 parse_early_param();
 parse_args("Booting kernel", command_line, __start___param,
     __stop___param - __start___param,
     &unknown_bootoption);
                                                        //解析由BOOT传递的启动参数
···········································································

 /* Do the rest non-__init'ed, we're now alive */
 rest_init();
}

start_kernel()中的函数rest_init()将创建第一个核心线程kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND),调用init()函数:

static int init(void * unused)-------------------
{
                ·······················
                 do_basic_setup();
                ······················

 /*
  * We try each of these until one succeeds.
  *
  * The Bourne shell can be used instead of init if we are
  * trying to recover a really broken machine.
  */
 if (execute_command) { //判断在启动时是否指定了init参数
                                      //如果指定则执行用户init进程,成功将不会返回
  run_init_process(execute_command);
  printk(KERN_WARNING "Failed to execute %s.  Attempting "
     "defaults...\n", execute_command);
 }

               /*   如果没有指定init启动参数,则查找下面的目录init进程,成功将不会返回,否则打印出错信息   */
 run_init_process("/sbin/init");
 run_init_process("/etc/init");
 run_init_process("/bin/init");
 run_init_process("/bin/sh");

 panic("No init found.  Try passing init= option to kernel.");

}

继而调用函数do_basic_setup()(此时与体系结构相关的部分已经初始化完了,现在开始初始化设备了):

/*
 * Ok, the machine is now initialized. None of the devices
 * have been touched yet, but the CPU subsystem is up and
 * running, and memory and process management works.
 *
 * Now we can finally start doing some real work..
 */
static void __init do_basic_setup(void)-----------------
{
 /* drivers will send hotplug events */
 init_workqueues();
 usermodehelper_init();
 driver_init();     //建立设备模型子系统

#ifdef CONFIG_SYSCTL
 sysctl_init();
#endif

 /* Networking initialization needs a process context */
 sock_init();

 do_initcalls();   //系统初始化(包括设备,文件系统,内核模块等)
}
<./drivers/base/init.c>-------------------------
/**
 * driver_init - initialize driver model.
 *
 * Call the driver model init functions to initialize their
 * subsystems. Called early from init/main.c.
 */

void __init driver_init(void)
{
 /* These are the core pieces */
 devices_init();
                       <./drivers/base/core.c>-------------
                                  int __init devices_init(void)
                                  {
                   return subsystem_register(&devices_subsys);
                                  }
                        -----------------------
 buses_init();
 classes_init();
 firmware_init();

 /* These are also core pieces, but must come after the
  * core core pieces.
  */
 platform_bus_init();
 system_bus_init();
 cpu_dev_init();
 memory_dev_init();
 attribute_container_init();
}
---------------------------
extern initcall_t __initcall_start[], __initcall_end[];

static void __init do_initcalls(void)
{
 initcall_t *call;
 int count = preempt_count();

 for (call = __initcall_start; call < __initcall_end; call++) {
                ··················
                (*call)();     //调用一系列初始化函数
               ···················
}
---------------------------
     __initcall_start和__initcall_end界定了存放初始化函数指针区域的起始地址,即从__initcall_start开始到__initcall_end结束的区域中存放了指向各个初始化函数的函数指针。 由(*call)()完成各个部分的初始化工作,且便于扩充。具体实现如下:
<./arch/arm/kernel/vmlinux.lds.S>-----------------
  __initcall_start = .;
   *(.initcall1.init)
   *(.initcall2.init)
   *(.initcall3.init)
   *(.initcall4.init)
   *(.initcall5.init)
   *(.initcall6.init)
   *(.initcall7.init)
  __initcall_end = .;

 <./include/linux/init.h>---------------------

#ifndef MODULE     /*    如果驱动模块静态编译进内核   */

  ···············································

/* 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)
                                            //此处初始化了设备
                                           /*----eg:arch_initcall(at91sam9261_device_init)---
                                               static int __init at91sam9261_device_init(void)
                                               {
                                                 at91_add_device_udc();
                                                 at91_add_device_dm9000();
                                                 armebs3_add_input_buttons();
                                                return platform_add_devices(at91sam9261_devices,ARRAY_SIZE(at91sam9261_devices));
                                                }
                                        ------------------------*/
#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)


  /**
 * module_init() - driver initialization entry point
 * @x: function to be run at kernel boot time or module insertion
 *
 * module_init() will either be called during do_initcalls (if
 * builtin) or at module insertion time (if a module).  There can only
 * be one per module.
 */
#define module_init(x) __initcall(x);
                                       //静态编译的驱动模块作为device_initcall在内核启动就被do_initcalls
/**
 * module_exit() - driver exit entry point
 * @x: function to be run when driver is removed
 *
 * module_exit() will wrap the driver clean-up code
 * with cleanup_module() when used with rmmod when
 * the driver is a module.  If the driver is statically
 * compiled into the kernel, module_exit() has no effect.
 * There can only be one per module.
 */
#define module_exit(x) __exitcall(x);

#else /* MODULE    如果驱动模块动态加载入内核   */

  ···············································

/* Each module must use one module_init(), or one no_module_init */
#define module_init(initfn)     \
 static inline initcall_t __inittest(void)  \
 { return initfn; }     \
 int init_module(void) __attribute__((alias(#initfn)));
     //insmod 是通过系统调用sys_init_module(const char *name_user, struct module *mod_user)
     //将动态驱动模块载入到内核空间

/* This is only required if you want to be unloadable. */
#define module_exit(exitfn)     \
 static inline exitcall_t __exittest(void)  \
 { return exitfn; }     \
 void cleanup_module(void) __attribute__((alias(#exitfn)));

-----------------------------

arm linux启动流程之进入内核

Author-------Dansen-----xzd2734@163.com
还是从编译链接生成vmlinux的过程来看吧,由一大堆.o文件链接而成,第一个就是
kernel\arch\arm\kernel\head-armv.o ,而且我们还看到了
lds链接文件kernel\arch\arm\vmlinux.lds,先把它分析一下
ENTRY(stext) //入口点是stext 应该就在head-armv.s中了
SECTIONS
{
. = 0xC0008000;  //基址,是内核开始的虚拟地址
.init : {   /* Init code and data  */
  _stext = .;
  __init_begin = .;
   *(.text.init)
  __proc_info_begin = .;
   *(.proc.info)
  __proc_info_end = .;
  __arch_info_begin = .;
   *(.arch.info)
  __arch_info_end = .;
  __tagtable_begin = .;
   *(.taglist)
  __tagtable_end = .;
   *(.data.init)
  . = ALIGN(16);
  __setup_start = .;
   *(.setup.init)
  __setup_end = .;
  __initcall_start = .;
   *(.initcall.init)
  __initcall_end = .;
  . = ALIGN(4096);
  __init_end = .;
}
关于虚拟地址和物理地址的:使用MMU后,系统就会使用虚拟地址,通过MMU来指向
实际物理地址而在这里我们的0xC0008000实际物理地址就是0x30008000,
具体关于MMU的介绍参考《ARM体系结构与编程》。
到head-armv.s找到程序的入口
  .section ".text.init",#alloc,#execinstr
  .type stext, #function
ENTRY(stext)
  mov r12, r0
  mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode
  msr cpsr_c, r0   @ and all irqs disabled
  bl __lookup_processor_type
  teq r10, #0    @ invalid processor?
  moveq r0, #'p'   @ yes, error 'p'
  beq __error
  bl __lookup_architecture_type
  teq r7, #0    @ invalid architecture?
  moveq r0, #'a'   @ yes, error 'a'
  beq __error
  bl __create_page_tables
  adr lr, __ret   @ return address
  add pc, r10, #12   @ initialise processor
来看看上一句跳到哪里去了
去追寻r10的值,是在__lookup_processor_type子函数中赋的
__lookup_processor_type:
  adr r5, 2f   //r5 标号2的地址 基址是0x30008000
  ldmia r5, {r7, r9, r10} //r7=__proc_info_end  r9=__proc_info_begin
  sub r5, r5, r10  //r10 标号2的链接地址   基址是0xc0008000
  add r7, r7, r5   @ to our address space
  add r10, r9, r5  //r10 变换为基址是0x30008000的__proc_info_begin
2:  .long __proc_info_end
  .long __proc_info_begin
  .long 2b
这样r10中存放的是__proc_info_begin的地址,因为现在我们还没有打开MMU
所以还是需要把基址变换到0x30008000,接着我们就去找__proc_info_begin吧
注意到在上面的vmlinux.lds中有这个标号,下来链接的是.proc.info段,
在kernel\arch\arm\mm\proc-arm920.s的最后找到了这个段
.section ".proc.info", #alloc, #execinstr
.type __arm920_proc_info,#object
__arm920_proc_info:
.long 0x41009200
.long 0xff00fff0
.long 0x00000c1e   @ mmuflags
b __arm920_setup
ok,这样我们就知道add pc, r10, #12跳到哪里去了,因为这个地址刚好放了条跳转语句
注意了b语句用的都是相对地址,所以不需要变换地址,反正是跳到__arm920_setup,而且
上一条语句是adr lr, __ret,设定了__arm920_setup的返回地址是__ret,所以执行完
__arm920_setup后回到head-armv.s的__ret标号继续执行.
__ret:  ldr lr, __switch_data
  mcr p15, 0, r0, c1, c0 //注意这里了,在这里打开了MMU
  mov r0, r0
  mov r0, r0
  mov r0, r0
  mov pc, lr //跳到__mmap_switched,这里已经用了虚拟地址了吧
// 这条指令ldr lr, __switch_data加载的__mmap_switched地址就是虚拟地址啊
__switch_data: .long __mmap_switched
从__mmap_switched一路执行下来,就要调到C语言代码中去了
  b SYMBOL_NAME(start_kernel) //在kernel\init\main.c中
这个程序不是特别复杂,细心看看还是能大概看懂,我也不能去一一注释
这里有一个流程图

到了C语言中就不是很难理解了
lock_kernel();
printk(linux_banner);
setup_arch(&command_line);
printk("Kernel command line: %s\n", saved_command_line);
parse_options(command_line);
trap_init();
init_IRQ();
sched_init();
softirq_init();
time_init();
就是一大堆初始化工作,追着每个函数去看好了
start_kernel最后调用的一个函数
static void rest_init(void)
{
kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
unlock_kernel();
current->need_resched = 1;
  cpu_idle();
}
用kernel_thread建立了一个init进程,执行的是main.c中的init函数
lock_kernel();
do_basic_setup();
在do_basic_setup中调用了do_initcalls函数
各种驱动都是在do_initcalls(void)中完成的
static void __init do_initcalls(void)
{
initcall_t *call;
call = &__initcall_start;
do {
  (*call)();
  call++;
} while (call
flush_scheduled_tasks();
}
__initcall_start也是在vmlinux.lds中赋值的,那就需要找到.initcall.ini这个段
在kernel\include\linux\init.h中可以找到
#define __init_call __attribute__ ((unused,__section__ (".initcall.init")))
typedef int (*initcall_t)(void);
#define __initcall(fn)        \
static initcall_t __initcall_##fn __init_call = fn
仔细研究下就发现这是把初始化函数的地址放到了.initcall.init段中
这样就可以不断调用驱动的初始化函数了
如果没有定义MODULE,那么#define module_init(x) __initcall(x);
所以如果要把驱动的编译进内核就很简单了吧
init的最后
if (execute_command)
  execve(execute_command,argv_init,envp_init);
execute_command与ppcboot传的命令行参数是有关的哦,就是init=/linuxrc
这样就要去执行根目录下的linuxrc脚本,这个脚本会去执行busybox
而busybox又去执行/etc/init.d/rcS脚本,这个脚本又去执行/usr/etc/rc.local
完了


__define_initcall 作用
前言

  宏定义__define_initcall(level,fn)对于内核的初始化很重要,它指示
  编译器在编译的时候,将一系列初始化函数的起始地址值按照一定的顺序
  放在一个section中。在内核初始化阶段,do_initcalls() 将按顺序从该
  section中以函数指针的形式取出这些函数的起始地址,来依次完成相应
  的初始化。由于内核某些部分的初始化需要依赖于其他某些部分的初始化
  的完成,因此这个顺序排列常常非常重要。

  下面将从__define_initcall(level,fn) 宏定义的代码分析入手,依次
  分析名称为initcall.init的section的结构,最后分析内核初始化函数
  do_initcalls()是如何利用宏定义__define_initcall(level,fn)及其相
  关的衍生的7个宏宏定义,来实现内核某些部分的顺序初始化的。

1、分析 __define_initcall(level,fn) 宏定义

   1) 这个宏的定义位于inlclude\linux\init.h中:

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

      其中 initcall_t 是一个函数指针类型:

        typedef int (*initcall_t)(void);

      而属性 __attribute__((__section__())) 则表示把对象放在一个这个
      由括号中的名称所指代的section中。

      所以这个宏定义的的含义是:1) 声明一个名称为__initcall_##fn的函数
      指针(其中##表示替换连接,);2) 将这个函数指针初始化为fn;3) 编译
      的时候需要把这个函数指针变量放置到名称为 ".initcall" level ".init"
      的section中(比如level="1",代表这个section的名称是 ".initcall1.init")。

   2) 举例:__define_initcall(6, pci_init)

      上述宏调用的含义是:1) 声明一个函数指针__initcall_pic_init = pci_init;
      且 2) 这个指针变量__initcall_pic_init 需要放置到名称为 .initcall6.init
      的section中( 其实质就是将 这个函数pic_init的首地址放置到了这个
      section中)。

    3) 这个宏一般并不直接使用,而是被定义成下述其他更简单的7个衍生宏
       这些衍生宏宏的定义也位于 inlclude\linux\Init.h 中:

       #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)

       因此通过宏 core_initcall() 来声明的函数指针,将放置到名称为
       .initcall1.init的section中,而通过宏 postcore_initcall() 来
       声明的函数指针,将放置到名称为.initcall2.init的section中,
       依次类推。

     4) 举例:device_initcall(pci_init)

        解释同上 1-2)。

2、与初始化调用有关section--initcall.init被分成了7个子section

   1) 它们依次是.initcall1.init、.initcall2.init、...、.initcall7.init

   2) 按照先后顺序依次排列

   3) 它们的定义在文件vmlinux.lds.S中

      例如 对于i386+,在i386\kernel\vmlinux.lds.S中有:

          __initcall_start = .;
          .initcall.init : {
                *(.initcall1.init)
                *(.initcall2.init)
                *(.initcall3.init)
                *(.initcall4.init)
                *(.initcall5.init)
                *(.initcall6.init)
                *(.initcall7.init)
                }
          __initcall_end = .;

       而在makefile 中有
     
       LDFLAGS_vmlinux += -T arch/$(ARCH)/kernel/vmlinux.lds.s

    4) 在这7个section总的开始位置被标识为__initcall_start,
       而在结尾被标识为__initcall_end。

3、 内核初始化函数do_basic_setup(): do_initcalls() 将从.initcall.init
    中,也就是这7个section中依次取出所有的函数指针,并调用这些
    函数指针所指向的函数,来完成内核的一些相关的初始化。

    这个函数的定义位于init\main.c中:

        extern initcall_t __initcall_start, __initcall_end;
        static void __init do_initcalls(void)
        {
            initcall_t *call;
            ....
            for (call = &__initcall_start; call < &__initcall_end; call++)
            {
                ....
                (*call)();
                ....
            }
            ....
         }

     这些函数指针指向的函数就是通过宏__define_initcall(level,fn)
     赋值的函数fn,他们调用的顺序就是放置在这些section中的顺序,
     这个顺序很重要, 这就是这个宏__define_initcall(level,fn)的作用。
     注意到,这里__initcall_start 和 __initcall_end 就是section
     initcall.init的头和尾。

4、 归纳之

    1) __define_initcall(level,fn)的作用就是指示编译器把一些初始化函数
       的指针(即:函数起始地址)按照顺序放置一个名为 .initcall.init 的
       section中,这个section又被分成了7个子section,它们按顺序排列。
       在内核初始化阶段,这些放置到这个section中的函数指针将供
       do_initcalls() 按顺序依次调用,来完成相应初始化。

    2) 函数指针放置到的子section由宏定义的level确定,对应level较小的
       子section位于较前面。而位于同一个子section内的函数指针顺序不定,
       将由编译器按照编译的顺序随机指定。

    3) 因此,如果你希望某个初始化函数在内核初始化阶段就被调用,那么你
       就应该使用宏__define_initcall(level,fn) 或 其7个衍生宏 把这个
       函数fn的对应的指针放置到按照初始化的顺序放置到相关的 section 中。
       同事,如果某个初始化函数fn_B需要依赖于另外一个初始化函数fn_A的
       完成,那么你应该把fn_B放在比fn_A对应的level值较大的子section中,
       这样,do_initcalls()将在fn_A之后调用fn_B。  ***********************************************************************  如果你希望某个初始化函数在内核初始化阶段就被调用,那么你
就应该使用宏__define_initcall(level,fn) 或 其7个衍生宏来
把这个初始化函数fn的起始地址按照初始化的顺序放置到相关的
section 中。 内核初始化时的do_initcalls()将从这个section
中按顺序找到这些函数来执行。 如果你希望某个初始化函数在内核初始化阶段就被调用,那么你
就应该使用宏__define_initcall(level,fn) 或 其7个衍生宏来
把这个初始化函数fn的起始地址按照初始化的顺序放置到相关的
section 中。 内核初始化时的do_initcalls()将从这个section
中按顺序找到这些函数来执行。 *******************************************************************http://forum.oss.org.cn/viewtopic.php?p=8899&sid=1f5aee1b543bfca24793e4508dd115d0  今天在做一个驱动的时候要用到另一个驱动(I2C)提供的API,在内核初始化时碰到了一个依赖问题。

  我的驱动在I2C初始化之前就运行起来了,而这时I2C提供的API还处于不可用状态。查了很多资料,网上有人说所有使用module_init这个宏的驱动程序的起动顺序都是不确定的(我没有查到权威的资料)。

  所有的__init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些__init函数指针,并在整个初始化完成后,释放整个init区段(包括.init.text,.initcall.init等)。

注意,这些函数在内核初始化过程中的调用顺序只和这里的函数指针的顺序有关,和1)中所述的这些函数本身在.init.text区段中的顺序无关。在2.4内核中,这些函数指针的顺序也是和链接的顺序有关的,是不确定的。在2.6内核中,initcall.init区段又分成7个子区段,分别是

.initcall1.init
.initcall2.init
.initcall3.init
.initcall4.init
.initcall5.init
.initcall6.init
.initcall7.init

  当需要把函数fn放到.initcall1.init区段时,只要声明

core_initcall(fn);

  即可。

  其他的各个区段的定义方法分别是:

core_initcall(fn) --->.initcall1.init
postcore_initcall(fn) --->.initcall2.init
arch_initcall(fn) --->.initcall3.init
subsys_initcall(fn) --->.initcall4.init
fs_initcall(fn) --->.initcall5.init
device_initcall(fn) --->.initcall6.init
late_initcall(fn) --->.initcall7.init

而与2.4兼容的initcall(fn)则等价于device_initcall(fn)。各个子区段之间的顺序是确定的,即先调用.initcall1.init中的函数指针,再调用.initcall2.init中的函数指针,等等。而在每个子区段中的函数指针的顺序是和链接顺序相关的,是不确定的。

  在内核中,不同的init函数被放在不同的子区段中,因此也就决定了它们的调用顺序。这样也就解决了一些init函数之间必须保证一定的调用顺序的问题。按照include/linux/init.h文件所写的,我在驱动里偿试了这样两种方式:

__define_initcall("7", fn);
late_initcall(fn);

  都可以把我的驱动调整到最后调用。实际上上面两个是一回事:

#define late_initcall(fn) __define_initcall("7", fn)



Linux-2.6.20的cs8900驱动分析(一)

一、初始化阶段

    网络初始化被调用的路径为:

init->do_basic_setup->do_initcalls->net_olddevs_init->ethif_probe2->probe_list2->cs89x0_probe->cs89x0_probe1

真是不容易啊,终于进到cs89x0_probe1了,在这里开始探测和初始化cs8900了。下面就按照这个顺序来说明网络驱动第一阶段的工作。注意:这里的调用顺序是将cs8900驱动编入内核所产生的,如果将cs8900驱动选为模块,这个路径:init->do_basic_setup->do_initcalls->net_olddevs_init->ethif_probe2->probe_list2也会执行。

1.1 init函数

我们知道当start_kernel函数完成后就会启动init进程执行,在真正的应用程序init进程(如busybox的/sbin/init)之前,Linux还需要执行一些初始化操作。init的代码可以在\init\main.c中找到,它的代码如下:

static int init(void * unused)
       {
               lock_kernel();
                ……                                                         //省略多cpu的初始化代码先
                do_basic_setup();                                   //我们所关注的初始化函数
                ……

        if (!ramdisk_execute_command)
                    ramdisk_execute_command = "/init";

      if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0)    {
                     ramdisk_execute_command = NULL;
                     prepare_namespace();                                       //挂接根文件系统     
             }
             ……

free_initmem();                                                       //释放初始化代码的空间

       unlock_kernel();

……                                                                      //这几段没看懂

 

       if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) //检查控制台console是否存在

              printk(KERN_WARNING "Warning: unable to open an initial console.\n");

……//这几段没看懂

       if (ramdisk_execute_command) {           //运行ramdisk_execute_command指定的init用户进程

              run_init_process(ramdisk_execute_command);

              printk(KERN_WARNING "Failed to execute %s\n",

                            ramdisk_execute_command);

       }

       ……

       if (execute_command) {       //判断在启动时是否指定了init参数,如果指定,此值将赋给execute_command

              run_init_process(execute_command);              //开始执行用户init进程,如果成功将不会返回。

              printk(KERN_WARNING "Failed to execute %s. Attempting "

                                   "defaults...\n", execute_command);

       }

//如果没有指定init启动参数,则查找下面的目录init进程,如果找到则不会返回

       run_init_process("/sbin/init");

       run_init_process("/etc/init");

       run_init_process("/bin/init");

       run_init_process("/bin/sh");

   //如果上面的程序都出错,则打印下面的信息,如果内核找到init进程,则程序不会指向到此处

       panic("No init found. Try passing init= option to kernel.");

}

1.2 do_basic_setup函数

在这里我们最关心的是do_basic_setup函数,顾名思义该函数的功能就是“做基本设置”,它的实现代码也在\init\main.c中。do_basic_setup()完成外设及其驱动程序的加载和初始化。该函数代码如下所示:

static void __init do_basic_setup(void)

{

       /* drivers will send hotplug events */

       init_workqueues();     //初始化工作队列

       usermodehelper_init(); //初始化khelper内核线程,还没弄清楚

 

       driver_init();           //初始化内核的设备管理架构需要的数据结构,很复杂,以后在谈这部分。

 

#ifdef CONFIG_SYSCTL

       sysctl_init();          //没搞懂

#endif

       do_initcalls();         //重点函数,初始化的主要工作就靠它了

}

1.3 do_ initcalls函数

       do_initcalls函数将会调用内核中所有的初始化函数,它的代码同样在\init\main.c中。do_initcalls函数调用其他初始化函数相当简洁,它的关键代码如下所示:

initcall_t *call;

for (call = __initcall_start; call < __initcall_end; call ) {

……

       result = (*call)();

……

       简洁归简洁,但这段代码是什么意思呢?这说来就话长了,最重要的应该是先了解Linux处理初始化的大体思想,由于Linux有很多部分需要初始化,每个部分都有自己的初始化函数,如果按照常理一个一个的调用未免显得冗长,而且也不便于扩展。那么Linux是怎么处理的呢?首先,Linux将各个部分的初始化函数编译到一个块内存区中,当初始化完了以后释放这块内存区,这就是init函数中free_initmem所要做的事。然后,再在另外的内存区中存放一些函数指针,让每个指针指向一个初始化函数。然后在do_initcalls中依次根据这些指针调用初始化函数。

上面一段就是Linux实现初始化的大体思想,下面我们看看它最终是怎么实现的。首先要了解的是__define_initcall宏,该宏的定义在\ include\linux\init.h中,它的原型如下所示:

#define __define_initcall(level,fn,id) static initcall_t __initcall_##fn##id __attribute_used__ \

       __attribute__((__section__(".initcall" level ".init"))) = fn

 

__define_initcall宏有三个参数,level表示初始化函数的级别,level值的大小觉得了调用顺序,level越小越先被调用,fn就是具体的初始化函数,id简单标识初始化函数,现在还没找到有什么用^_^。__define_initcall的功能为,首先声明一个initcall_t类型的函数指针__initcall_##fn##id,initcall_t的原型为:

typedef int (*initcall_t)(void);

该类型可简单理解为函数指针类型^_^。然后,让该函数指针指向fn。最后,通过编译器的编译参数将此指针放到指定的空间".initcall" level ".init"中,__attribute_used向编译器说明这段代码有用,即使在没用到的时候,编译器也不会警告。__attribute__的__section__参数表示该段代码放入什么内存区域中,也即指定编译到什么地方,编译参数更详细的地方可以查阅GCC文档,在gcc官方网站http://gcc.gnu.org/onlinedocs/中能找到各个版本的手册。这样说来还是比较抽象,下面举个例子来说明:

       假如有初始化函数init_foolish函数,现在使用__define_initcall宏向内核加入该函数。假如调用方式如下:

__define_initcall("0",init_foolish,1);

那么,__define_initcall宏首先申请一个initcall_t类型的函数指针__initcall_init_foolish1(注意替换关系),且使该指针指向了init_foolish,函数指针__initcall_init_foolish1被放到.initcall.0.init内存区域中,这个标志在连接时会用到。

       有了上面的基础知识,现在回到do_initcalls函数中,首先注意到是__initcall_start和__initcall_end,它们的作用就是界定了存放初始化函数指针区域的起始地址,也即从__initcall_start开始到__initcall_end结束的区域中存放了指向各个初始化函数的函数指针。换句话说,只要某段程序代码从__initcall_start开始依次调用函数指针,那么就可以完成各个部分的初始化工作,这显得十分优雅而且便于扩充,再看看do_initcalls,它何尝不是如此呢。这里还有一个有用的技巧就是__initcall_start和__initcall_end的原型是initcall_t型的数组,以后可以使用这种技巧^_^。

       现在我们知道了do_initcalls函数的实现原理,那么到底它调用了多少初始化函数呢?我们怎样才能知道呢?根据上面的分析,我们知道所有的初始化函数的指针都放在__initcall_start和__initcall_end区域期间,而函数指针与它指向的函数之间又有固定的关系,如上面的例子,初始化函数名为init_foolish,指向它的函数指针就是__initcall_init_foolish1,即在此函数加上前缀__initcall_和一个数字后缀,反之,从函数指针也可推出初始化函数名。有了这两个信息,我们就可以很方便的找个初始化函数。怎么找呢??首先打开Linux完后产生的System.map文件,然后找到__initcall_start和__initcall_end字符串,你会发现它们之间有很多类似于__initcall_xxx1这样的符号,这些符号就是我们需要的函数指针了,这样就可推出初始化函数的名字。比如,我们这里需要的函数指针__initcall_net_olddevs_init6,按照上面的名字规则,很容易推出它所指向的初始化函数名字是net_olddevs_init。

       得到了初始化函数的名字又怎么样呢?又不知道它在哪个文件里,不要着急!请打开你的浏览器登陆http://lxr.linux.no/ident网站,然后选择Linux版本和架构,然后可以搜索我们想要的信息。比如我输入net_olddevs_init,然后我就会得到该函数所在文件的相关信息。

1.4 net_olddevs_init函数

       我们知道net_olddevs_init函数在do_initcalls函数中被调用并执行,那么它到底要做什么呢?看看实现代码就知道了,它的实现代码可以在\drivers\net\Space.c中找到。对于网络驱动部分的主要实现代码如下:

static int __init net_olddevs_init(void){  

……

       int num;

       for (num = 0; num < 8; num)

              ethif_probe2(num);

       ……

}

这段代码就不用讲解了吧,嘿嘿!就是调用了8次ethif_probe2,赶快去看看ethif_probe2长什么样子。

1.5 ethif_probe2函数

       先看看该函数的实现代码,该代码也在\drivers\net\Space.c文件中。

static void __init ethif_probe2(int unit)

{

       unsigned long base_addr = netdev_boot_base("eth", unit);   // 由于ethif_probe2被net_olddevs_init调用了8次,

                                          // 所以unit的值为0~7,也即在这里可以注册eth0~eth7八个网络设备

       if (base_addr == 1)

              return;


       (void)(    probe_list2(unit, m68k_probes, base_addr == 0) &&

              probe_list2(unit, eisa_probes, base_addr == 0) &&

              probe_list2(unit, mca_probes, base_addr == 0) &&

              probe_list2(unit, isa_probes, base_addr == 0) &&

              probe_list2(unit, parport_probes, base_addr == 0));

}

       该函数首先调用netdev_boot_base所给的设备是否已经向内核注册,如果已注册netdev_boot_base返回1,随后推出ethif_probe2。如果设备没注册,则又调用函数probe_list2四次,每次传递的传输不同,注意到每次传递的第二个参数不同,这个参数也是相当重要的,这里拿isa_probes参数为例说明,因为这个参数与cs89x0_probe有关,isa_probes的定义也在\drivers\net\Space.c中,它的样子形如:

static struct devprobe2 isa_probes[] __initdata = {

……

#ifdef CONFIG_SEEQ8005

       {seeq8005_probe, 0},

#endif

#ifdef CONFIG_CS89x0

     {cs89x0_probe, 0},

#endif

#ifdef CONFIG_AT1700

       {at1700_probe, 0},

#endif

       {NULL, 0},

……

};

如果把cs8900的驱动选为非编译进内核,那么它的探测函数cs89x0_probe就不会存在于isa_probes数组中,所以在初始阶段就不能被调用。从上面的代码可以知道devprobe2类型至少包括两个域,至少一个域为函数指针,看看它的原型如下:

struct devprobe2 {

       struct net_device *(*probe)(int unit);                         //函数指针,指向探测函数

       int status;       /* non-zero if autoprobe has failed */

};

下面看看probe_list2函数是怎么表演的。

1.6 ethif_probe2函数

       对于ethif_probe2函数也没有什么需要说明的,它的主要任务是依次调用devprobe2类型的probe域指向的函数。他的实现代码同样在\drivers\net\Space.c中,它的关键代码如下:

static int __init probe_list2(int unit, struct devprobe2 *p, int autoprobe)

{

       struct net_device *dev;

       for (; p->probe; p ) {

           ……

              dev = p->probe(unit);

              ……

       }

……

}

1.7 cs89x0_probe函数

       从该函数起,真正开始执行与cs8900驱动初始化程序,该函数在\drivers\net\cs89x0.c文件实现。下面依次解释该函数。

 

struct net_device * __init cs89x0_probe(int unit)

{

       struct net_device *dev = alloc_etherdev(sizeof(struct net_local)); //该函数申请一个net_device+

//sizeof(struct net_local)的空间,net_local是cs8900驱动的私有数据空间。

       unsigned *port;

       int err = 0;

       int irq;

       int io;

      

       if (!dev)

              return ERR_PTR(-ENODEV);

       sprintf(dev->name, "eth%d", unit);                 //初始化dev->name域

       netdev_boot_setup_check(dev);                  //检查是否给定了启动参数,如果给定了启动参数,此函数将初始

//化dev的irq、base_addr、mem_start和mem_end域。

       io = dev->base_addr;                                 //io实际实质cs8900所占地址空间的起始地址,此地址为虚拟地址

       irq = dev->irq;

 

       if (net_debug)

              printk("cs89x0:cs89x0_probe(0x%x)\n", io);

//下面根据io的值调用cs89x0_probe1函数

       if (io > 0x1ff) {/* Check a single specified location. *///此段没搞懂,由于没给启动参数,这里也不会执行

       err = cs89x0_probe1(dev, io, 0);

       } else if (io != 0) { /* Don''''''''''''''''''''''''''''''''t probe at all. */

              err = -ENXIO;

       } else {

              for (port = netcard_portlist; *port; port ) {// netcard_portlist为unsigned int型数组,在cs89x0.c文件中定

//义,里面列出了cs8900可能占用空间的起始地址,这些地址

//将在cs89x0_probe1函数中用于向内核申请。

                     if (cs89x0_probe1(dev, *port, 0) == 0) // cs89x0_probe1探测成功就返回0

                            break;

                     dev->irq = irq;

              }

              if (!*port)

                     err = -ENODEV;

       }

       if (err)

              goto out;

       return dev;

out:

       free_netdev(dev);   //表示探测失败,这里就释放dev的空间,随后打印些消息

       printk(KERN_WARNING "cs89x0: no cs8900 or cs8920 detected. Be sure to disable PnP with SETUP\n");

       return ERR_PTR(err);

}

       从上面的程序清单可以看到该函数还没有真正的开始探测cs8900,实质的探测工作是让cs89x0_probe1完成的。在解释cs89x0_probe1之前先提一下网络驱动程序中非常重要的一些函数。内核需要一个数据结构来管理或者描述每个网络驱动程序,这个数据类型就是struct net_device,该数据类型包括很多域,详细的解释可以参见《Linux 设备驱动程序》一书中的描述,也可以参见源代码(在\include\linux\netdevice.h中,源码中也有详细的注解)。内核为了编程方便特地实现了函数alloc_netdev来完成对net_device的空间分配。那么alloc_etherdev函数主要针对以太网在alloc_netdev基础上封装的一个函数,它除了申请net_device空间外,还会初始化net_device的相关域。

Linux下网络子系统的初始化

首先,我的最初出发点是从init进程开始的,如果你的系统的引导之类的感兴趣,那请在白云黄鹤的Linux版上查找相关资料或去其它地方查找.然后我的Code也是基于单CPU的机器,SMP相关的话请参考其它的资料.分析的代码是2.6.18+
 
一.init进程与网络子系统的第一个函数.  
       init进程相关的知识相信是总所周之,这就不提了(init进程是所有进程之父,使用ps -aux可以查看init的进程号为1)
         init进程的入口函数即为init()函数,定义于init/main.c中。
    1.init()函数调用do_basic_setup()函数完成最后的初始化工作。
    2.调用free_initmem()把内核中标记有__init、__initfunc、__initdata的程序代码和数据所占据的内存全部释放。
    3.调用open("/dev/console", O_RDWR, 0)打开控制台设备。同时调用复制标准输出设备和标准出错设备的描述符。
    4.调用execve()执行init程序,该程序完成各种脚本的执行。
    
    好了,我们的故事就此开始:),来看看网络子系统是如何登上历史的舞台,继续自己的辉煌之旅.
    
    前面说道了do_basic_setup(),我们的故事,其实真真是从它开始的,不过开头似乎有些隐晦,但只要认真看来,紧抓蛛丝马迹,自然抽丝剥茧不再话下:)
    
do_basic_setup()[init/main.c]
    |--------do_initcalls()[init/main.c]
    
我们贴出do_initcalls()的全部代码,以备分析:

__setup("initcall_debug", initcall_debug_setup);
struct task_struct *child_reaper = &init_task;
extern initcall_t __initcall_start[], __initcall_end[];

static void __init do_initcalls(void)
{
    .........
    for (call = __initcall_start; call < __initcall_end; call++) {
        char *msg = NULL;
        char msgbuf[40];
        int result;

        if (initcall_debug) {
            printk("Calling initcall 0x%p", *call);
            print_fn_descriptor_symbol(": %s()",
                    (unsigned long) *call);
            printk("\n");
        }
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       {1}:需要注意的代码
        result = (*call)();
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        if (result && result != -ENODEV && initcall_debug) {
            sprintf(msgbuf, "error code %d", result);
            msg = msgbuf;
        }
        if (preempt_count() != count) {
            msg = "preemption imbalance";
            preempt_count() = count;
        }
        if (irqs_disabled()) {
            msg = "disabled interrupts";
            local_irq_enable();
        }
        if (msg) {
            printk(KERN_WARNING "initcall at 0x%p", *call);
            print_fn_descriptor_symbol(": %s()",
                    (unsigned long) *call);
            printk(": returned with %s\n", msg);
        }
    }

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

上面的Code有一个很是奇怪的extern initcall_t __initcall_start[], __initcall_end[];生命,仿佛initcall_t只是个一般的数据类型而已,但真的是这样吗?

grep一下:
typedef (*initcall_t)(void);[include/init.h]
原来如此,不就一函数指针而已么?犯的着吗?但是,后面的变量__initcall_start[], __initcall_end[];你有仔细的查找过么?

恩,那就查找一番,原来,居然在[arch/i386/kernel/vmlinux.lds.S],如果这时你进去看了这个文件依然是雾水一头,那看来只能出杀手锏了:).(请参考网上的Using LD的中文译文)

###########################################################################
关键时刻,除了的确是语法上的东西,还是考虑GNU的哪些工具:),还有就是,万千万千不要忘了,就算Kernel,它也是个software,自然,少不了:
预处理->编译->链接->运行,在这方面来说,与其它程序并无二置
###########################################################################

好了,我们继续,查看[include/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)
结合上面的ld脚本,基本可以开始猜测了,原来do_initcalls()中的:
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       {1}:需要注意的代码
        result = (*call)();
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        
是按照声明为core_initcall()->late_initcall()这样的顺序来调用这些函数啊(看到声明对应的1,2,3...,7可以猜测啊)

你或许又开始发问了,这和网络子系统初始化,有关系么?
怎么没有,请参考[net/socket.c]
static int __init sock_init(void)
{
    /*
     *    Initialize sock SLAB cache.
     */
    sk_init();

    /*
     *    Initialize skbuff SLAB cache
     */
    skb_init();

    /*
     *    Initialize the protocols module.
     */
    init_inodecache();
    register_filesystem(&sock_fs_type);
    sock_mnt = kern_mount(&sock_fs_type);

    /* The real protocol initialization is performed in later
     * initcalls.
     */
#ifdef CONFIG_NETFILTER
    netfilter_init();
#endif

    return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{2}:和我们上面的叙述是不是可以联系上了:),说了这久,原来只是为了说这一点啊:)
core_initcall(sock_init);    /* early initcall */
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

因此啊,我可以断定sock_init()[net/socket.c]是网络子系统初始化的第一个函数,不信,可以在net目录里面"grep core_initcall * -rn",看声明为core_initcall函数有几个,然后采用排除法:)

    恩,可以喝口水了,是否有拨云见日的感觉:),可打铁得趁热,继续吧:)
    
二. 网络子系统的初始化
    在上面得分析中,既然都见到了第一个与网络子系统相关得函数,后面自然要好过一些了.
    
    在上面得Code中可以看到几点:
sock_init()[net/socket.c]
    |--------sk_init()[net/core/sock.c]
    |--------skb_init()[net/core/skbuff.c]
    |--------init_inodecache()    |#|
    |--------register_filesystem()    |#|
    |--------kern_mount()        |#|
    |--------netfilter_init()[net/netfilter/core.c]
    
简单说明一下这几个函数:
    1.函数sk_init()
这个函数初始化slab cache给struct sock类型的对象使用。
if (num_physpages <= 4096) {            /* 4096=16MB 在x86 */
        sysctl_wmem_max = 32767;         /* 32767=32Kb-1    */
        sysctl_rmem_max = 32767;
        sysctl_wmem_default = 32767;
        sysctl_rmem_default = 32767;
} else if (num_physpages >= 131072) {    /* 131072=512MB 在x86 */
        sysctl_wmem_max = 131071;          /* 131071=128Kb-1     */
        sysctl_rmem_max = 131071;
}
这设置了读或写的buffer最大值,如果物理页数目(num_physpages)在16MB~512MB之间,默认分配为(net/core/sock.c):
/* Run time adjustable parameters. */
__u32 sysctl_wmem_max = SK_WMEM_MAX;
__u32 sysctl_rmem_max = SK_RMEM_MAX;
__u32 sysctl_wmem_default = SK_WMEM_MAX;
__u32 sysctl_rmem_default = SK_RMEM_MAX;
其中,SK_WMEM_MAX和SK_RMEM_MAX均在skbuff.h中设置为65535b(64Kb-1)

    2.函数skb_init()
这个函数创建struct sk_buff的对象的cache(参考sk_init()很明显)

    3.注册sock文件系统(详细内容参考文件系统相关文档).
    
    4.Netfilter框架初始化