1. 程式人生 > >linux核心原始碼中常見巨集定義

linux核心原始碼中常見巨集定義

1. gcc的__attribute__編繹屬性

要了解Linux Kernel程式碼的分段資訊,需要了解一下gcc的__attribute__的編繹屬性,__attribute__主要用於改變所宣告或定義的函式或資料的特性,它有很多子項,用於改變作用物件的特性。比如對函式,noline將禁止進行內聯擴充套件、noreturn表示沒有返回值、pure表明函式除返回值外,不會通過其它(如全域性變數、指標)對函式外部產生任何影響。但這裡我們比較感興趣的是對程式碼段起作用子項section。

__attribute__的section子項的使用格式為:

__attribute__((section("section_name")))

其作用是將作用的函式或資料放入指定名為"section_name"輸入段。

輸入段和輸出段是相對於要生成最終的elf或binary時的Link過程說的,Link過程的輸入大都是由原始碼編繹生成的目標檔案.o,那麼這些.o檔案中包含的段相對link過程來說就是輸入段,而Link的輸出一般是可執行檔案elf或庫等,這些輸出檔案中也包含有段,這些輸出檔案中的段就叫做輸出段。輸入段和輸出段本來沒有什麼必然的聯絡,是互相獨立,只是在Link過程中,Link程式會根據一定的規則(這些規則其實來源於Link Script),將不同的輸入段重新組合到不同的輸出段中,即使是段的名字,輸入段和輸出段可以完全不同。

其用法舉例如下:

int  var __attribute__((section(".xdata"))) = 0;

這樣定義的變數var將被放入名為.xdata的輸入段,(注意:__attribute__這種用法中的括號好像很嚴格,這裡的幾個括號好象一個也不能少。)

static int __attribute__((section(".xinit"))) functionA(void)

{

 .....
}

這個例子將使函式functionA被放入名叫.xinit的輸入段。

需要著重注意的是,__attribute__的section屬性只指定物件的輸入段,它並不能影響所指定物件最終會放在可執行檔案的什麼段。

2. Linux Kernel原始碼中與段有關的重要巨集定義

A. 關於__init、__initdata、__exit、__exitdata及類似的巨集

 開啟Linux Kernel原始碼樹中的檔案:include/init.h,可以看到有下面的巨集定議:

#define __init  __attribute__ ((__section__ (".init.text")))  __cold

#define __initdata    __attribute__ (( __section__ (".init.data")))

#define __exitdata   __attribute__ (( __section__ (".exit.data")))

#define __exit_call  __attribute_used__ __attribute__ (( __section__ (".exitcall.exit")))

#define __init_refok  oninline __attribute__ ((__section__ (".text.init.refok")))

#define __initdata_refok __attribute__ ((__section__ (".data.init.refok")))

#define __exit_refok noinline __attribute__ ((__section__ (".exit.text.refok")))

.........

#ifdef MODULE

#define __exit  __attribute__ (( __section__ (".exit.text"))) __cold

#else

#define __exit __attribute_used__ __attribute__ ((__section__ (".exit.text"))) __cold

#endif

 對於經常寫驅動模組或翻閱Kernel原始碼的人,看到熟悉的巨集了吧:__init, __initdata, __exit, __exitdata。

__init 巨集最常用的地方是驅動模組初始化函式的定義處,其目的是將驅動模組的初始化函式放入名叫.init.text的輸入段。對於__initdata來說,用於資料定義,目的是將資料放入名叫.init.data的輸入段。其它幾個巨集也類似。另外需要注意的是,在以上定意中,用__section__代替了section。還有其它一些類似的巨集定義,這裡不一一列出,其作用都是類似的。

B. 關於initcall的一些巨集定義

在該檔案中,下面這條巨集定議更為重要,它是一條可擴充套件的巨集:

#define  __define_initcall(level,fn,id) /

     static initcall_t __initcall_##fn##id  __attribute_used__ /

    __attribute__ ((__section__(".initcall" level ".init"))) = fn

