__define_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  

***********************************************************************

假如您希望某個初始化函數在內核初始化階段就被調用,那麼您
就應該使用宏__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)
_________________
爲樂助人

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章