do_initcalls 的原理

简介: 前言    宏定义__define_initcall(level,fn)对于内核的初始化很重要,它指示    编译器在编译的时候,将一系列初始化函数的起始地址值按照一定的顺序    放在一个section中。
前言


   宏定义__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
             {
                 ....
                 (*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://m.blog.csdn.net/blog/Eric_tao/5150930


相关文章
|
存储 Kubernetes 负载均衡
k8s是什么以及它的原理和如何去使用它?
k8s是什么以及它的原理和如何去使用它?
|
7月前
|
机器学习/深度学习 数据采集 人工智能
奥卡姆剃刀原理
奥卡姆剃刀原理“【5月更文挑战第17天】”
78 4
|
4月前
|
数据采集 自然语言处理 算法
Sekiro原理
这篇文章介绍了SEKIRO,一个多语言的、分布式的、与网络拓扑无关的服务发布平台,它支持通过编写不同语言的handler将功能发布到中心API市场,并通过RPC方式调用,特别适用于受限环境下的功能外放和服务提供。
61 0
|
7月前
|
编译器 C++ 容器
C++模板的原理及使用
C++模板的原理及使用
|
Kubernetes 监控 调度
k8s 自身原理 4
k8s 自身原理 4
|
Kubernetes 监控 Cloud Native
k8s 自身原理 3
k8s 自身原理 3
|
存储 Kubernetes API
k8s 自身原理 1
k8s 自身原理 1
|
存储 缓存 算法
四、深入剖析【离屏渲染】原理
深入剖析【离屏渲染】原理
530 0
四、深入剖析【离屏渲染】原理
|
监控 Dubbo 搜索推荐
ShutdownHook原理
有了ShutdownHook我们可以 在进程结束时做一些善后工作,例如释放占用的资源,保存程序状态等 为优雅(平滑)发布提供手段,在程序关闭前摘除流量
309 0
ShutdownHook原理
说了这么多次 I/O,可你知道其中的原理么(二)
现在让我们转向对 I/O 软件的研究,I/O 软件设计一个很重要的目标就是设备独立性(device independence)。啥意思呢?这意味着我们能够编写访问任何设备的应用程序,而不用事先指定特定的设备。
说了这么多次 I/O,可你知道其中的原理么(二)