信號量(semaphore)與已經介紹過的ipC機構(管道、FIFO以及消息隊列)不同。它是一個計數器,用于多進程對共享數據對象的訪問。
為了獲得共享資源,進程需要執行下列操作:
(1)測試控制該資源的信號量。
(2)若此信號量的值為正,則進程可以使用該資源。進程將信號量值減1,表示它使用了一個資源單位。
(3)若此信號量的值為0,則進程進入休眠狀態,直至信號量值大于0.進程被喚醒后,它返回至第(1)步。
當進程不再使用由一個信號量控制的共享資源時,該信號量值增1。如果有進程正在休眠等待此信號量,則喚醒它們。
為了正確地實現信號量,信號量值的測試及減1操作應當是原子操作。為此,信號量通常是在內核中實現的。
常用的信號量形式被稱為二元信號量或雙態信號量(binary semaphore)。它控制單個資源,初始值為1。但是一般而言,信號量的初值可以是任一正值,該值說明有多少個共享資源單位可供共享應用。
遺憾的是,XSI的信號量與此相比要復雜得多。三種特性造成了這種不必要的復雜性:
(1)信號量并非是單個非負值,而必需將信號量定義為含有一個或多個信號量值的集合。當創建一個信號量時,要指定該集合中信號量值的數量。
(2)創建信號量(semget)與對其賦初值(semctl)分開。這是一個致命的弱點,因為不能原子地創建一個信號量集合,并且對該集合中的各個信號量值賦初值。
(3)即使沒有進程正在使用各種形式的XSI IPC,它們仍然是存在的。有些程序在終止時并沒有釋放已經分配給它的信號量,所以我們不得不為這種程序擔心。下面將要說明的undo功能就是假定要處理這種情況的。
內核為每個信號量集合設置了一個semid_ds結構:
struct semid_ds { struct ipc_perm sem_perm; unsigned short sem_nsems; /* # of semaphores in set */ time_t sem_otime; /* last-semop() time */ time_t sem_ctime; /* last-change time */ ...};
Single UNIX Specification定義了上面所示的各字段,但是具體實現可在semid_ds結構中定義添加的成員。
每個信號量由一個無名結構表示,它至少包含下列成員:
struct { unsigned short semval; /* semaphore value, always >= 0 */ pid_t sempid; /* pid for last Operation */ unsigned short semncnt; /* # PRocesses awaiting semval>curval */ unsigned short semzcnt; /* # processes awaiting semval==0 */};
要獲得一個信號量ID,要調用的第一個函數是semget。
#include <sys/sem.h>int semget(key_t key, int nsems, int flag);返回值:若成功則返回信號量ID,若出錯則返回-1
http://www.CUOXin.com/nufangrensheng/p/3561681.html中標識符與鍵部分,說明了將key轉換為標識符的規則,討論了是否創建一個新集合,或是引用一個現存的集合。
nsems是該集合中的信號量數。如果是創建新集合(一般在服務器進程中),則必須指定nsems。如果引用一個現存的集合(一個客戶進程),則將nsems指定為0。
創建一個新集合時,對semid_ds結構的下列成員賦初值:
semctl函數包含了多種信號量操作。
#include < sys/sem.h>int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);返回值:見下
注意,依賴于所請求的命令,第四個參數是可選的,如果使用該參數,則其類型是semun,它是多個特定命令參數的聯合(union):
union semun { int val; /* for SETVAL */ struct semid_ds *buf; /* for IPC_STAT and IPC_SET */ unsigned short *array; /* for GETALL and SETALL */};
注意,這是一個聯合,而非指向聯合的指針。
cmd參數指定下列10種命令中的一種,在semid指定的信號量集合上執行此命令。其中有5條命令(如下綠色背景所示)是針對一個特定的信號量值的,它們用semnum指定該信號量集合中的一個成員。semnum值在0和nsems-1之間(包括0和nsems-1)。
IPC_STAT 對此集合取semid_ds結構,并存放在由arg.buf指向的結構中。
IPC_SET 按由arg.buf指向結構中的值設置與此集合相關結構中的下列三個字段值:sem_perm.uid、sem_perm.gid和sem_perm.mode。此命令只能由下列兩種進程執行:一種是其有效用戶ID等于sem_perm.cuid或sem_perm.uid的進程;另一種是具有超級用戶特權的進程。
IPC_RMID 從系統中刪除該信號量集合。這種刪除是立即發生的。仍在使用此信號量集合的其他進程在它們下次試圖對此信號量集合進行操作時,將出錯返回EIDRM。此命令只能由下列兩種進程執行:一種是其有效用戶ID等于sem_perm.cuid或sem_perm.uid的進程;另一種是具有超級用戶特權的進程。
GETVAL 返回成員semnum的semval值。
SETVAL 設置成員semnum的semval值。該值由arg.val指定。
GETPID 返回成員semnum的sempid值。
GETNCNT 返回成員semnum的semncnt值。
GETZCNT 返回成員semnum的semzcnt值。
GETALL 取該集合中所有信號量的值,并將它們存放在由arg.array指向的數組中。
SETALL 按arg.array指向的數組中的值,設置該集合中所有信號量的值。
對于除GETALL以外的所有GET命令,semctl函數都返回相應的值。其他命令的返回值為0.
函數semop自動執行信號量集合上的操作數組,這是個原子操作。
#include <sys/sem.h>int semop(int semid, struct sembuf semoparray[], size_t nops);返回值:若成功則返回0,若出錯則返回-1
參數semoparray是一個指針,它指向一個信號量操作數組,信號量操作由sembuf結構表示:
struct sembuf { unsigned short sem_num; /* member # in set ( 0, 1, ..., nsems-1) */ short sem_op; /* operation (negtive, 0, or positive) */ short sem_flag; /* IPC_NOWAIT, SEM_UNDO */};
參數nops規定該數組中操作的數量(元素數)。
對集合中每個成員的操作由相應的sem_op值規定。此值可以是負值、0或正值。
(1)最易于處理的情況是sem_op為正。這對應于進程釋放占用的資源數。sem_op值加到信號量的值上。如果指定了undo標志(此標志對應于相應sem_flag成員的SEM_UNDO位),則也從該進程的此信號量調整值中減去sem_op。
(2)若sem_op為負,則表示要獲取由該信號量控制的資源。
如若該信號量的值大于或等于sem_op的絕對值(具有所需的資源),則從信號量值中減去sem_op的絕對值。這保證信號量的結果值大于或等于0。如果指定了undo標志,則sem_op的絕對值也加到該進程的此信號量調整值上。
如果信號量值小于sem_op的絕對值(資源不能滿足要求),則:
(a)若指定了IPC_NOWAIT,則semop出錯返回EAGAIN。
(b)若未指定IPC_NOWAIT,則該信號量的semncnt值加1(因為調用進程將進入休眠狀態),然后調用進程被掛起直至下列事件之一發生:
(i)此信號量變成大于或等于sem_op的絕對值(即某個進程已釋放了某些資源)。此信號量的semncnt減1(因為已經結束等待),并且從信號量值中減去sem_op的絕對值。如果指定了undo標志,則sem_op的絕對值也加到該進程的此信號量調整值上。
(ii)從系統中刪除了此信號量。在此情況下,函數出錯則返回EIDRM。
(iii)進程捕捉到一個信號,并從信號處理程序返回。在此情況下,此信號量的semncnt值減1(因為調用進程不再等待),并且函數出錯返回EINTR。
(3)若sem_op為0,這表示調用進程希望等待到該信號量值變成0。
如果信號量值當前是0,則此函數立即返回。
如果信號量值非0,則:
(a)若指定了IPC_NOWAIT,則出錯返回EAGAIN。
(b)若未指定IPC_NOWAIT,則該信號量的semzcnt值加1(因為調用進程將進入休眠狀態),然后調用進程被掛起,直至下列事件之一發生為止:
(i)此信號量值變成0。此信號量的semzcnt值減1(因為調用進程已經結束等待)。
(ii)從系統中刪除了此信號量。在此情況下,函數出錯返回EIDRM。
(iii)進程捕捉到一個信號,并從信號處理程序返回。在此情況下此信號量的semzcnt值減1(因為調用進程不再等待),并且函數出錯返回EINTR。
semop函數具有原子性,它或者執行數組中的所有操作,或者什么也不做。
exit時信號量調整
正如前面提到的,如果在進程終止時,它占用了經由信號量分配的資源,那么就會成為一個問題。無論何時,只要為信號量操作指定了SEM_UNDO標志,然后分配資源(sem_op值小于0),那么內核就會記住對于該特定信號量,分配給調用進程多少資源(sem_op的絕對值)。當該進程終止時,不論自愿或者不自愿,內核都將檢驗該進程是否還有尚未處理的信號量調整值,如果有,則按調整值對相應信號量值進行處理。
如果用帶有SETVAL或SETALL命令的semctl設置一信號量的值,則在所有進程中,對于該信號量的調整值都設置為0。
實例:信號量與記錄鎖的耗時比較
如果多個進程共享一個資源,則可使用信號量或記錄鎖。
若使用信號量,則先創建一個包含一個成員的信號量集合,然后對該信號量值賦初值1。為了分配資源,以sem_op為-1調用semop;為了釋放資源,則以sem_op為+1調用semop。對每個操作都指定SEM_UNDO,以處理在未釋放資源條件下進程終止的情況。
若使用記錄鎖,則先創建一個空文件,并且用該文件的第一個字節(無需存在)作為鎖字節。為了分配資源,先對該字節獲得一個寫鎖;釋放該資源時,則對該字節解鎖。記錄鎖的性質確保了當一個鎖的屬主進程終止時,內核會自動釋放該鎖。
在linux上,記錄鎖與信號量鎖相比,在時間上要多耗時約60%。
雖然記錄鎖慢于信號量鎖,但如果只需要鎖一個資源(例如共享存儲段)并且不需要使用XSI信號量的所有花哨的功能,則寧可使用記錄鎖。理由是使用簡易,且進程終止時系統會處理任何遺留下來的鎖。
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關于本書可參考:http://www.apuebook.com/。
新聞熱點
疑難解答