這條巨集帶有3個引數:level,fn, id,分析該巨集可以看出:

 1.其用來定義型別為initcall_t的static函式指標,函式指標的名稱由引數fn和id決定:__initcall_##fn##id,這就是函式指標的名稱,它其實是一個變數名稱。從該名稱的定義方法我們其學到了巨集定義的一種高階用法,即利用巨集的引數產生名稱,這要藉助於"##"這一符號組合的作用。

 2.  這一函式指標變數放入什麼輸入段呢,請看__attribute__ ((__section__ (".initcall" levle ".init"))),輸入段的名稱由level決定,如果level="1",則輸入段是.initcall1.init,如果level="3s",則輸入段是.initcall3s.init。這一函式指標變數就是放在用這種方法決定的輸入段中的。

 3. 這一定義的函式指標變數的初始值是什麼叫,其實就是巨集引數fn,實際使用中,fn其實就是真實定義好的函式。

該巨集定義並不直接使用,請看接下來的這些巨集定義:

#define pure_initcall(fn)  __define_initcall("0",fn,0)
#define core_initcall(fn)  __define_initcall("1",fn,1)
#define core_initcall_sync(fn)  __define_initcall("1s",fn,1s)
#define postcore_initcall(fn)  __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn)  __define_initcall("3",fn,3)
#define arch_initcall_sync(fn)  __define_initcall("3s",fn,3s)
#define subsys_initcall(fn)  __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn)   __define_initcall("5",fn,5)
#define fs_initcall_sync(fn)  __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn)  __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn)  __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn)  __define_initcall("7",fn,7)
#define late_initcall_sync(fn)  __define_initcall("7s",fn,7s)

這些巨集定義出來是為了方便的使用__define_initcall巨集定義的,上面每條巨集第一次使用時都會產生一個新的輸入段。

接下來還有一條

#define __initcall(fn) device_initcall(fn)
這一條其實只是定義了另一個別名,即平常使用的__initcall其實就是這兒的device_initcall,用它定義的函式指定位於段.initcall6.init中。

C. __setup巨集的來源及使用

__setup這條巨集在Linux Kernel中使用最多的地方就是定義處理Kernel啟動引數的函式及資料結構,請看下面的巨集定義:

