互斥鎖,我們剛剛討論過了,通常認為是最簡單的同步工具。本節將會討論乙個更棒的工具,它的功能類似於互斥鎖,但是它能提供更為高階的方法,以便程序能夠同步活動。
乙個訊號量 s 是個整型變數,它除了初始化外只能通過兩個標準原子操作:wait () 和 signal() 來訪問:
操作 wait() 最初稱為 p(荷蘭語proberen,測試);
操作 signal() 最初稱為 v(荷蘭語verhogen,增加);
可按如下來定義wait ():
wait(s) semaphore;
每個訊號量都有乙個整數 value 和乙個程序鍊錶 list。當乙個程序必須等待訊號量時,就被新增到程序鍊錶。操作 signal() 從等待、程序鍊錶上取走乙個程序,並加以喚醒。
現在,訊號量操作 wait() 可以定義如下:
wait(semaphore *s) {
s->value--;
if (s->value < 0) {
add this process to s->list;
block();
而訊號量操作 signal() 可定義如下:
signal(semaphore *s) {
s->value++;
if (s->value <= 0) {
remove a process p from s->list;
wakeup(p);
操作 block() 掛起呼叫它的程序。操作 wakeup(p) 重新啟動阻塞程序 p 的執行。這兩個操作都是由作業系統作為基本系統呼叫來提供的。
注意,這樣實現的訊號量的值可以是負數,而在具有忙等待的訊號量經典定義下,訊號量的值不能為負。如果訊號量的值為負,那麼它的絕對值就是等待它的程序數。出現這種情況源於,在實現操作 wait() 時互換了遞減和測試的順序。
通過每個程序控制塊 pcb 的乙個鏈結字段,等待程序的鍊錶可以輕鬆實現。每個訊號量包括乙個整數和乙個 pcb 鍊錶指標。向鍊錶中增加和刪除程序以便確保有限等待的一種方法採用 fifo 佇列,這裡的訊號量包括佇列的首指標和尾指標。然而,一般來說,鍊錶可以使用任何排隊策略。訊號量的正確使用不依賴於訊號量鍊錶的特定排隊策略。
關鍵的是,訊號量操作應原子執行。我們應保證:對同一訊號量,沒有兩個程序可以同時執行操作 wait() 和 signal()。這是乙個臨界區問題。
對於單處理器環境,在執行操作 wait() 和 signal() 時,可以簡單禁止中斷。這種方案在單處理器環境下能工作,這是因為一旦中斷被禁用,不同程序指令不會交織在一起。只有當前執行程序一直執行,直到中斷 被重新啟用並且排程程式重新獲得控制。
對於多處理器環境,每個處理器的中斷都應被禁止;否則,在不同處理器上不同的執行程序可能會以任意不同方式一起交織執行。每個處理器中斷的禁止會很困難,也會嚴重影響效能。因此,smp 系統應提供其他加鎖技術,如 compare_and__swap() 或自旋鎖,以確保 wait() 與 signal() 原子執行。
重要的是,對於這裡定義的操作 wait() 和 signal(),我們並沒有完全取消忙等待。我們只是將忙等待從進入區移到臨界區。此外,我們將忙等待限制在操作 wait() 和 signal() 的臨界區內,這些區比較短(如經合理編碼,它們不會超過 10 條指令)。因此,臨界區幾乎不被占用,忙等待很少發生,而且所需時間很短。對於應用程式,存在一種完全不同的情況,即臨界區可能很長(數分鐘或數小時)或幾乎總是被占用。在這種情況下,忙等待極為低效。
死鎖與飢餓
具有等待佇列的訊號量實現可能導致這樣的情況:兩個或多個程序無限等待乙個事件,而該事件只能由這些等待程序之一來產生。當出現這樣的狀態時,這些程序就為死鎖。
為了說明起見,假設有乙個系統,它有兩個程序 p0 和 p1,每個訪問共享訊號量 s 和 q,這兩個訊號量的初值均為 1:
p0p1
wait(s);
wait(q);
wait(q);
wait(s);
signal(s);
signal(q);
signal(q);
signal(s);
假設 p0 執行 wait(s),接著 p1 執行 wait(q)。當 p0 執行 wait(q) 時,它必須等待直到 p1 執行 signal(q)。類似地,當 p1 執行 wait(s) 時,它必須等待直到 p0 執行 signal(s)。由於這兩個操作 signal() 都不能執行,這樣 p0 和 p1 就死鎖了。
我們說一組程序處於死鎖狀態:組內的每個程序都等待乙個事件,而該事件只可能由組內的另乙個程序產生。這裡主要關心的事件是資源的獲取和釋放。然而,其他型別的事件也能導致死鎖。
與死鎖相關的另乙個問題是無限阻塞或飢餓,即程序無限等待訊號量。如果對與訊號量有關的鍊錶按 lifo 順序來增加和刪除程序,那麼可能發生無限阻塞。
優先順序的反轉
如果乙個較高優先順序的程序需要讀取或修改核心資料,而且這個核心資料當前正被較低優先順序的程序訪問(這種串聯方式可涉及更多程序),那麼就會出現乙個排程挑戰。由於核心資料通常是用鎖保護的,較高優先順序的程序將不得不等待較低優先順序的程序用完資源。如果較低優先順序的程序被較高優先順序的程序搶占,那麼情況變得更加複雜。
比如,假設有三個程序 l、m 和 h,它們的優先順序順序為 l
這個問題稱為優先順序反轉。它只出現在具有兩個以上優先順序的系統中,因此乙個解決方案是只有兩個優先順序。然而,這對於大多數通用作業系統是不夠的。通常,這些系統在解決問題時採用優先順序繼承協議。
根據這個協議,所有正在訪問資源的程序獲得需要訪問它的更高優先順序程序的優先順序,直到它們用完了有關資源為止。當它們用完時,它們的優先順序恢復到原始值。在上面的示例中,優先順序繼承協議將允許程序 l 臨時繼承程序 h 的優先順序,從而防止程序 m 搶占執行。當程序 l 用完資源 r 時,它將放棄繼承的程序 h 的優先順序,以採用原來的優先順序。因為資源 r 現在可用,程序 h(而不是程序 m)會接下來執行。
訊號量 二值訊號量
訊號量 二值訊號量 訊號量是作業系統的重要部分,訊號量一般用來進行資源管理和任務同步。freertos中訊號量分為二值訊號量 互斥訊號量 計數訊號量和遞迴互斥訊號量,應用場景各不同。二值訊號量通常用於互斥訪問或同步,二值訊號量和互斥訊號量非常相似,但互斥訊號量有優先順序,二值訊號量沒有。因此二值訊號...
python訊號量 Python訊號量
python訊號量教程 訊號量是由作業系統管理的一種抽象資料型別,用於在多執行緒中同步對共享資源的使用。本質上說,訊號量是乙個內部資料,用於標明當前的共享資源可以有多少併發讀取。也可以簡單的理解為,訊號量是多把鎖,同時允許多個執行緒來更改資料,而 python訊號量與互斥鎖的關係 訊號量的乙個特殊用...
訊號 訊號量
訊號是由 系統或者程序傳送給目標程序的資訊,以通知目標程序某個狀態的改變或系統異常。linux訊號可以由如下條件產生 1 對於前台程序,使用者可以通過輸入特殊的終端字元來給它傳送訊號。比如輸入ctrl c通常或給程序傳送乙個中斷訊號 2 系統異常。比如浮點異常和非法記憶體段訪問。3 系統狀態變化 4...