Linux 進程間的通信(四)—信號量
信號量
爲了防止出現因多個程序同時訪問一個共享資源而引發的一系列問題,我們需要一種方法,它可以通過生成並使用令牌來授權
,在任一時刻只能有一個
執行線程訪問代碼的臨界區域
。臨界區域
是指執行數據更新的代碼需要獨佔式地執行。而信號量就可以提供這樣的一種訪問機制,讓一個臨界區同一時間只有一個線程在訪問它,也就是說信號量是用來調協進程對共享資源的訪問的。
工作原理
由於信號量只能進行兩種操作等待和發送信號,即P(sv)和V(sv),他們的行爲是這樣的:
P(sv):如果sv的值大於零,就給它減1;如果它的值爲零,就掛起該進程的執行
V(sv):如果有其他進程因等待sv而被掛起,就讓它恢復運行,如果沒有進程因等待sv而掛起,就給它加1.
舉個例子,就是兩個進程共享信號量sv,一旦其中一個進程執行了P(sv)操作,它將得到信號量,並可以進入臨界區,使sv減1。而第二個進程將被阻止進入臨界區,因爲當它試圖執行P(sv)時,sv爲0,它會被掛起以等待第一個進程離開臨界區域並執行V(sv)釋放信號量,這時第二個進程就可以恢復執行
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.
semop函數
它的作用是改變信號量的值,原型爲:
int semop(int semid, struct sembuf *sops, unsigned nsops);
semop()函數第二個參數(sops)是一個指針,指向將要在信號量集合上執行的操作的一個數組,而第三個參數(nsops)則是該數組中操作的個數。sops參數指向的是類型爲sembuf的結構的一個數組。sembuf結構是在linux/sem.h中定義的,如下所示:
struct sembuf{ ushort sem_num; /*信號量的編號*/ short sem_op; /*信號量的操作*/ short sem_flg; /*信號量的操作標誌可設置爲IPC_NOWAIT或SEM_UNDO(防止因程序崩潰導致信號量無法恢復)*/ };
如果sem_op爲負,則從信號量中減掉一個值。如果sem_op爲正,則從信號量中加上值。如果sem_op爲零,並且沒有設置IPC_NOWAIT,則調用進程將睡眠,直到信號量的值爲零爲止;否則, 直接返回EAGAIN
semctl函數
int semctl(int semid, int semnum, int cmd, ...);
semctl()的第一個參數是關鍵字的值(在我們的例子中它是調用semget所返回的值)。第二個參數(semun)是將要執行操作的信號量的編號,它是信號量集合的一個索引值,對於集合中的第一個信號量(有可能只有這一個信號量)來說,它的索引值將是一個爲零的值。cmd參數代表將要在集合上執行的命令。其取值如下:
Command:
SETVAL:用來把信號量初始化爲一個已知的值。p 這個值通過union semun中的val成員設置,其作用是在信號量第一次使用前對它進行設置。
IPC_RMID:用於刪除一個已經無需繼續使用的信號量標識符。
GETVAL:返回集合中某個信號量的值。如果有第四個參數,它通常是一個union semum結構,定義如下:
union semun{ int val; struct semid_ds *buf; unsigned short *arry; };
案例
/* ************************************************************************
* Filename: sem.c
* Description:
* Version: 1.0
* Created: 05/12/2020 10:32:37 PM
* Revision: none
* Compiler: gcc
* Author: YOUR NAME (WCT),
* Company:
* ************************************************************************/
#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 *bug;
unsigned short *arry;
};
static int sem_id = 0;
// 初始化設置信號令的值
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");
}
}
// 對信號量進行 P 操作
static int semaphore_p(){
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;
}
// 對信號量進行 V 操作
static int semaphore_v(){
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;
}
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 semaphhore\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();
}
return 0;
}