__attribute__編譯屬性---section

要了解Linux Kernel代碼的分段信息,需要了解一下gcc的__attribute__的編繹屬性,__attribute__主要用於改變所聲明或定義的函數或數據的特性,它有很多子項,用於改變作用對象的特性。比如對函數,noline將禁止進行內聯擴展、noreturn表示沒有返回值、pure表明函數除返回值外,不會通過其它(如全局變量、指針)對函數外部產生任何影響。但這裏我們比較感興趣的是對代碼段起作用子項section。

編譯器的關鍵字 __attribute__ 用來指定變量或結構位域的特殊屬性。關鍵字後的

雙括弧中的內容是屬性說明。下面是目前支持的變量屬性:

• address (addr)

• aligned (alignment)

• boot

• deprecated

• fillupper

• far

• mode (mode)

• near

• noload

• packed

• persistent

• reverse (alignment)

• section ("section-name")

• secure

• sfr (address)

• space (space)

• transparent_union

• unordered

• unused

• weak

__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。這一函數指針變量就是放在用這種方法決定的輸入段中的



文/three_eyelid(簡書作者)
原文鏈接:http://www.jianshu.com/p/cefcc59e7155
著作權歸作者所有,轉載請聯繫作者獲得授權,並標註“簡書作者”。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章