Linux进程间通信(2)信号量
Linux信號量
- 信號量的背景
- 信號量的含義:
- 信號量描述
- 創建和初始化信號量
- 信號量接口介紹
- 信號量使用(例題)
信號量的背景
信號量在1968年由Edsger Wybe DijKstra9提出,此后它逐漸成為一種常用的鎖機制。信號量支持兩個原子操作P(和V(),這兩個名字來自荷蘭語Proberen和Vershogen。前者叫做測試操作(字面意思是探查),后者叫做增加操作。后來的系統把兩種操作分別叫做down()和up0, Linux也遵從這種叫法down()操作通過對信號量計數減1來請求獲得-一個信 號量。如果結果是0或大于0,獲得信號量鎖,任務就可以進入臨界區。如果結果是負數,任務會被放入等待隊列,處理器執行其他任務。該函數如同一個動詞,降低(down) 一個信號量就等于獲取該信號量。相反,當臨界區中的操作完成后,up0操作用來釋放信號量,該操作也被稱作是提升(upping) 信號量,因為它會增加信號量的計數值。如果在該信號量上的等待隊列不為空,那么處于隊列中等待的任務在被喚醒的同時會獲得該信號量。
信號量的含義:
Linux中的信號量是一種睡眠鎖。 如果有一個任務試圖獲得一個已經被占用的信號量時,信號
量會將其推進一個 等待隊列,然后讓其睡眠。這時處理器能重獲自由,從而去執行其他代碼。當
持有信號量的進程將信號量釋放后,處于等待隊列中的那個任務將被喚醒,并獲得該信號量。讓我們再一次回到門和鑰匙的例子。當某個人到了門前,他抓取鑰匙,然后進入房間。最大的差異在于當另一個人到了門前,但無法得到鑰匙時會發生什么情況。在這種情況下,這家伙不是在徘徊等待,而是把自己的名字寫在一一個列表中,然后打盹去了。當里面的人離開房間時,就在門口查看一下列表。 如果列表上有名字,他就對第一個名字仔細檢查,并在胸部給他一拳,叫醒他,讓他進入房間。在這種方式中,鑰匙(相當于信號量)繼續確保一次只有一個人(相當于執行線程)進入房間(相當于臨界區)。如果房間被占用,那么,那個人不是徘徊等待,而是把自己的名字寫在列表(相當于等待隊列)上,然后打一會兒盹(相當于在等待隊列上阻塞,并且入睡),讓處理器離開這里到別的地方執行代碼。這就比自旋鎖提供了更好的處理器利用率,因為沒有把時間花費在忙等上,但是,信號量比自旋鎖有更大的開銷。生活總是一分為二的。
我們可以從信號量的睡眠特性得出一些有意思的結論:
●由于爭用信號量的進程在等待鎖重新變為可用時會睡眠,所以信號量適用于鎖會被長時間持有的情況。
●相反,鎖被短時間持有時,使用信號量就不太適宜了。因為睡眠、維護等待隊列以及喚醒所花費的開銷可能比鎖被占用的全部時間還要長。
●由于執行線程在鎖被爭時會睡眠,所以只能在進程上下文中才能獲取信號量鎖,因為在中斷上下文中是不能進行調度的。
●你可以在持有信號量時去睡眠(當然你也可能并不需要睡眠),因為當其他進程試圖獲得同一信號量時不會因此而死鎖(因為該進程也只是去睡眠而已,而你最終會繼續執行的)。
●在你占用信號量的同時不能占用自旋鎖。因為在你等待信號量時可能會睡眠,而在持有自旋鎖時是不允許睡眠的。
信號量描述
信號量是一個特殊的變量,一般取正數值。它的值代表允許訪問的資源數目,獲取資源時,需要對信號量的值進行原子減一,該操作被稱為 P 操作。當信號量值為 0 時,代表沒有資源可用,P 操作會阻塞。釋放資源時,需要對信號量的值進行原子加一,該操作被稱為 V操作。信號量主要用來同步進程。信號量的值如果只取 0,1,將其稱為二值信號量。如果信號量的值大于 1,則稱之為計數信號量。
臨界資源:同一時刻,只允許被一個進程或線程訪問的資源臨界區:訪問臨界資源的代碼段
創建和初始化信號量
信號量的實現是與體系結構相關的,具體實現定義在文件**<asm/semaphore.h>**中。struct semaaphore類型用來表示信號量。可以通過以下方式靜態地聲明信號量:
- static DECLARE SEMAPHORE GENERIC ( name , count )
注釋:其中name是信號量變量名,count是信號量的使用者數量。創建更為普通的互斥信號量可以使用以下快捷方式;
- static DECLARE_ MUTEX (name) ;
注釋:name同樣指信號量變量名。更常見的情況是,信號量作為一個大數據結構的一部分被動態創建。此時,你只有指向該動態創建的信號量的間接指針,你可以使用如下函數來對它進行初始化:
- sema_ init ( sem, count);
注釋: sem是指針,count是 信號量的使用者數量。與前面類似,初始化-一個動態創建的互斥信號量時使用以下函數:
- init_ MUTEX( sem) ;
信號量接口介紹
頭文件:
#include <sys/sem.h>#include <sys/types.h>#include <sys/ipc.h>- int semget(key_t key, int nsems, int semflg);
- *int semop( int semid, struct sembuf sops, unsigned nsops);
- int semctl( int semid, int semnum, int cmd, …);
信號量使用(例題)
例題:進程 a 和進程 b 模擬訪問打印機,進程 a 輸出第一個字符‘a’表示開始使用打印機,輸出第二個字符‘a’表示結束使用,b 進程操作與 a 進程相同。(由于打印機同一時刻只能被一個進程使用,所以輸出結果不應該出現 abab),如圖所示:
sem.h
sem.c
#include"sem.h"static int semid=-1;void sem_init() {semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);if(semid==-1){semid=semget((key_t)1234,1,IPC_CREAT|0600);if(semid==-1){printf("creat sem failed\n");return ;}}else{union semun a;a.val=1;if(semctl(semid,0,SETVAL,a)==-1)//semid返回的信號量標識符 0信號量編號 ,setval將信號量初始化初始化為一個已知的值,也就是union semun中的val 其作用是在信號量第一次使用之前進行設置{printf("semctl setval err\n");return;}} } int sem_p() {struct sembuf buf;buf.sem_num=0;//信號量編號buf.sem_op=-1;//pbuf.sem_flg=SEM_UNDO;//使操作系統跟蹤當前進程對這個信號的修改情況if(semop(semid,&buf,1)==-1){printf("semop p err\n");return -1;}return 0; }int sem_v() {struct sembuf buf;buf.sem_num=0;buf.sem_op=1;buf.sem_flg=SEM_UNDO;if(semop(semid,&buf,1)==-1){printf("semop v err\n");return -1;}return 0; }void sem_destroy() {if(semctl(semid,0,IPC_RMID)==-1)//IPC_RMID刪除一個無關繼續使用的信號量標識符{perror("semctl del error");} }a.c
#include"sem.h"int main() {sem_init();int i=0;for( ; i<5;i++){sem_p();printf("A");fflush(stdout);int n=rand()%3;sleep(n);printf("A");fflush(stdout);sem_v();n=rand()%3;sleep(n);}}b.c
#include"sem.h"int main() {sem_init();int i=0;for( ; i<5;i++){sem_p();printf("B");fflush(stdout);int n=rand()%3;sleep(n);printf("B");fflush(stdout);sem_v();n=rand()%3;sleep(n);}sleep(10);sem_destroy(); }
over!
總結
以上是生活随笔為你收集整理的Linux进程间通信(2)信号量的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【KITTI】KITTI数据集简介(一)
- 下一篇: SVNadmin