所有的內(nèi)核代碼,基本都包含了include/linux/compile.h這個文件,所以它是基礎(chǔ),涵蓋了分析內(nèi)核所需要的一些列編譯知識,現(xiàn)在就分析分析這個文件里的代碼:
#ifndef __LINUX_COMPILER_H
#define __LINUX_COMPILER_H
#ifndef __ASSEMBLY__
首先印入眼簾的是對__ASSEMBLY__這個宏的判斷,這個變量實(shí)際是在編譯匯編代碼的時候,由編譯器使用-D這樣的參數(shù)加進(jìn)去的,gcc會把這個宏定義為1。用在這里,是因?yàn)閰R編代碼里,不會用到類似于__user這樣的屬性(關(guān)于 __user這樣的屬性是怎么回子事后面會提到),因?yàn)檫@樣的屬性是在定義函數(shù)參數(shù)的時候加的,這樣避免不必要的宏在編譯匯編代碼時候的引用。
#ifdef __CHECKER__
接下來是一個對__CHECKER__這個宏的判斷,這里需要講的東西比較多,是本文的重點(diǎn)。
????????
當(dāng)編譯內(nèi)核代碼的時候,使用make C=1或C=2的時候,會調(diào)用一個叫Sparse的工具,這個工具對內(nèi)核代碼進(jìn)行檢查,怎么檢查呢,就是靠對那些聲明過Sparse這個工具所能識別的特性的內(nèi)核函數(shù)或是變量進(jìn)行檢查。在調(diào)用Sparse這個工具的同時,在Sparse代碼里,會加上#define __CHECKER__ 1的字樣。換句話說,就是,如果使用Sparse對代碼進(jìn)行檢查,那么內(nèi)核代碼就會定義__CHECKER__宏,否則就不定義。具體解釋請?jiān)L問:http://linux.die.net/man/1/sparse
例如:
# define __user? __attribute__((noderef, address_space(1)))
??????
這個宏是重點(diǎn),用來檢查是否屬于用戶空間!這里就能看出來,類似于__attribute__((noderef, address_space(1)))這樣的屬性就是Sparse這個工具所能識別的了。
???????
其他的那些個屬性是用來檢查什么的呢,我一個個地做介紹。
__user 這個特性,即__attribute__((noderef, address_space(1))),是用來修飾一個變量的,這個變量必須是非解除參考(__attribute__((noderef))——no dereference)的,即這個變量地址必須是有效的,而且變量所在的地址空間必須是1(__attribute__((address_space(1)))),即用戶程序空間的。這里Sparse工具把程序空間分成了3個部分,0表示normal space,即普通地址空間,對內(nèi)核代碼來說,當(dāng)然就是內(nèi)核空間地址了。1表示用戶地址空間,這個不用多講,還有一個2,表示是設(shè)備地址映射空間,例如硬件設(shè)備的寄存器在內(nèi)核里所映射的地址空間。
所以在內(nèi)核函數(shù)里,有一個copy_to_user的函數(shù)(我們在系統(tǒng)調(diào)用博文中會詳細(xì)介紹),函數(shù)的參數(shù)定義就使用了這種方式。當(dāng)然,這種特性檢查,只有當(dāng)機(jī)器上安裝了Sparse這個工具,而且進(jìn)行了編譯的時候調(diào)用,才能起作用的。
# define __kernel /* default address space */
根據(jù)定義,就是檢查是否處于內(nèi)核空間。其為默認(rèn)的地址空間,即0,我想定義成__attribute__((noderef, address_space(0)))也是沒有問題的。
# define __safe? __attribute__((safe))
這個定義在sparse里也有,內(nèi)核代碼是在2.6.6-rc1版本變到2.6.6-rc2的時候被Linus加入的,原因是這樣的:
有人發(fā)現(xiàn)在代碼編譯的時候,編譯器對變量的檢查有些苛刻,導(dǎo)致代碼在編譯的時候老是出問題(我這里沒有去檢查是編譯不通過還是有警告信息,因?yàn)楝F(xiàn)在的編譯器已經(jīng)不是當(dāng)年的編譯器了,代碼也不是當(dāng)年的代碼)。比如說這樣一個例子,
?int test( struct a * a, struct b * b, struct c * c ) {
? return a->a + b->b + c->c;
?}
這個編譯的時候會有問題,因?yàn)闆]有檢查參數(shù)是否為空,就直接進(jìn)行調(diào)用。但是呢,在內(nèi)核里,有好多函數(shù),當(dāng)它們被調(diào)用的時候,這些個參數(shù)必定不為空,所以根本用不著去對這些個參數(shù)進(jìn)行非空的檢查,所以就增加了一個__safe的屬性,如果這樣聲明變量,
?int test( struct a * __safe a, struct b * __safe b, struct c * __safe c ) {
? return a->a + b->b + c->c;
?}
編譯就沒有問題了。
不過到目前為止,在現(xiàn)在的代碼里沒有發(fā)現(xiàn)有使用__safe這個定義的地方,不知道是不是編譯器現(xiàn)在已經(jīng)支持這種特殊的情況了,所以就不用再加這樣的代碼了。
# define __force __attribute__((force))
表示所定義的變量類型是可以做強(qiáng)制類型轉(zhuǎn)換的,在進(jìn)行Sparse分析的時候,是不用報告警信息的。
# define __nocast __attribute__((nocast))
這里表示這個變量的參數(shù)類型與實(shí)際參數(shù)類型一定得對得上才行,要不就在Sparse的時候生產(chǎn)告警信息。
# define __iomem __attribute__((noderef, address_space(2)))
這個定義與__user一樣,不過這里的變量地址需要在設(shè)備地址映射空間。
# define __acquires(x) __attribute__((context(x,0,1)))
# define __releases(x) __attribute__((context(x,1,0)))
這是一對相互關(guān)聯(lián)的函數(shù)定義,第一句表示參數(shù)x在執(zhí)行之前,引用計(jì)數(shù)必須為0,執(zhí)行后,引用計(jì)數(shù)必須為1,第二句則正好相反,這個定義是用在修飾函數(shù)定義的變量的。
# define __acquire(x) __context__(x,1)
# define __release(x) __context__(x,-1)
這是一對相互關(guān)聯(lián)的函數(shù)定義,第一句表示要增加變量x的計(jì)數(shù),增加量為1,第二句則正好相反,這個是用來函數(shù)執(zhí)行的過程中。
以上四句如果在代碼中出現(xiàn)了不平衡的狀況,那么在Sparse的檢測中就會報警。當(dāng)然,Sparse的檢測只是一個手段,而且是靜態(tài)檢查代碼的手段,所以它的幫助有限,有可能把正確的認(rèn)為是錯誤的而發(fā)出告警。要是對以上四句的意思還是不太了解的話,請?jiān)谠创a里搜一下相關(guān)符號的用法就能知道了。這第一組與第二組,在本質(zhì)上,是沒什么區(qū)別的,只是使用的位置上,有所區(qū)別罷了。
# define __cond_lock(x,c) ((c) ? ({ __acquire(x); 1; }) : 0)
這句話的意思就是條件鎖。當(dāng)c這個值不為0時,則讓計(jì)數(shù)值加1,并返回值為1。不過這里我有一個疑問,有一個__cond_lock定義,但沒有定義相應(yīng)的__cond_unlock,那么在變量的釋放上,就沒辦法做到一致。而且spin_trylock()這個函數(shù)的定義,它就用了 __cond_lock,而且里面又用了_spin_trylock函數(shù),在_spin_trylock函數(shù)里,再經(jīng)過幾次調(diào)用,就會使用到 __acquire函數(shù),這樣的話,相當(dāng)于一個操作,就進(jìn)行了兩次計(jì)算,會導(dǎo)致Sparse的檢測出現(xiàn)告警信息,經(jīng)過寫代碼進(jìn)行實(shí)驗(yàn),驗(yàn)證了我的判斷,確實(shí)會出現(xiàn)告警信息,如果我寫兩遍unlock指令,就沒有告警信息了,但這是與程序的運(yùn)行是不一致的。
extern void __chk_user_ptr(const volatile void __user *);
extern void __chk_io_ptr(const volatile void __iomem *);
這兩句比較有意思。只是定義了函數(shù),但是代碼里沒有函數(shù)的實(shí)現(xiàn)。這樣做的目的,就是在進(jìn)行Sparse的時候,讓Sparse給代碼做必要的參數(shù)類型檢查,在實(shí)際的編譯過程中,并不需要這兩個函數(shù)的實(shí)現(xiàn)。
#define notrace __attribute__((no_instrument_function))
這一句,是定義了一個屬性,這個屬性可以用來修飾一個函數(shù),指定這個函數(shù)不被跟蹤。在gcc編譯器里面,實(shí)現(xiàn)了一個非常強(qiáng)大的功能,如果在編譯的時候把一個相應(yīng)的選擇項(xiàng)打開,那么就可以在執(zhí)行完程序的時候,用一些工具來顯示整個函數(shù)被調(diào)用的過程,這樣就不需要讓程序員手動在所有的函數(shù)里一點(diǎn)點(diǎn)添加能顯示函數(shù)被調(diào)用過程的語句,這樣耗時耗力,還容易出錯。那么對應(yīng)在應(yīng)用程序方面,可以使用Graphviz這個工具來進(jìn)行顯示,至于使用說明與軟件實(shí)現(xiàn)的原理可以自己在網(wǎng)上查一查,很容易查到。對應(yīng)于內(nèi)核,因?yàn)閮?nèi)核一直是在運(yùn)行階段,所以就不能使用這套東西了,內(nèi)核是在自己的內(nèi)部實(shí)現(xiàn)了一個ftrace的機(jī)制,編譯內(nèi)核的時候,如果打開這個選項(xiàng),那么通過掛載一個debugfs的文件系統(tǒng)來進(jìn)行相應(yīng)內(nèi)容的顯示,具體的操作步驟,可以參看內(nèi)核源碼所帶的文檔。那上面說了這么多,與notrace這個屬性有什么關(guān)系呢?因?yàn)樵谶M(jìn)行函數(shù)調(diào)用流程的顯示過程中,是使用了兩個特殊的函數(shù)的,當(dāng)函數(shù)被調(diào)用與函數(shù)被執(zhí)行完返回之前,都會分別調(diào)用這兩個特別的函數(shù)。如果不把這兩個函數(shù)的函數(shù)指定為不被跟蹤的屬性,那么整個跟蹤的過程 就會陷入一個無限循環(huán)當(dāng)中。
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
這兩句是一對對應(yīng)關(guān)系。__builtin_expect(expr, c)這個函數(shù)是新版gcc支持的,它是用來作代碼優(yōu)化的,用來告訴編譯器,expr的期,非常有可能是c,這樣在gcc生成對應(yīng)的匯編代碼的時候,會把相應(yīng)的可能執(zhí)行的代碼都放在一起,這樣能少執(zhí)行代碼的跳轉(zhuǎn)。為什么這樣能提高CPU的執(zhí)行效率呢?因?yàn)镃PU在執(zhí)行的時候,都是有預(yù)先取指令的機(jī)制的,把將要執(zhí)行的指令取出一部分出來準(zhǔn)備執(zhí)行。CPU不知道程序的邏輯,所以都是從可程序程序里挨著取的,如果這個時候,能不做跳轉(zhuǎn),則CPU預(yù)先取出的指令都可以接著使用,反之,則預(yù)先取出來的指令都是沒有用的。還有個問題是需要注意的,在__builtin_expect的定義中,以前的版本是沒有!!這個符號的,這個符號的作用其實(shí)就是負(fù)負(fù)得正,為什么要這樣做呢?就是為了保證非零的x的值,后來都為1,如果為零的0值,后來都為0,僅此而已。
#ifndef barrier
# define barrier() __memory_barrier()
#endif
這里表示如果沒有定義barrier函數(shù),則定義barrier()函數(shù)為__memory_barrier()。但在內(nèi)核代碼里,是會包含 compiler-gcc.h這個文件的,所以在這個文件里,定義barrier()為__asm__ __volatile__("": : :"memory")。barrier翻譯成中文就是屏障的意思,為什么要一個屏障呢?這是因?yàn)镃PU在執(zhí)行的過程中,為了優(yōu)化指令,可能會對部分指令以它自己認(rèn)為最優(yōu)的方式進(jìn)行執(zhí)行,這個執(zhí)行的順序并不一定是按照程序在源碼內(nèi)寫的順序。編譯器也有可能在生成二進(jìn)制指令的時候,也進(jìn)行一些優(yōu)化。這樣就有可能在多CPU,多線程或是互斥鎖的執(zhí)行中遇到問題。那么這個內(nèi)存屏障可以看作是一條線,內(nèi)存屏障用在這里,就是為了保證屏障以上的操作,不會影響到屏障以下的操作。然后再看看這個屏障怎么實(shí)現(xiàn)的。__asm__表示后面的東西都是匯編指令,當(dāng)然,這是一種在C語言中嵌入?yún)R編的方法,語法有其特殊性,我在這里只講跟這條指令有關(guān)的。__volatile__表示不對此處的匯編指令做優(yōu)化,這樣就會保證這里代碼的正確性。""表示這里是個空指令,那么既然是空指令,則所對應(yīng)的指令所需要的輸入與輸出都沒有。在gcc中規(guī)定,如果以這種方式嵌入?yún)R編,如果輸出沒有,則需要兩個冒號來代替輸出操作數(shù)的位置,所以需要加兩個::,這時的指令就為"" : :。然后再加上為分隔輸入而加入的冒號,再加上空的輸入,即為"" : : :。后面的memory是gcc中的一個特殊的語法,加上它,gcc編譯器則會產(chǎn)生一個動作,這個動作使gcc不保留在寄存器內(nèi)內(nèi)存的值,并且對相應(yīng)的內(nèi)存不會做存儲與加載的優(yōu)化處理,這個動作不產(chǎn)生額外的代碼,這個行為是由gcc編譯器來保證完成的。
#ifndef RELOC_HIDE
# define RELOC_HIDE(ptr, off)???? /
? ({ unsigned long __ptr;???? /
???? __ptr = (unsigned long) (ptr);??? /
??? (typeof(ptr)) (__ptr + (off)); })
#endif
接下來好多定義都沒有實(shí)現(xiàn),可以看一看注釋就知道了,所以這里就不多說了。唉,不過再插一句,__deprecated屬性的實(shí)現(xiàn)是為deprecated。
#define noinline_for_stack noinline
#ifndef __always_inline
#define __always_inline inline
#endif
這里noinline與inline屬性是兩個對立的屬性,從詞面的意思就非常好理解了。
#ifndef __cold
#define __cold
#endif
從注釋中就可以看出來,如果一個函數(shù)的屬性為__cold,那么編譯器就會認(rèn)為這個函數(shù)幾乎是不可能被調(diào)用的,在進(jìn)行代碼優(yōu)化的時候,就會考慮到這一點(diǎn)。不過我沒有看到在gcc里支持這個屬性的說明。
#ifndef __section
# define __section(S) __attribute__ ((__section__(#S)))
#endif
這個比較容易理解了,用來修飾一個函數(shù)是放在哪個區(qū)域里的,不使用編譯器默認(rèn)的方式。這個區(qū)域的名字由定義者自己取,格式就是__section__加上用戶輸入的參數(shù)。
#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
這個函數(shù)的定義很有意思,它就是訪問這個x參數(shù)所對應(yīng)的東西一次,它是這樣做的:先取得這個x的地址,然后把這個地址進(jìn)行變換,轉(zhuǎn)換成一個指向這個地址類型的指針,然后再取得這個指針?biāo)赶虻膬?nèi)容。這樣就達(dá)到了訪問一次的目的。
?
評論