#define __setup_param(str, unique_id, fn, early)   /
 static char __setup_str_##unique_id[] __initdata __aligned(1) = str; /
 static struct obs_kernel_param __setup_##unique_id /
  __used __section(.init.setup)   /
  __attribute__((aligned((sizeof(long))))) /
  = { __setup_str_##unique_id, fn, early }


#define __setup(str, fn)     /
 __setup_param(str, fn, fn, 0)

使用Kernel中的例子分析一下這兩條定義:

__setup("root=",root_dev_setup);

這條語句出現在init/do_mounts.c中,其作用是處理Kernel啟動時的像root=/dev/mtdblock3之類的引數的。

分解一下這條語句,首先變為:

__setup_param("root=",root_dev_setup,root_dev_setup,0);

繼續分解,將得到下面這段代嗎:

static char __setup_str_root_dev_setup_id[] __initdata __aligned(1) = "root=";
static struct obs_kernel_param __setup_root_dev_setup_id
  __used __section(.init.setup)
  __attribute__((aligned((sizeof(long)))))
  = { __setup_str_root_dev_setup_id, root_dev_setup, 0 };


這段程式碼定義了兩個變數:字元陣列變數__setup_str_root_dev_setup_id,其初始化內容為"root=",由於該變數用__initdata修飾,它將被放入.init.data輸入段;另一變數是結構變數__setup_root_dev_setup_id,其型別為struct obs_kernel_param, 該變理被放入輸入段.init.setup中。結構struct struct obs_kernel_param也在該檔案中定義如下:

struct obs_kernel_param {
 const char *str;
 int (*setup_func)(char *);
 int early;
};

變數__setup_root_dev_setup_id的三個成員分別被初始化為:

__setup_str_root_dev_setup_id --> 前面定義的字元陣列變數,初始內容為"root="。

root_dev_setup --> 通過巨集傳過來的處理函式。

0 -->常量0,該成員的作用以後分析。

現在不難想像核心啟動時怎麼處理啟動引數的了:通過__setup巨集定義obs_kernel_param結構變數都被放入.init.setup段中,這樣一來實際是使.init.setup段變成一張表,Kernel在處理每一個啟動引數時,都會來查詢這張表,與每一個數據項中的成員str進行比較,如果完全相同,就會呼叫該資料項的函式指標成員setup_func所指向的函式(該函式是在使用__setup巨集定義該變數時傳入的函式引數),並將啟動引數如root=後面的內容傳給該處理函式。

unistech_train 說:
這些巨集包括 __init、__initdata、__initfunc()、asmlinkage、ENTRY()、FASTCALL()等等。它們的定義主要位於 Include/linux/linkage.h和 include/asm-i386/Init.h以及其他一些.h檔案中。

  1) __init位置:include/asm-i386/Init.h

  定義:#define __init __attribute__ ((__section__ (".text.init")))

  註釋:這個標誌符和函式宣告放在一起,表示gcc編譯器在編譯的時候需要把這個函式放.text.init section中,而這個section在核心完成初始化之後,會被釋放掉。

  舉例:asmlinkage void __init start_kernel(void){...}

  2) __initdata

  位置:include/asm-i386/Init.h

  定義:#define __initdata __attribute__ ((__section__ (".data.init")))

  註釋:這個標誌符和變數宣告放在一起,表示gcc編譯器在編譯的時候需要把這個變數放在.data.init section中,而這個section在核心完成初始化之後,會被釋放掉。

  舉例:static struct kernel_param raw_params[] __initdata = {....}

  3) __initfunc()

  位置:include/asm-i386/Init.h

  定義: #define __initfunc(__arginit) /

  __arginit __init; /

  __arginit

  註釋: 這個巨集用來定義一個 __init 函式。

  舉例: __initfunc(void mem_init(unsigned long start_mem, unsigned long e

  nd_mem)) {....}

  4) asmlinkage

  位置:Include/linux/linkage.h

  定義:#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

  註釋:這個標誌符和函式宣告放在一起,告訴gcc編譯器該函式不需要通過任何暫存器來傳遞引數,引數只是通過堆疊來傳遞。

  舉例:asmlinkage void __init start_kernel(void){...}

  5) ENTRY()

  位置:Include/linux/linkage.h

  定義: #define ENTRY(name) /

  .globl SYMBOL_NAME(name); /

  ALIGN; /

  SYMBOL_NAME_LABEL(name)

  註釋: 將name宣告為全域性,對齊,並定義為標號。

  舉例: ENTRY(swapper_pg_dir)

  .long 0x00102007

  .fill __USER_PGD_PTRS-1,4,0

  /* default: 767 entries */

  .long 0x00102007

  /* default: 255 entries */

  .fill __KERNEL_PGD_PTRS-1,4,0

  等價於

  .globl swapper_pg_dir

  .align 16,0x90

  /* if i486 */

  swapper_pg_dir:

  .long 0x00102007

  .fill __USER_PGD_PTRS-1,4,0

  /* default: 767 entries */

  .long 0x00102007

  /* default: 255 entries */

  .fill __KERNEL_PGD_PTRS-1,4,0

  6) FASTCALL()

  位置:Include/linux/kernel.h

  定義:#define FASTCALL(x) x __attribute__((regparm(3)))

  註釋:這個標誌符和函式宣告放在一起,帶regparm(3)的屬性宣告告訴gcc編譯器這個函式可以通過暫存器傳遞多達3個的引數,這3個暫存器依次為EAX、EDX 和 ECX。更多的引數才通過堆疊傳遞。這樣可以減少一些入棧出棧操作,因此呼叫比較快。

  舉例:extern void FASTCALL(__switch_to(struct task_struct *prev, struct t

  ask_struct *next));

  這個例子中,prev將通過eax,next通過edx傳遞

  7)_sched 存在於kernel/sched.h檔案中

  Attach to any functions which should be ignored in wchan output

  #define _sched _attribute_ ((_section_(".sched.text")))