可以使用前面所述的同步原語保護共享資料結構避免競爭條件。當然,系統效能可能隨所選擇同步原語種類的不同而有很大變化。通常情況下,核心開發者採用下述由經驗得到的法則:把系統中的併發度保持在盡可能高的程式。
系統中的併發度又取決於兩個主要因素:
同進運轉的
i/o裝置數
進行有效工作的
cpu數
為了使i/o
吞吐量最大化,應該使中斷禁止保持在很短的時間。當中斷被禁止時,由
i/o裝置產生的
irq被
pic暫時忽略,因此,就沒有新的活動在這種裝置上開始。
為了有效地利用
cpu,應該盡可能避免使用基於自旋鎖的同步原語。當乙個
cpu執行緊指令迴圈等待自旋鎖開啟時,是在浪費機器週期。就像我們前面所描述的,由於自旋鎖對硬體快取記憶體的影響而使其對系統的整體效能產生不利影響。
讓我們舉例說明在下列兩種情況下,既可以維持較高的併發度,也可以達到同步。
共享的資料結構是乙個單獨的整數值,可以把它宣告為
atomic_t
型別並使用原子操作對其更新。原子操作比自旋鎖和中斷禁止都快,只有在幾個核心控制路徑同時訪問這個資料結構時速度才會慢下來。
把乙個元素插入到共享鍊錶的操作決不是原子的,因為這至少涉及兩個指標賦值。
不過,核心有時並不用鎖或禁止中斷就可以執行這種插入操作。我們把這種操作的工作機制作為例子來說明。考慮一種情況,系統呼叫服務例程把新元素插入到乙個簡單鍊錶中,而中斷處理程式或可延遲函式非同步地檢視該鍊錶。在c
語言中,插入是通過下列指標賦值實現的:
new->next=list_element->next;
list_element->next= new
在組合語言中,插入簡化為兩個連續的原子指令。第一條指令建立
new元素的
next
指標,但不修改鍊錶,因此,如果中斷處理程式在第一條指令和第二條指令執行後檢視這個鍊錶,看到的就是沒有新元素的鍊錶。如果該處理程式在第二條指令執行後檢視鍊錶。就會看到新元素的鍊錶。關鍵是,在任一種情況下,鍊錶都是一致的且處於未損壞狀態。然而,只有在中斷處理程式不修改鍊錶的情況下才能確保這種完整性。如果修改了鍊錶,那麼在
new元素內剛剛設定的
next
指標就可能變為無效的。
在自旋鎖、訊號量及中斷禁止之間選擇
對大多數核心資料結構的訪問模式非常複雜,遠不像上便所示的哪樣簡單,於是,迫使核心開發者使用訊號量、自旋鎖、中斷禁止和軟中斷禁止。一般來說,同步原語的選取取決於訪問資料結構的核心控制路徑的種類,如表
5-8所示。記住,只要核心控制路徑獲得自旋鎖,就禁用本地中斷或本地軟中斷,自動禁用核心搶占。
保護異常所訪問的資料結構
當乙個資料結構僅由異常處理程式訪問時,競爭通常是易於理解也易於避免的。最常見的產生同步問題的異常是系統呼叫服務例程,在這種情況下,
cpu執行在核心態而為使用者態程式提供服務。因此,僅由異常訪問的資料結構通常表示一種資源,可以分配乙個或多個程序。
競爭條件可以通過訊號量避免,因為訊號量原語允許程序睡眠到資源變為可用。注意,訊號量工作方式在單處理器系統和多處理器系統上完全相同。
核心搶占不會引起太大的問題。如果乙個擁有訊號量的程序是可以被搶占的,執行在同乙個
cpu上的新程序就可能試圖獲得這個訊號量。在這種情況下,讓新程序處於睡眠狀態,而且原來擁有訊號量的程序最終會釋放訊號量。只有在訪問每
cpu變數的情況下,必須顯式地禁用核心搶占,就像在本章前面」每
cpu變數」一節中所描述的那樣。
保護中斷所訪問的資料結構
假定乙個資料結構僅被中斷處理程式的」上半部分」訪問。也就是說,中斷處理程式本身不能同時多次執行。因此,訪問資料結構就無需任何同步原語。
但是,如果多個中斷處理程式訪問乙個資料結構,情況就有所不同了。乙個處理程式可以中斷另乙個處理程式,不同的中斷處理程式可以在多處理器系統上同時執行。沒有同步,共享的資料結構就很容易被破壞。
在單處理器系統上,必須通過在中斷處理程式的所有臨界區上禁止中斷來避免競爭條件。只能用這種方式進行同步,因為其它的同步原語都不能完成這件事。訊號量能夠阻塞程序,因此,不能用在中斷處理程式上。另乙個方面,自旋鎖可能使系統凍結:如果訪問資料結構的處理程式被中斷。它就不能釋放鎖;因此,新的中斷處理程式在自旋鎖的緊迴圈上保持等待。
同樣,多處理器系統的要求甚至更加苛刻。不能簡單地通過禁止本地中斷來避免競爭條件。事實上,即使乙個
cpu上禁止了中斷。中斷處理程式還可以在其它
cpu上執行。避免競爭條件最簡單的方法是禁止本地中斷,並獲取保護資料結構的自旋鎖或讀
/寫鎖。注意,這些附加的自旋鎖不能凍結系統,因為即使中斷處理程式發現鎖被關閉,在另乙個
cpu上擁有鎖的中斷處理程式最終也會釋放這個鎖。
linux
核心使用了幾個巨集,把本地中斷啟用
/禁止與自旋鎖結合起來。表
5-9描述了其中的所有巨集。在單處理器系統上,這些巨集公啟用或禁止本地中斷和核心搶占。
保護可延遲函式所訪問的資料結構
只被可延遲函式訪問的資料結構需要哪種保護呢?這主要取決於可延遲函式的種類。
首先,在單處理器系統上不存在競爭條件。這是因為可延遲函式的執行總是在乙個
cpu上序列進行
------
也就是說,乙個可延遲函式不會被另乙個可延遲函式中斷。因此,根本不需要同步原語。
相反,要多處理器系統上,競爭條件的確存在。因為幾個可延遲函式可以併發執行。表
5-10
列出所有可能的情況。
由軟中斷訪問的資料結構必須受到保護,通常使用自旋鎖進行保護,因為同乙個軟中斷可以在兩個或多個
cpu上併發執行。相反,僅由一種
tasklet
訪問的資料結構不需要保護,因為同種
tasklet
不能併發執行。但是,如果資料結構被幾種
tasklet
訪問,那麼,就必須對資料結構進行保護。
保護由異常和中斷訪問的資料結構
讓我們考慮一下由異常處理程式和中斷處理程式訪問的資料結構。
在單處理器系統上,競爭條件的防止是相當簡單的,因為中斷處理程式不是可重入的且不能被異常中斷。只要核心以本地中斷禁止訪問資料結構,核心在訪問資料結構的過程中就不會被中斷。不過,如果資料結構正好是被一種中斷處理程式訪問,那麼,中斷處理程式不用禁止本地中斷就可以自由地訪問資料結構。
在多處理器系統上,我們必須關注異常和中斷在其它
cpu上的併發執行。本地中斷禁止還必須外加自旋鎖,強制併發的核心控制路徑等待,直到訪問資料結構的處理程式完成自己的工作。
有時,用訊號量代替自旋鎖可能更好。因為中斷處理程式不能被掛起,它們必須用緊迴圈和
down_trylock()
函式獲得訊號量;對這些中斷處理程式來說,訊號量起的作用本質上與自旋鎖一樣。另一方面,系統呼叫服務例程可以在訊號量忙時掛起呼叫程序。對大部分系統呼叫而言,這是所期望的行為。在這種情況下,訊號量比自旋鎖更好,因為訊號量使系統具有更高的併發度。
保護由異常和可延遲函式訪問的資料結構
異常和可延遲函式都訪問的資料結構與異常和中斷處理程式訪問的資料結構處理方式類似。事實上,可延遲函式本質上是由中斷的出現啟用的,可延遲函式執行時不可能產生異常。因此,把本地中斷禁止與自旋鎖結合起來就足夠了。
實際上,這更加充分:異常處理程式可以通過使用
local_bh_disable()
巨集簡單地禁止可延遲函式,而不禁止本地中斷。僅禁止可延遲函式比禁止中斷更可取,因為中斷還可以繼續在
cpu上得到服務。在每個
cpu上可延遲函式的執行都被序列化。因此,不存在競爭條件。
同樣,在多處理器系統上,要用自旋鎖確保任何時候只有乙個核心控制路徑訪問資料結構。
保護由中斷和可延遲函式訪問的資料結構
這種情況類似於中斷和異常處理程式訪問的資料結構。當可延遲函式執行時可能產生中斷,但是,可延遲函式不能阻止中斷處理程式。因此,必須通過在可延遲函式執行期間禁用本地中斷來避免競爭條件。不過,中斷處理程式可以隨意訪問被可延遲函式訪問的資料結構而不用關中斷,前提是沒有其它的中斷處理程式訪問這個中斷資料結構。
在多處理系統上,還是需要自旋鎖禁止對多個
cpu上資料結構的併發訪問。
核心資料結構
關於開發驅動重要的核心資料結構,方便自己理解 driver object typedef struct driver object cshort type cshort size 乙個鍊錶,記錄了該驅動建立的所有裝置物件 pdevice object deiceobject ulong flags ...
核心資料結構
核心需要儲存i o元件使用的狀態資訊,可以通過若干核心資料結構比如說檔案開啟表等來完成 unix系統中在讀取乙個使用者檔案的時候,核心需要去檢查下快取,然後再去決定是否執行磁碟i o,在讀乙個程序映象時候,核心只需要從記憶體當中讀取資料,也就是說這些操作都可以呼叫read 函式來完成,但是語義不同 ...
核心資料結構
這部分包含了修改核心模組,所以要使用到核心鏈結表資料結構。首先你得定義乙個包含元素的結構去插入鍊錶。下邊這段 用c語言定義了生日結構 struct birthday 注意struct list head list。結構list head在包含目錄的裡有定義。它的作用就是嵌入包含列表節點的鏈結表。li...