1. 程式人生 > >Linux核心設計與實現(1)--核心開發的特點

Linux核心設計與實現(1)--核心開發的特點

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

       其中的原因有很多種。其一,C標準庫的很多函式實現都是基於核心實現的,這核心編譯的時候都還沒有核心,所以就不存在這些函式,這個就是先有雞還是先有蛋這個悖論。其二,其主主要的的原因是熟讀和大小。對於核心來說,完整的C庫–哪怕是它的一個子集,都太大且太低效了。
       不過大部分的C庫函式在核心中都已經得到實現。其實現原始碼在lib/***.c,對應的標頭檔案在include/linux/資料夾中,只需要包含<linux/xxx.h>標頭檔案,就可以使用它們了。

2. 核心使用GNU C

       Linux核心都是用C語言編寫的。但是核心不完全符合ANSI C標準。它是ANSI C的擴充套件,包含一些GNU C獨有的特性。具體的區別可以參考 https://blog.csdn.net/lbit20131014/article/details/81108096
       開發者採用gcc編譯核心同時也可以用gcc編譯系統應用程式(gcc是多種GNU編譯器的集合,即可以編譯核心,也可以編譯Linux系統上用C寫的其他程式碼)

3. 沒有記憶體保護機制

       如果使用者程式試圖進行一次非法的記憶體訪問,核心就會發現這個錯誤,傳送SIGSEGV訊號,並結束整個程序;這個記憶體保護機制是核心來實現,而在核心編寫的時候如果出現非法記憶體訪問那就很難控制了,所以設計的時候一定喲注意記憶體錯誤的問題

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

       

5. 容積小而固定的棧

       使用者態程式棧的大小可以是比較大的,同時還可以動態地增長。而核心的對戰大小都是固定的,其準確大小隨著體系結構而變化,以下是網路引用。

Linux核心棧溢位(stack overflow)問題
最近一段時間在設計和開發一個Linux核心模組,進入到最後的正確性測試與穩定性測試階段。在這個階段發現了一個非常有意思的問題,堆疊溢位(stack overflow)。Linux核心堆疊溢位之後直接導致了系統kernel Panic。由於導致stack overflow的原因是遞迴呼叫導致的,所以,最後通過除錯串列埠匯出的kernel panic資訊很快就定位問題所在了,否則這樣的問題還真是很難除錯和發現。通過這次bug,我們應該記住的是:Linux核心stack資源是有限的,而遞迴呼叫將大量消耗stack資源,因此在核心程式設計中儘量少用遞迴演算法,否則將會導致出乎意料的一些問題。依次類推,為了減少stack資源的消耗,程式的區域性變數定義的不要太大,否則也將會消耗大量stack資源,從而導致核心程式的不穩定。
 
為了解決遞迴呼叫導致的問題,我將遞迴演算法改寫成了非遞迴演算法,解決了stack overflow的問題。在此介紹一下遞迴演算法改寫成非遞迴演算法的一些思想。在專案實現過程中,需要對IO請求進行按順序排隊,因此採用了效率較高並且實現簡單的快速排序演算法,該演算法是一種分治演算法,即將排序佇列進行切分,分解成一系列的小問題進行求解,針對這種問題,很容易採用遞迴的辦法進行實現,虛擬碼描述如下:
/* qs_sort實現從小到大的排序 */
Struct bio qs_sort(struct bio_list *list_head, struct bio *bio_tail) {
       Struct bio_list *less_list, *large_list;
       Struct bio *middle_bio;
 
       /* 遞迴呼叫結束點,小問題求解完畢,直接返回最後一個元素 */
If (!list_head) {
              Return bio_tail;
       }
      
/* 對佇列進行切分,選擇一個middle_bio,並且按照middle_bio將其切分成less_list佇列和large_list佇列 */
       Split_list(list_head, less_list, large_list, &middle_bio);
      
       /* 採用遞迴的方法實現大佇列的排序操作 */
       Middle_bio->bi_next = qs_sort(large_list, bio_tail);
      
/* 採用遞迴的方法實現小佇列的排序操作 */
       Return qs_sort(less_list, middle_bio);
}
 
