第二章從核心出發之Linux核心開發特點

2021-08-21 11:44:56 字數 3808 閱讀 2226

2.4 核心開發的特點

相對於使用者空間內應用程式的開發,核心開發有一些獨特之處。儘管這些差異並不會使開發核心的難點超過開發使用者**,但依然有很大不同。

核心程式設計時既不能訪問c庫也不能訪問標準的c標頭檔案。

核心程式設計時必須使用gnu c。

核心程式設計時缺乏像使用者空間那樣的記憶體保護機制。

核心程式設計時難以執行浮點運算。

核心給每個程序只有乙個很小的定長堆疊。

由於核心支援非同步中斷、搶占和smp,因此必須時刻注意同步和併發。

要考慮可移植性的重要性。

1、無libc庫抑或無標準標頭檔案

與使用者空間的應用程式不同,核心不能鏈結使用標準c函式庫-或者其他的那些庫也不行。造成這種情況的原因有許多,最主要的原因還是速度和大小。對核心來說,完整的c庫-哪怕是它的乙個子集,都太大且太低效了。

大部分常用的c庫函式在核心中都已經得到實現。比如操作字串的函式組就位於lib/string.c檔案中。只要包含標頭檔案,就可以使用它們。

標頭檔案:指的是組成核心源**樹的核心標頭檔案,核心源**檔案不能包含外部標頭檔案。基本的標頭檔案位於核心源**樹頂級目錄下的include目錄中。例如,標頭檔案對應核心源**樹的include/linux/inotify.h。

體系結構相關的標頭檔案位於核心源**樹的arc//include/asm目錄下。例如,如果編譯的是x86體系結構,則體系結構相關的標頭檔案是arch/x86/include/asm。核心**通過以asm/為字首的方式包含這些標頭檔案,例如。

在所有沒有實現的函式中,最著名的就是printf()函式。核心**雖然無法呼叫printf(),但核心提供的printk()函式幾乎與printf()相同。printk()函式負責把格式化好的字串拷貝到核心日誌緩衝區上,syslog程式可以通過讀取該緩衝區來獲取核心資訊。

printk(kern_info "bus unregister\n");

printk()和printf()之間的乙個顯著區別在於,printk()允許通過指定乙個標誌來設定優先順序。syslogd會根據這個優先順序標誌來決定在什麼地方顯示這條系統訊息。

備註:在kern_info和要列印的訊息之間沒有逗號。優先順序標誌是預處理程式定義的乙個描述性字串,在編譯時優先順序標誌就與要列印的訊息繫結在一起處理。

2、gnu c

linux核心是用c語言編寫的,核心並不完全符號ansi c標準。只要有可能,核心開發者總是要用到gcc提供的許多語言擴充套件部分。

核心開發者使用的c語言涵蓋iso c99標準和gnu c擴充套件特性。

1)內聯函式

c99和gnu c均支援內聯函式。inline可以反映出它的工作方式,函式會在它所呼叫的位置上展開。這麼做可以消除函式呼叫和返回所帶來的開銷(暫存器儲存和恢復)。而且,由於編譯器會把呼叫函式的**和函式本身放在一起進行優化,所以也有進一步優化**的可能。這麼做是有代價的,**會變長,也就意味著占用更多的記憶體空間或者占用更多的指令快取。核心開發者通常把那些對時間要求比較高,而本身長度又比較短的函式定義成內聯函式。如果乙個函式較大,會被反覆呼叫,且沒有特別的時間上的限制,不贊成把它做成內聯函式。

定義內聯函式時,需要使用static關鍵字,並且用inline來限定。例如:

static inline int memory_dev_init(void)

內聯函式必須在使用之前就定義好,否則編譯器就沒法把這個函式展開。一般在標頭檔案中定義內聯函式。由於使用了static作為關鍵字限制,所以編譯時不會為內聯函式單獨建立乙個函式體。如果乙個內聯函式僅僅在某個原始檔中使用,也可以把它定義在該檔案開始的地方。

在核心中,為了型別安全和易讀性,優先使用內聯函式而不是複雜的巨集。

2)內聯彙編

gcc編譯器支援在c函式中嵌入彙編指令。在核心程式設計時,只有知道對應的體系結構,才能使用這個功能。通常使用asm()指令嵌入彙編**。linux核心混合使用了c語言和組合語言。在偏近體系結構的底層或對執行時間要求嚴格的地方,一般使用的是組合語言。而核心其它部分的大部分**是用c語言編寫的。

3)分支宣告

對於條件選擇語句,gcc內建了一條指令用於優化,在乙個條件經常出現,或者該條件很少出現時,編譯器根據這條指令對條件分支選擇進行優化。核心把這條指令封裝成了巨集,比如likely()和unlikely(),這樣使用起來比較方便。例如,下面是乙個條件選擇語句:

