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