內核同步

 linux內核中運行的程序,時刻都要防止併發引起的競態。這將會導致數據結構被破話,嚴重的時候會引起內核崩潰。所以內核同步技術對內核開發的驅動程序來說非常重要。不懂內核同步技術的人,是寫不出安全健壯的內核驅動程序來的。在學習內核同步技術之前需要掌握一下幾個概念。
        1 並行,併發與競態:在SMP運行的linux內核是真正的並行運行程序,多個CPU可以同時訪問同一數據結構,而在單處理器的系統上,內核程序的運行就是併發交錯的運行,看起來像同時執行,一個CPU上同一時間只能執行一條指令。當正常的內核代碼訪問一種數據結構的時候被中斷或者搶佔,中斷程序或者搶佔後運行的內核代碼也訪問這種數據,在這種情況下,有兩種內核控制路徑訪問數據結構,會發生不一致訪問,這就發生了競態。
        2 臨界區:臨界區是訪問一種數據結構數據結構的一段代碼,這段代碼有可能會被不同的內核控制路徑運行。內核同步保護的是數據結構而不是代碼,但實際上實現的同步技術是保護訪問數據結構的代碼。
        3 內核搶佔定義:進程在執行內核函數時(包括陷入內核的普通進程與內核線程)。即它在內核態運行時,允許發生進程切換。
        4 核控制路路徑:內核執行代碼所處不同的環境,主要分爲三種:
         (1) 正常的系統調用與內核線程。特點是可以被中斷,允許睡眠,有進程上下文,Current宏有意義,可以被內核搶佔。
         (2) 可延遲函數:軟中斷,內核定時器,tasklet。特點是可以被中斷,但是不允許睡眠,沒有進程上下文,Current宏無意義,不可以被內核搶佔。
           <1> 軟中斷的特點:在SMP系統上,相同的軟中斷可以在不同的CPU中同時執行,不同的軟中斷也可以同時執行。在單CPU的系統中,相同的軟中斷不能同時執行,不同的軟中斷也不能同時執行。
           <2> 內核定時器:是一種內核預定義的軟中斷,具有軟中斷的所有特徵。
           <3> tasklet:也是一種內核預定義的軟中斷,但是採用了一些保護機制,使得有其他軟中斷沒有的特點。相同的tasklet在SMP系統中也不能同時執行,不同的tasklet可以SMP系統中同時執行。
         (3) 中斷處理程序:可以被其他類型的中斷程序所打斷。相同的中斷不能嵌套發生。中斷函數可以寫成非可重入函數。
        5 內核運行環境分類:
         (1) 不支持內核搶佔的單CPU系統
         (2) 支持內核搶佔的單CPU系統
         (3) SMP系統
        在瞭解了這些概念後,然後再理清一下內核控制路徑的關係,這對於理解不同的同步技術很重要,下面的情況只針對單CPU的可以發生內核搶佔的情況:
        1 正常的內核代碼,可以被中斷程序打斷,也可以被可延遲函數打斷(本質上也是被中斷打斷,因爲內核在每隔1ms的節拍中斷處理程序中,據決定是否運行可執行函數)。如果中斷處理程序中或者可延遲函數使另外的一個進程可運行,並且優先級高於被打斷的進程,這時被打斷的進程就會失去處理器。發生內核搶佔。這樣其他進程就會運行,這個進程有可能有會調用這段代碼,相當於間接打斷了現在的代碼。所以總結一下:由於中斷,正常的內核代碼可以被中斷處理程序打斷,可以被可延遲函數間接打斷,由於中斷和內核搶佔所以正常的內核代碼必須是可重入的。(打斷爲強制暫停執行,非自願放棄處理器)
        2 可延遲函數:可以被中斷處理程序打斷,不可以被其他可延遲函數打斷,因爲在同一個CPU上可延遲函數是順序執行的。
        3 中斷處理程序:可以被其他類型中斷打斷,不可以被同類型的中斷打斷。因爲發生中斷時,同種類型的中斷被禁止。
        linux內核爲了應對不同內核控制路徑的代碼引發的競態的問題,應用了一下幾種內核同步技術:
        1 per-CPU變量:最簡單的同步技術,他是數據結構的數組,系統中的每個CPU都擁有數組中的一個元素,而且只有自己纔可以操作這個元素,其他的CPU不能夠訪問。但是如果異步函數也要訪問這個變量的話,就必須進行相關的保護。
        2 原子操作: 這種技術主要針對單變量的數據,CPU對一個變量的訪問可能不是原子的,但是採用atomic_t類型的原子變量,內核可以保證原子的訪問變量。注意的是,操作這種變量只能用內核提供的函數,不能單獨的
操作。
        3 優化屏障與內存屏障:優化屏障:保證編譯器不會混淆在屏障操作之前的彙編指令與屏障之後的彙編指令。內存屏障:保證在屏障之後的操作開始執行之前,屏障之前的操作已經完成。
        4 自旋鎖: 自旋鎖主要針對SMP系統,在單處理器上自旋鎖退化爲禁止內核搶佔的開關。在SMP系統上:spin_lock首先禁止內核搶佔,然後原子的測試自旋鎖的裝他。如果爲1,那麼跳出,內核控制路徑獲得自旋縮。如果爲0,則打開內核搶佔,循環測試直到爲1.這就是自旋鎖名字的來歷。注意在自旋鎖自旋的時候,可以被搶佔。在單處理器系統上:spin_lock除了禁止內核搶佔,什麼都不做。自旋鎖使用要點:在中斷與可延遲函數中使用自旋鎖是允許的。但一定要注意死鎖。如果正常內核代碼與中斷處理程序使用同一把鎖,在正常內核代碼持有鎖的時候應該禁止中斷,否則就會發生死鎖。可延遲函數與正常內核代碼使用同一把鎖的時候,在正常內核代碼或得鎖之時應該禁用可延遲函數。在中斷處理程序與可延遲代碼中使用同一把鎖的時候,可延遲函數或得鎖之時應該禁止中斷。大體的原則是這樣的:中斷處理函數,可延遲函數,正常內核代碼三者的打斷能力由大到小。在能力小的代碼中獲得鎖需要禁止所有能力大的代碼執行。在單處理器上,雖然瑣只是一個內核搶佔的開關,但是用鎖的方法與SMP系統上是一樣的。但是目的不同,SMP系統中是爲了防止死鎖。單處理器上是爲了防止競態,在單處理器上的是不可能發生自旋鎖死鎖的。
         其他的同步技術還有:
         讀/寫自旋鎖。這個鎖是爲了增加內核的併發能力。只要沒有內核控制路徑對數據進行修改,允許多個控制路徑同時讀取同一數據結構。讀/寫操作具有相同的優先級。
         順序鎖:與讀寫鎖類似,但是讀/寫操作有不同的優先級,讀操作的優先級低於寫操作。也就是一個讀程序持有鎖的情況下可以被一個寫程序打斷。
         讀取-複製-更新技術(RCU):爲了保護大多數情況下被多個CPU讀的數據結構,這種技術沒有使用加鎖技術
         信號量:使進程可以睡眠的鎖。當進程或得不到鎖的時候睡眠,當釋放鎖的時候,喚醒睡在鎖上的進程。
        編寫驅動程序中要靈活運用內核中的同步技術,這樣才能編寫出併發度高,穩定健壯的驅動程序。首先要確定哪些部分是需要保護的,這就需要分析訪問數據的代碼可能有幾個內核控制路徑並行或者併發執行,結合各個同步技術的特點靈活應用,一般而言能不需要同步的就不要同步,能不加鎖的就不加鎖,因爲內核同步必然會引起一定的性能損失。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章