if(error){

/*......*/

如果要把這個選擇標記成絕少發生的分支:

if(unlikely(error)){//error絕大多數時間都會為0

/*......*/

如果把乙個分支標記為通常為真的選擇:

if(likely(success)){//success通常都不會為0

/*......*/

在想要對某個條件選擇語句進行優化之前,一定要搞清楚其中是不是存在這麼乙個條件,在絕大多數情況下都會成立。這點十分重要:如果判斷正確,確實是這個條件佔壓倒性的地位,那麼效能就會提公升;如果搞錯了,效能反而會下降。通常在對一些錯誤條件進行判斷時使用likely()或unlikely()。unlikely()在核心中會得到更廣泛的應用,因為if語句往往判斷一種特殊情況。

3、沒有記憶體保護機制

如果乙個使用者程式檢視進行一次非法的記憶體訪問,核心會發現這個錯誤,傳送sigsegv(段錯誤)訊號,並結束整個程序。如果是核心自己非法訪問了記憶體,後果很難控制。核心中發生的記憶體錯誤會導致oops,這是核心中出現的最常見的一類錯誤。在核心中,不應該去做訪問非法的記憶體位址,引用空指標之類的事情,否則它可能會導致死掉。

此外,核心中的記憶體都不分頁。也就是說,每用掉乙個位元組,物理記憶體就減少乙個位元組。所以,往核心中加入新功能時,要記住這一點。

4、不要輕易在核心中使用浮點數

在使用者空間的程序內進行浮點操作時,核心會完成從整數操作到浮點數操作的模式轉換。在執行浮點指令時到底會做些什麼,因體系結構不同,核心的選擇也不同,但是,核心通常捕獲陷阱並著手於整數到浮點方式的轉變。

與使用者空間程序不同,核心並不能完美地支援浮點操作,因為它本身不能陷入。在核心中使用浮點數時,除了要人工儲存和恢復浮點暫存器,還有其他一些瑣碎的事情要做。除了一些極少的情況,不要在核心中使用浮點操作。

5、容積小而固定的棧

使用者空間的程式可以從棧上分配大量的空間來存放變數,甚至巨大的結構體或者是包含數以千計的資料項的陣列都沒有問題。之所以可以這麼做,是因為使用者空間的棧本身比較大,而且還能動態地增長。

核心棧的準確大小隨體系結構而變。在x86上,棧的大小在編譯時配置,可以是4kb也可以是8kb。從歷史上說,核心棧的大小是兩頁,這意味著,32位機的核心棧是8kb,而64位機的核心棧是16kb,這是固定不變的。每個處理器都有自己的棧。

6、同步和併發

核心很容易產生競爭條件。和單執行緒的使用者空間程式不同,核心的許多特性都要求能夠併發地訪問共享資料,這就要求有同步機制以保證不出現競爭條件,特別是:

linux是搶占多工作業系統。核心的程序排程程式即對程序進行排程和重新排程。核心必須和這些任務同步。

linux核心支援對稱多處理器系統smp。如果沒有適當的保護,同時在兩個或兩個以上的處理器上執行的核心**很可能會同時訪問共享的同乙個資源。

中斷是非同步到來的,完全不顧及當前正在執行的**。如果不加以適當的保護,中斷完全有可能在**訪問資源的時候到來,中斷處理程式就有可能訪問同一資源。

linux核心可以搶占。如果不加以適當的保護,核心中一段正在執行的**可能會被另一段**搶占,從而有可能導致幾段**同時訪問相同的資源。

常用的解決競爭的辦法是自旋鎖和訊號量。

7、可移植性的重要性

儘管使用者空間的應用程式不太注意移植問題,然而linux是乙個可移植的作業系統。大部分c**應該與體系結構無關,在許多不同體系結構的計算機上都能編譯和執行,因此,必須把與體系結構相關的**從核心**樹的特定目錄中適當地分離出來。

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

第二章 核心C

如果在一條語句中宣告和初始化了多個變數,那麼所有的變數都具有相同的資料型別 int x 10,y 20 變數使用前需要賦初始值 宣告不同型別的變數需要單獨的語句。變數的初始化 變數時類或者結構中的字段,如果沒有顯式的初始化,建立這些變數時,其預設值就是0.方法的區域性變數必須在 中顯示的初始化,之後...

Python核心教程 第二章

輸出hello world 核心筆記一 在互動式直譯器中顯示變數的值 通常當你想看變數內容時,你會在 中使用print 語句輸出。不過在互動式直譯器中,你可以用 print 語句顯示變數的字串表示,或者僅使用變數名檢視該變數的原始值。在下面的例子中,我們把乙個字串賦值給變數mystring,先用pr...

C primer之第二章

閱讀至2.5.2時,發現乙個不知道的知識點 如果某個型別的別名指代的是復合型別或是常量,那麼它用到宣告語句裡面就會產生意想不到的後果,例如下面的宣告語句用到了型別pstring,它實際上是型別char 的別名 typedef char pstring 1 const pstring cstr 0 c...