linux內核代碼特點

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,這是內核中出現的最常見的一類錯誤。在內核中,不應該去做訪問非法的內存地址,引用空指針之類的事情,否則它可能會死掉,卻根本不知會你一聲——在內核裏,風險常常會比外面大一些。

此外,內核中的內存都不分頁,也就是說,用掉一個字節,物理內存就減少一個字節。所以,在你想往內核里加入什麼新功能的時候,要記住這一點。

4.不要輕易在內核中使用浮點數

用戶空間的進程內進行浮點操作的時候,內核會完成從整數操作到浮點數操作的模式轉換。在執行浮點指令時到底會做些什麼,因體系結構不同,內核的選擇也不同,但是,內核通常捕獲陷阱並做相應處理。

和用戶空間進程不同,內核並不能完美地支持浮點操作,因爲它本身不能陷入。在內核中使用浮點數時,除了要人工保存和恢復浮點寄存器,還有其他一些瑣碎的事情要做。如果要直截了當的回答,那就是:別這麼做,不要在內核中使用浮點數。

5.容積小而固定的棧

用戶空間的程序可以從棧上分配大量的空間來存放變量,甚至巨大的結構體或者是包含許多數據項的數組都沒有問題。因爲用戶空間的棧本身比較大,而且還能動態的增長。

內核棧的準確大小隨體系結構而變。在x86上,棧的大小在編譯時配置,可以是4KB或8KB。內核棧的大小是兩頁,所以32位機的內核棧是8KB,而64位機是16KB,這是固定不變的。每個處理器都有自己的棧。

6.同步和併發

內核很容易產生競爭條件,因內核的許多特性都要求能夠併發的訪問共享數據,這就要求有同步機制保證不出現競爭條件,尤其是:

1)Linux是搶佔多任務操作系統。內核的進程調度程序即興對進程進行調度和重新調度。內核必須對這些任務同步。

2)Linux內核支持多處理器系統。如果沒有適當的保護,在兩個或兩個以上的處理器上運行的代碼很可能會訪問共享的同一個資源。

3)中斷是異步到來的,完全不顧及當前正在執行的代碼。如果不加以適當的保護,中斷完全有可能在代碼訪問共享資源的當中到來,這樣中斷處理程序就有可能訪問同一資源。

4)Linux內核可以搶佔,如果不加以適當的保護,內核中的一段正在執行的代碼可能會被另外一段代碼搶佔,從而有可能導致幾段代碼同時訪問相同的資源。

7.可移植性的重要性

Linux是一個可移植的操作系統,大部分C代碼應該與體系結構無關,在許多不同體系結構的計算機上都可以編譯和執行。因此,必須把體系結構相關的代碼從內核代碼樹的特定目錄中適當地分享出來。

諸如保持字節序,64位對齊,不假定字長和頁面長度等一系列準則都有助於移植性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章