。下面就進入訊號量的講解。
一、什麼是訊號量
為了防止出現因多個程式同時訪問乙個共享資源而引發的一系列問題,我們需要一種方法,它可以通過生成並使用令牌來授權,在任一時刻只能有乙個執行執行緒訪問**的臨界區域。臨界區域是指執行資料更新的**需要獨佔式地執行。而訊號量就可以提供這樣的一種訪問機制,讓乙個臨界區同一時間只有乙個執行緒在訪問它,也就是說訊號量是用來調協程序對共享資源的訪問的。
訊號量是乙個特殊的變數,程式對其訪問都是原子操作,且只允許對它進行等待(即p(訊號變數))和傳送(即v(訊號變數))資訊操作。最簡單的訊號量是只能取0和1的變數,這也是訊號量最常見的一種形式,叫做二進位制訊號量。而可以取多個正整數的訊號量被稱為通用訊號量。這裡主要討論二進位制訊號量。
二、訊號量的工作原理
由於訊號量只能進行兩種操作等待和傳送訊號,即p(sv)和v(sv),他們的行為是這樣的:
p(sv):如果sv的值大於零,就給它減1;如果它的值為零,就掛起該程序的執行
v(sv):如果有其他程序因等待sv而被掛起,就讓它恢復執行,如果沒有程序因等待sv而掛起,就給它加1.
舉個例子,就是兩個程序共享訊號量sv,一旦其中乙個程序執行了p(sv)操作,它將得到訊號量,並可以進入臨界區,使sv減1。而第二個程序將被阻止進入臨界區,因為當它試圖執行p(sv)時,sv為0,它會被掛起以等待第乙個程序離開臨界區域並執行v(sv)釋放訊號量,這時第二個程序就可以恢復執行。
三、linux的訊號量機制
linux提供了一組精心設計的訊號量介面來對訊號進行操作,它們不只是針對二進位制訊號量,下面將會對這些函式進行介紹,但請注意,這些函式都是用來對成組的訊號量值進行操作的。它們宣告在標頭檔案sys/sem.h中。
1、semget函式
它的作用是建立乙個新訊號量或取得乙個已有訊號量,原型為:
int semget(key_t key, int num_sems, int sem_flags);
第乙個引數key是整數值(唯一非零),不相關的程序可以通過它訪問乙個訊號量,它代表程式可能要使用的某個資源,程式對所有訊號量的訪問都是間接的,程式先通過呼叫semget函式並提供乙個鍵,再由系統生成乙個相應的訊號識別符號(semget函式的返回值),只有semget函式才直接使用訊號量鍵,所有其他的訊號量函式使用由semget函式返回的訊號量識別符號。如果多個程式使用相同的key值,key將負責協調工作。
第二個引數num_sems指定需要的訊號量數目,它的值幾乎總是1。
第三個引數sem_flags是一組標誌,當想要當訊號量不存在時建立乙個新的訊號量,可以和值ipc_creat做按位或操作。設定了ipc_creat標誌後,即使給出的鍵是乙個已有訊號量的鍵,也不會產生錯誤。而ipc_creat | ipc_excl則可以建立乙個新的,唯一的訊號量,如果訊號量已存在,返回乙個錯誤。
semget函式成功返回乙個相應訊號識別符號(非零),失敗返回-1.
2、semop函式
它的作用是改變訊號量的值,原型為:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
sem_id是由semget返回的訊號量識別符號,sembuf結構的定義如下:
struct sembuf;
3、semctl函式
該函式用來直接控制訊號量資訊,它的原型為:
int semctl(int sem_id, int sem_num, int command, ...);
如果有第四個引數,它通常是乙個union semum結構,定義如下:
union semun;
前兩個引數與前面乙個函式中的一樣,command通常是下面兩個值中的其中乙個
setval:用來把訊號量初始化為乙個已知的值。p 這個值通過union semun中的val成員設定,其作用是在訊號量第一次使用前對它進行設定。
ipc_rmid:用於刪除乙個已經無需繼續使用的訊號量識別符號。
四、程序使用訊號量通訊
下面使用乙個例子來說明程序間如何使用訊號量來進行通訊,這個例子是兩個相同的程式同時向螢幕輸出資料,我們可以看到如何使用訊號量來使兩個程序協調工作,使同一時間只有乙個程序可以向螢幕輸出資料。注意,如果程式是第一次被呼叫(為了區分,第一次呼叫程式時帶乙個要輸出到螢幕中的字元作為乙個引數),則需要呼叫set_semvalue函式初始化訊號並將message字元設定為傳遞給程式的引數的第乙個字元,同時第乙個啟動的程序還負責訊號量的刪除工作。如果不刪除訊號量,它將繼續在系統中存在,即使程式已經退出,它可能在你下次執行此程式時引發問題,而且訊號量是一種有限的資源。
在main函式中呼叫semget來建立乙個訊號量,該函式將返回乙個訊號量識別符號,儲存於全域性變數sem_id中,然後以後的函式就使用這個識別符號來訪問訊號量。
原始檔為seml.c,**如下:
#include #include #include #include #include #include #include #include union semun
;static int sem_id = 0;
static int set_semvalue();
static void del_semvalue();
static int semaphore_p();
static int semaphore_v();
int main(int argc, char *argv)
//設定要輸出到螢幕中的資訊,即其引數的第乙個字元
message = argv[1][0];
sleep(2);
} for(i = 0; i < 10; ++i)
sleep(10);
printf("\n%d - finished\n", getpid());
if(argc > 1)
exit(exit_success);
}static int set_semvalue()
static void del_semvalue()
static int semaphore_p()
return 1;
}static int semaphore_v()
return 1;
}
執行結果如下:
注:這個程式的臨界區為main函式for迴圈不的semaphore_p和semaphore_v函式中間的**。
例子分析:同時執行乙個程式的兩個例項,注意第一次執行時,要加上乙個字元作為引數,例如本例中的字元『o』,它用於區分是否為第一次呼叫,同時這個字元輸出到螢幕中。因為每個程式都在其進入臨界區後和離開臨界區前列印乙個字元,所以每個字元都應該成對出現,正如你看到的上圖的輸出那樣。在main函式中迴圈中我們可以看到,每次程序要訪問stdout(標準輸出),即要輸出字元時,每次都要檢查訊號量是否可用(即stdout有沒有正在被其他程序使用)。所以,當乙個程序a在呼叫函式semaphore_p進入了臨界區,輸出字元後,呼叫sleep時,另乙個程序b可能想訪問stdout,但是訊號量的p請求操作失敗,只能掛起自己的執行,當程序a呼叫函式semaphore_v離開了臨界區,程序b馬上被恢復執行。然後程序a和程序b就這樣一直迴圈了10次。
五、對比例子——程序間的資源競爭
看了上面的例子,你可能還不是很明白,不過沒關係,下面我就以另乙個例子來說明一下,它實現的功能與前面的例子一樣,執行方式也一樣,都是兩個相同的程序,同時向stdout中輸出字元,只是沒有使用訊號量,兩個程序在互相競爭stdout。它的**非常簡單,檔名為normalprint.c,**如下:
#include #include int main(int argc, char *argv)
sleep(10);
printf("\n%d - finished\n", getpid());
exit(exit_success);
}
執行結果如下:
例子分析:
從上面的輸出結果,我們可以看到字元『x』和『o』並不像前面的例子那樣,總是成對出現,因為當第乙個程序a輸出了字元後,呼叫sleep休眠時,另乙個程序b立即輸出並休眠,而程序a醒來時,再繼續執行輸出,同樣的程序b也是如此。所以輸出的字元就是不成對的出現。這兩個程序在競爭stdout這一共同的資源。通過兩個例子的對比,我想訊號量的意義和使用應該比較清楚了。
六、訊號量的總結
訊號量是乙個特殊的變數,程式對其訪問都是原子操作,且只允許對它進行等待(即p(訊號變數))和傳送(即v(訊號變數))資訊操作。我們通常通過訊號來解決多個程序對同一資源的訪問競爭的問題,使在任一時刻只能有乙個執行執行緒訪問**的臨界區域,也可以說它是協調程序間的對同一資源的訪問權,也就是用於同步程序的。
詳解Linux程序間通訊 使用訊號量
一 什麼是訊號量 為了防止出現因多個程式同時訪問乙個共享資源而引發的一系列問題,我們需要一種方法,它可以通過生成並使用令牌來授權,在任一時刻只能有乙個執行執行緒訪問 的臨界區域。臨界區域是指執行資料更新的 需要獨佔式地執行。而訊號量就可以提供這樣的一種訪問機制,讓乙個臨界區同一時間只有乙個執行緒在訪...
linux 訊號量(程序間通訊)
將使用乙個程式來演示訊號量的使用,程式用pv操作控制訊號量,以操作臨界區,p操作讓訊號量減1,v操作讓訊號量加1,而pv操作之間的 即為臨界區關鍵 每次只能由乙個程序訪問。程式建立出乙個子程序,在兩個程序中分別有一段臨界區關鍵 實現的功能都是不斷的順序輸出0 9的字元。保證程序間同步 plain v...
linux 程序間通訊 訊號量
例項中首先使用fork 建立乙個子程序,在父程序呼叫kill 之前,在子程序中使用raise 向自身傳送sigstop訊號,是子程序暫停。接下來使用kill 向子程序傳送訊號 ngnsvr9 none home xionghailong example cat kill raise.c includ...