Linux基礎系列-Kernel 初始化宏

來源:互聯網
上載者:User

在看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() 中用到。
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.