Linux處理序間通訊——使用訊號量

來源:互聯網
上載者:User

這篇文章將講述別一種處理序間通訊的機制——訊號量。注意請不要把它與之前所說的訊號混淆起來,訊號與訊號量是不同的兩種事物。有關訊號的更多內容,可以閱讀我的另一篇文章:Linux處理序間通訊——使用訊號。下面就進入訊號量的講解。

一、什麼是訊號量為了防止出現因多個程式同時訪問一個共用資源而引發的一系列問題,我們需要一種方法,它可以通過產生並使用令牌來授權,在任一時刻只能有一個執行線程存取碼的臨界地區。臨界地區是指執行資料更新的代碼需要獨佔式地執行。而訊號量就可以提供這樣的一種訪問機制,讓一個臨界區同一時間只有一個線程在訪問它,也就是說訊號量是用來調協進程對共用資源的訪問的。訊號量是一個特殊的變數,程式對其訪問都是原子操作,且只允許對它進行等待(即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{    short sem_num;//除非使用一組訊號量,否則它為0    short sem_op;//訊號量在一次操作中需要改變的資料,通常是兩個數,一個是-1,即P(等待)操作,                    //一個是+1,即V(發送訊號)操作。    short sem_flg;//通常為SEM_UNDO,使作業系統跟蹤訊號,                    //並在進程沒有釋放該訊號量而終止時,作業系統釋放訊號量};
3、semctl函數該函數用來直接控制訊號量資訊,它的原型為:
int semctl(int sem_id, int sem_num, int command, ...);
如果有第四個參數,它通常是一個union semum結構,定義如下:
union semun{    int val;    struct semid_ds *buf;    unsigned short *arry;};
前兩個參數與前面一個函數中的一樣,command通常是下面兩個值中的其中一個SETVAL:用來把訊號量初始化為一個已知的值。p 這個值通過union semun中的val成員設定,其作用是在訊號量第一次使用前對它進行設定。IPC_RMID:用於刪除一個已經無需繼續使用的訊號量標識符。四、進程使用訊號量通訊下面使用一個例子來說明進程間如何使用訊號量來進行通訊,這個例子是兩個相同的程式同時向螢幕輸出資料,我們可以看到如何使用訊號量來使兩個進程協調工作,使同一時間只有一個進程可以向螢幕輸出資料。注意,如果程式是第一次被調用(為了區分,第一次調用程式時帶一個要輸出到螢幕中的字元作為一個參數),則需要調用set_semvalue函數初始化訊號並將message字元設定為傳遞給程式的參數的第一個字元,同時第一個啟動的進程還負責訊號量的刪除工作。如果不刪除訊號量,它將繼續在系統中存在,即使程式已經退出,它可能在你下次運行此程式時引發問題,而且訊號量是一種有限的資源。在main函數中調用semget來建立一個訊號量,該函數將返回一個訊號量標識符,儲存於全域變數sem_id中,然後以後的函數就使用這個標識符來訪問訊號量。源檔案為seml.c,代碼如下:
#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdlib.h>#include <stdio.h>#include <string.h>#include <sys/sem.h>union semun{int val;struct semid_ds *buf;unsigned short *arry;};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[]){char message = 'X';int i = 0;//建立訊號量sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);if(argc > 1){//程式第一次被調用,初始化訊號量if(!set_semvalue()){fprintf(stderr, "Failed to initialize semaphore\n");exit(EXIT_FAILURE);}//設定要輸出到螢幕中的資訊,即其參數的第一個字元message = argv[1][0];sleep(2);}for(i = 0; i < 10; ++i){//進入臨界區if(!semaphore_p())exit(EXIT_FAILURE);//向螢幕中輸出資料printf("%c", message);//清理緩衝區,然後休眠隨機時間fflush(stdout);sleep(rand() % 3);//離開臨界區前再一次向螢幕輸出資料printf("%c", message);fflush(stdout);//離開臨界區,休眠隨機時間後繼續迴圈if(!semaphore_v())exit(EXIT_FAILURE);sleep(rand() % 2);}sleep(10);printf("\n%d - finished\n", getpid());if(argc > 1){//如果程式是第一次被調用,則在退出前刪除訊號量sleep(3);del_semvalue();}exit(EXIT_SUCCESS);}static int set_semvalue(){//用於初始化訊號量,在使用訊號量前必須這樣做union semun sem_union;sem_union.val = 1;if(semctl(sem_id, 0, SETVAL, sem_union) == -1)return 0;return 1;}static void del_semvalue(){//刪除訊號量union semun sem_union;if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)fprintf(stderr, "Failed to delete semaphore\n");}static int semaphore_p(){//對訊號量做減1操作,即等待P(sv)struct sembuf sem_b;sem_b.sem_num = 0;sem_b.sem_op = -1;//P()sem_b.sem_flg = SEM_UNDO;if(semop(sem_id, &sem_b, 1) == -1){fprintf(stderr, "semaphore_p failed\n");return 0;}return 1;}static int semaphore_v(){//這是一個釋放操作,它使訊號量變為可用,即發送訊號V(sv)struct sembuf sem_b;sem_b.sem_num = 0;sem_b.sem_op = 1;//V()sem_b.sem_flg = SEM_UNDO;if(semop(sem_id, &sem_b, 1) == -1){fprintf(stderr, "semaphore_v failed\n");return 0;}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 <stdio.h>#include <stdlib.h>int main(int argc, char *argv[]){char message = 'X';int i = 0;if(argc > 1)message = argv[1][0];for(i = 0; i < 10; ++i){printf("%c", message);fflush(stdout);sleep(rand() % 3);printf("%c", message);fflush(stdout);sleep(rand() % 2);}sleep(10);printf("\n%d - finished\n", getpid());exit(EXIT_SUCCESS);}
運行結果如下:例子分析:從上面的輸出結果,我們可以看到字元‘X’和‘O’並不像前面的例子那樣,總是成對出現,因為當第一個進程A輸出了字元後,調用sleep休眠時,另一個進程B立即輸出並休眠,而進程A醒來時,再繼續執行輸出,同樣的進程B也是如此。所以輸出的字元就是不成對的出現。這兩個進程在競爭stdout這一共同的資源。通過兩個例子的對比,我想訊號量的意義和使用應該比較清楚了。六、訊號量的總結訊號量是一個特殊的變數,程式對其訪問都是原子操作,且只允許對它進行等待(即P(訊號變數))和發送(即V(訊號變數))資訊操作。我們通常通過訊號來解決多個進程對同一資源的訪問競爭的問題,使在任一時刻只能有一個執行線程存取碼的臨界地區,也可以說它是協調進程間的對同一資源的訪問權,也就是用於同步進程的。
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.