Linux基础系列-Kernel 初始化宏

在看linux代码时,很多驱动的init函数里面都有类似core_initcall,subsys_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。

 

 

此外,还有一个重要的宏,在很多BSP中使用(以S3C为例):

linux2.6.18内核,在Mach-s3c2410.c文件中,有如下的宏定义:
 

MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch to SMDK2410 */
 /* Maintainer: Jonas Dietsche */
 .phys_io = S3C2410_PA_UART,
 .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
 .boot_params = S3C2410_SDRAM_PA + 0x100,
 .map_io = smdk2410_map_io,
 .init_irq = s3c24xx_init_irq,
 .init_machine = smdk_machine_init,
 .timer = &s3c24xx_timer,
MACHINE_END

 
MACHINE_START定义在include/asm-arm/mach/arch.h中

#define MACHINE_START(_type,_name) /
static const struct machine_desc __mach_desc_##_type /
 __attribute_used__ /
 __attribute__((__section__(".arch.info.init"))) = { /
 .nr = MACH_TYPE_##_type, /
 .name = _name,
#define MACHINE_END /
};

 
将前面定义的MACHINE_START展开后得到,

static const struct machine_desc __mach_desc_SMDK2410
 __attribute_used__
 __attribute__((__section__(".arch.info.init"))) = {
 .nr = MACH_TYPE_SMDK2410, /* architecture number */
 .name = "SMDK2410", /* architecture name */
 /* Maintainer: Jonas Dietsche */
 .phys_io = S3C2410_PA_UART, /* start of physical io */
 .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
 .boot_params = S3C2410_SDRAM_PA + 0x100, /* tagged list */
 .map_io = smdk2410_map_io, /* IO mapping function */
 .init_irq = s3c24xx_init_irq,
 .init_machine = smdk_machine_init,
 .timer = &s3c24xx_timer,
}

 
MACH_TYPE_SMDK2410定义在arch/include/asm-arm/mach-types.h内,值为193.
/* arch/include/asm-arm/mach-types.h */
#define MACH_TYPE_SMDK2410             193
这个值是机器的类型值,编译时由arch/arm/tool/mach-types里面定义的数据生成的。
/* arch/arm/tool/mach-types */
smdk2410  ARCH_SMDK2410  SMDK2410  193

由上发现,MACHINE_START主要是定义了"struct machine_desc"的类型,放在 section(".arch.info.init"),是初始化数据,Kernel 起来之后将被丢弃。

各个成员函数在不同时期被调用:
1. .init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 调用,放在 arch_initcall() 段里面,会自动按顺序被调用。
2. init_irq在start_kernel() --> init_IRQ() --> init_arch_irq() 被调用
3. map_io 在 setup_arch() --> paging_init() --> devicemaps_init()被调用
其他主要都在 setup_arch() 中用到。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值