linux核心必須使用GNU的GCC編譯器來編譯,而GCC提供了很多的C語言擴充,這些擴充對最佳化,目標代碼布局,更安全的檢查等提供了很強的支援。因此,核心代碼所使用的C文法並不完全符合ANSI C標準,只要有可能,核心開發人員總是要用到GCC提供的C語言擴充部分。
計算最大值和最小值:
/* * ..and if you can't take the strict * types, you can specify one yourself. * * Or not use min/max/clamp at all, of course. */#define min_t(type, x, y) ({ \ type __min1 = (x); \ type __min2 = (y); \ __min1 < __min2 ? __min1: __min2; })#define max_t(type, x, y) ({ \ type __max1 = (x); \ type __max2 = (y); \ __max1 > __max2 ? __max1: __max2; })
零長度數組
結構體中的零長度數組並不佔用結構空間,意味著這個結構的長度是可變的。
可變參數宏
printk
特殊屬性__attribute__
GCC可以支援十幾個屬性,__attribute__ 可以支援的屬性比較常用的又
__attribute__((ATTRIBUTE)) , 其中ATTRIBUTE是屬性說明
- noreturn 表示該函數從不返回
- format
- unused 表示該函數或變數可能並不使用
- section 用於函數和變數放在只從的便宜地區
- aligned 用於變數,結構或聯合,設定一個指定大小的對齊格式,一位元組為單位
- packed 用於變數和類型,用於變數或者結構體成員時表示使用最小可能的對其,用於枚舉,結構體或者聯合體是表示該類型使用最小記憶體。
內建函數
以__builtin開始的內建函數,__buildin_xxx 函數
Linux核心開發的特點
相對於使用者空間內的應用程式開發,核心開發存在很多的不同,最重要的差異包括以下幾種:
1)核心編程時不能訪問C庫。
2)核心編程時必須使用GNU C。
3)核心編程時缺乏像使用者空間那樣的記憶體保護機制。
4)核心編程時浮點數很難使用。
5)核心只有一個很小的定長堆棧。
6)由於核心支援非同步中斷,搶佔和SMP,因此必須時刻注意同步和並發。
7)要考慮可移植性的重要性。
1.沒有libc庫
與使用者空間的應用程式不同,核心不能連結使用標準C函數庫(其他的那些庫也不行)。最主要的原因在於速度和大小。雖然不能使用,但大部分常用的C庫函數在核心中都已經得到實現。比如說操作字串的函數組就位於lib/string.c檔案中,只要包含<linux/string.h>標頭檔,就可以使用它們。
核心並沒有實現printf(),但可以使用printk(),printk()負責把格式化好的字串拷貝到核心日誌緩衝區上,這樣,syslog程式就可以通過讀取該緩衝區來擷取核心資訊。printk()的用法很像printf()`:
printk("Hello world! A string: %s and an integer: %d.\n", a_string, an_integer);
printk()和printf()之間的一個顯著區別在於printk()允許通過指定一個標誌來設定優先權。syslog會根據這個優先順序標誌來決定在什麼地方顯示這條系統訊息。
printk(KERN_ERR "this is a error!\n");
2.GNU C
Linux核心是用C語言編寫的,但它並不完全符合ANSI C標準,而使用到gcc提供的許多語言擴充部分。
1)內聯(inline)函數
inline使函數在它被調用的位置上展開,這樣做可以消除函數調用和返回所帶來的開銷(寄存器儲存和恢複)。而且,由於編譯器會把調用函數的代碼和函數本身放在一起進行最佳化,所以也有進一步最佳化代碼的可能。不過,這樣做是有代價的,代碼會變長,意味著佔用更多的記憶體空間或者佔用更多的指令緩衝。通常把那些對時間要求比較高,而本身長度又比較短的函數定義成內嵌函式。
static inline void dog(unsinged long tail_size);
在核心中,為了型別安全的原因,優先使用內嵌函式而不是複雜的宏。
2)內聯彙編
gcc編譯器支援在C函數中嵌入彙編指令。當然,在核心編程的時候,只有知道對應的體繫結構,才能使用這個功能。
3)分支聲明
對於條件選擇語句,gcc內建了一條指令用於最佳化,在一個條件經常出現,或者該條件很少出現的時候,編譯器可以根據這條指令對條件分支選擇進行最佳化,核心把這條指令封裝成了宏,比如likely()和unlikely()。
例如,下面是一個條件選擇語句:
if (foo) { /* .......... */ }
如果想要把這個選擇標記成絕少發生的分支:
/* 我們認為foo絕大多數時間都會為 0 */ if (unlikely(foo)) { /* .......... */ }
相反,如果我們想把一個分支標記為通常為真的選擇
/* 我們認為foo通常都不會為 0 */ if (likely(foo)) { /* .......... */ }
在想要對某個條件選擇語句進行最佳化之間,一定要搞清楚其中是不是存在這麼一個條件,在絕大多數情況下都會成立,這點十分重要:如果你的判斷正確,確定是這個條件佔壓倒性的地位,那麼效能會得到提升,如果你搞錯了,效能反而會下降。在對一些錯誤條件進行判斷的時候,常常用到unlikely()和likely()。可以猜到,unlikely()在核心中得到廣泛使用,因為if語句往往判斷一種特殊情況。
3.沒有記憶體保護機制
如果一個使用者程式試圖進行一次非法的記憶體訪問,核心會發現這個錯誤,發送SIGSEGV,並結束整個進程。然而,如果是核心自己非法訪問了記憶體,那後果就很難控制了,核心中發生的記憶體錯誤會導致oops,這是核心中出現的最常見的一類錯誤。在核心中,不應該去做訪問非法的記憶體位址,引用null 指標之類的事情,否則它可能會死掉,卻根本不知會你一聲——在核心裡,風險常常會比外面大一些。
此外,核心中的記憶體都不分頁,也就是說,用掉一個位元組,實體記憶體就減少一個位元組。所以,在你想往核心裡加入什麼新功能的時候,要記住這一點。
4.不要輕易在核心中使用浮點數
使用者空間的進程內進行浮點操作的時候,核心會完成從整數操作到浮點數操作的模式轉換。在執行浮點指令時到底會做些什麼,因體繫結構不同,核心的選擇也不同,但是,核心通常捕獲陷阱並做相應處理。
和使用者空間進程不同,核心並不能完美地支援浮點操作,因為它本身不能陷入。在核心中使用浮點數時,除了要人工儲存和恢複浮點寄存器,還有其他一些瑣碎的事情要做。如果要直截了當的回答,那就是:別這麼做,不要在核心中使用浮點數。
5.容積小而固定的棧
使用者空間的程式可以從棧上分配大量的空間來存放變數,甚至巨大的結構體或者是包含許多資料項目的數組都沒有問題。因為使用者空間的棧本身比較大,而且還能動態增長。
核心棧的準確大小隨體繫結構而變。在x86上,棧的大小在編譯時間配置,可以是4KB或8KB。核心棧的大小是兩頁,所以32位機的核心棧是8KB,而64位機是16KB,這是固定不變的。每個處理器都有自己的棧。
6.同步和並發
核心很容易產生競爭條件,因核心的許多特性都要求能夠並發的訪問共用資料,這就要求有同步機制保證不出現競爭條件,尤其是:
1)Linux是搶佔多任務作業系統。核心的進程發送器即興對進程進行調度和重新調度。核心必須對這些任務同步。
2)Linux核心支援多處理器系統。如果沒有適當的保護,在兩個或兩個以上的處理器上啟動並執行代碼很可能會訪問共用的同一個資源。
3)中斷是非同步到來的,完全不顧及當前正在執行的代碼。如果不加以適當的保護,中斷完全有可能在代碼訪問共用資源的當中到來,這樣中斷處理常式就有可能訪問同一資源。
4)Linux核心可以搶佔,如果不加以適當的保護,核心中的一段正在執行的代碼可能會被另外一段代碼搶佔,從而有可能導致幾段代碼同時訪問相同的資源。
7.可移植性的重要性
Linux是一個可移植的作業系統,大部分C代碼應該與體繫結構無關,在許多不同體繫結構的電腦上都可以編譯和執行。因此,必須把體繫結構相關的代碼從核心代碼樹的特定目錄中適當地分享出來。
諸如保持位元組序,64位對齊,不假定字長和頁面長度等一系列準則都有助於移植性。