有上述可見,採用函式遞迴的方法實現簡單,但是將會犧牲(棧)儲存空間,為此,需要將其改寫成非遞迴的實現方法,非遞迴的實現演算法可以點選此處下載(,歡迎提出意見)。改寫的思想是將遞迴所採用的儲存棧空間動態分配。遞迴演算法本質上利用堆疊儲存了切分的小問題,因此,可以採用系統記憶體動態分配儲存空間,自己維護小問題堆疊。那麼可以做到不利用函式堆疊空間,避免了棧空間的大量消耗。
總之,在核心程式實現過程中,一定要注意棧空間的使用,特別像遞迴這樣的方法儘量少用,否則將可能會對產品產生致命的打擊。

from:http://hi.baidu.com/mffppwbneqdmnqe/item/8761040489cddfd6dde5b098

6. 同步和併發

        核心很容易產生競爭條件,核心的許多特性要求能夠併發地訪問共享資料,這就要求核心有同步機制保證不出現競爭的條件。以下是應用網路部落格。

造成併發執行的原因

中斷,軟中斷和tasklet: 中斷和程序, 中斷和中斷之間有可能會引起併發問題.

核心搶佔: 一個執行緒會被另一個執行緒搶佔, 所以執行緒和執行緒之間也有同步問題.

睡眠: 執行緒主動性睡眠也會引起同步問題.

對稱多處理(SMP): 多個處理器同時執行一套程式碼就有問題.

 

從上面我們可以看出來, 如果不需要支援SMP的話, 我們只需要關閉中斷或者搶佔就可以解決併發問題(關閉中斷之後, 被動的搶佔不會發生, 只有執行緒主動呼叫某些API才會觸發程序排程). 當SMP出現之後(linux2.0時代就已經出現了), 我們就必須使用自旋鎖來解決同步問題了.

 

造成死鎖的原因

遞迴引起的死鎖: linux下的spin_lock是不支援遞迴的, 所以同一個執行緒不能多次獲取同一個鎖.

ABBA引起的死鎖: 多個執行緒獲取鎖的順序不一致引起的死鎖. 解決方法是大家都按相同的順序去獲取鎖.

 

Linux核心中的同步方法

自旋鎖: 可以在中斷上下文中使用. (不會引起死鎖, 如果執行緒和中斷處理函式共用了一個鎖的話, 那麼當執行緒獲取自旋鎖之後是會明確的關中斷的. Spin_lock_irqsave)

訊號量: 會引起睡眠, 所以不能在中斷上下文中使用.

互斥體: 很容易理解, 這裡需要注意的是互斥體只能被同一個執行緒獲取和釋放.

讀-寫自旋鎖: 適用於讀者和讀操作很多, 但是寫者和寫操作很少的情況. 需要注意的是, 如果讀者過多的話, 會造成寫者飢餓.

讀-寫訊號量: 基本同讀寫自旋鎖, 然後像訊號量一樣會引起睡眠.

完成變數: 一種簡單的非同步通知機制, 通過名字就很容易理解.

順序鎖: 順序鎖與讀寫自旋鎖很類似, 只是該鎖的寫會優先於讀, 也就是寫者不會讓讀者飢餓.

Linux提供了這麼多種不同的方式來做同步, 就是為了在不同的應用場景中找到效能和效率的平衡點. 我們要權衡CPU佔用和程序排程帶來的開銷, 在不同的場合下選擇不同的加鎖機制.
--------------------- 
作者:shinezhang86 
來源:CSDN 
原文:https://blog.csdn.net/shinezhang86/article/details/48292565

7. 可移植的重要性

        因為Linux是一個可移植的系統,大部分程式碼與體系結構無關,同時,作為一個專業的程式設計者來說,應該要注重系統的可移植性