__define_initcall(level,fn)和do_initcalls()的妙用

本文深入探讨了Linux内核初始化过程中的__define_initcall宏作用,详细解释了如何通过该宏将初始化函数按照特定顺序放置在不同的section中,确保内核各部分按需正确初始化。同时,介绍了do_initcalls函数如何遍历这些section并调用相应的初始化函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        __define_initcall(level,fn)将一系列初始化函数的起始地址值按照一定的顺序放在一个section中。在内核初始化阶段,do_initcalls() 将按顺序从section中以函数指针的形式取出这些函数的起始地址,来依次完成相应的初始化。由于内核某些部分的初始化需要依赖于其他某些部分的初始化的完成,因此这个顺序排列常常非常重要。
一、分析 __define_initcall(level,fn) 宏定义
   1、 这个宏的定义位于inlclude\linux\init.h中:

typedef int (*initcall_t)(void);     
#define __define_initcall(level,fn)   \
     static initcall_t __initcall_##fn  \
     __attribute__((__section__(".initcall" level ".init"))) \
     = fn

      属性 __attribute__((__section__())) 则表示把对象放在一个这个由括号中的名称所指代的section中并且会取消对齐
      所以这个宏定义的的含义是:

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

  2、 举例:__define_initcall(6, pci_init)

         上述宏调用的含义是:

  • 声明一个函数指针__initcall_pic_init = pci_init;
  • 这个指针变量__initcall_pic_init 需要放置到名称为 .initcall6.init的section中( 其实质就是将 这个函数pic_init的首地址放置到了这个section中)。
  • 这个宏一般并不直接使用,而是被定义成下述其他更简单的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中,
       依次类推。

二、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。

三、 do_initcalls()调用

        内核初始化函数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的头和尾。

四、 归纳之

    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。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值