程序間通訊之——訊號量(一)(system V)
system v訊號量又被稱為system v訊號量集。它的本質就是一個計數器,用來為多個程序共享的資料結構提供受控訪問。
訊號量支援的操作有:
使用最廣泛的訊號量為二元訊號量,它控制單個資源,對於這種訊號量而言,它只有兩種合法值: 0 和 1 ,對應一個可用的資源。若當前有資源可用,則與之對應的二值訊號量的值為 1 ;若資源已被佔用,則與之對應的二值訊號量的值為 0 。當程序申請資源時,如果當前訊號量的值為 0 ,那麼程序會陷入阻塞,直到有其他程序釋放資源,將訊號量的值加 1 才能被喚醒。
核心為每個訊號量集維護者一個semid_ds結構,該結構定義了此訊號量的許可權、指標、最近修改時間和佇列中訊號量佇列資訊:/usr /src/kernels/2.6.32-431.el6.i686/include/Linux/sem.h
——sem_perm許可權;
——sem_otime最近semop時間;
——sem_ctime最近修改時間;
——sem_base佇列第一個訊號量;
——sem_pending阻塞訊號量;
——sem_pending_last最後一個阻塞訊號量;
——undo表示undo佇列;
——sem_nsems表示訊號量個數;
每一個訊號量還有一個結構:
——semval表示訊號量值;
——sempid表示最近一個操作的程序號pid;
當然訊號量仍然存在系統限制:
——SEMMNI系統容許的訊號量集的上限;
——SEMMSL 單個訊號量集中訊號量的上限;
——SEMMNS 系統容許的訊號量的上限
——SEMOPM 單次 semop 呼叫能夠操作的訊號量的最大值;
——SEMVMX 訊號量值的上限;
訊號量集函式
semget
功能:用來建立或開啟一個訊號量集;
原型:int semget(key_t key, int nsems, int semflg);
引數:key表示訊號集的名字,一般由ftok函式產生;
nsems表示訊號集中訊號量的個數;
semflg用來標識訊號量集合的許可權,和建立檔案類似,由九個許可權標誌為構成如0644,他還可以和以下引數一起使用:
——IPC_CREAT表示如果key不存在就建立;
——IPC_EXCL表示如果key存在就返回失敗;
——IPC_NOWAIT表示如果需要等待,則直接返回錯誤;
返回值:成功
semctl
功能:用來控制訊號量集;
原型:int semctl(int semid, int semnum, int cmd, ...);
引數:semid由semget返回的訊號量標識;
semnum訊號集中訊號量的序號;
cmd表示將要採取的操作;
最後一個引數根據命令不同而不同,他是一個型別為senum的聯合(下文會有介紹);
cmd可採取的操作有:IPC_RMID、IPC_SET、IPC_STAT和IPC_INFO,定義在:/usr/include/linux/ipc.h中
IPC_RMID、IPC_SET、IPC_STAT和IPC_INFO這四個引數都是system v的通用引數;
——IPC_RMID表示刪除訊號集
——IPC_STAT表示獲取ipc_perm的引數
——IPC_INFO表示獲取系統資訊
——IPC_SET表示設定ipc_prem的引數,對於這個引數,semctl有單獨的規定引數:
——GETPID獲取訊號量擁有者的pid的值;
——GETVAL獲取訊號量的值;
——GETALL獲取所有訊號量的值;
——GETNCNT獲取等待訊號量的值遞增的程序數;
——GETZCNT獲取等待訊號量的值遞減的程序數;
——SETVAL設定訊號量的值;
——SETALL設定所有訊號的值;
第四個引數的senum的聯合體定義如下:
接下來介紹幾個常用的操作:
|cmd操作為GETVAL
semctl第二個引數為訊號量編號,若執行成功,semctl返回當前訊號量的值,失敗返回-1;
|cmd操作為SETVAL
semctl第二個引數為訊號量編號,第四個引數為要設定的val
返回值:成功返回0;失敗返回-1;
semop
功能:修改集合中一個或多個訊號量值;
原型:int semop(int semid, struct sembuf *sops, unsigned nsops);
引數:semid訊號領=量標識碼;
sops是一個sembuf型別的指標;
nsops標識訊號量個數;
sembuf結構體定義如下:
——sem_num標識訊號量編號;
——sem_op訊號量一次pv操作時加減的數值,一般會用到兩個值:
-1,p操作,等待訊號量變得可用
+1,v操作,發出的訊號量變得可用
——sem_flg操作標識,有以下值: IPC_NOWAIT和SEM_UNDO
IPC_NOWAIT對某一訊號量操作,即使其中一個操作失敗,也不會導致修改其他訊號量的值;
SEM_UNDO當程序退出後,該程序對sem進行的操作將被撤銷;
返回值:成功返回0;失敗返回-1;
測試案例一:哲學家就餐問題
問題描述:
(由Dijkstra首先提出並解決)5個哲學家圍繞一張圓桌而坐,桌子上放著5支筷子,每兩個哲學家之間放一支;哲學家的動作包括思考和進餐,進餐時需要同時拿起他左邊和右邊的兩支筷子,思考時則同時將兩支筷子放回原處。如何保證哲學家們的動作有序進行?如:不出現相鄰者同時要求進餐;不出現有人永遠拿不到筷子;
解決思路:五隻筷子相當於資源,保證每次會有兩位哲學家先進餐,進餐完後在換其他哲學家,能夠保證五位哲學家都能進餐
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<string.h>
int semid;
union semun{
int value;
};
//獲得資源
void get_sour(int num)
{
struct sembuf sb[2]={
{num,-1,0},
{(num+1)%5,-1,0}
};
semop(semid,sb,2);
}
//釋放資源
void free_sour(int num)
{
struct sembuf sb[2]={
{num,1,0},
{(num+1)%5,1,0}
};
semop(semid,sb,2);
}
void phil(int num)
{
while(1){
printf("%d is thinking...\n",num);
sleep(rand()%5);
get_sour(num);
printf("%d start eating...\n",num);
sleep(rand()%3);
printf("%d end eating...\n",num);
free_sour(num);
}
}
int main()
{
srand(getpid());
semid=semget(1234,5,IPC_CREAT|0600);//建立訊號量
if(semid == -1){
perror("semget");
exit(1);
}
union semun s;
s.value=1;
//五個訊號量分別設初值
int i=0;
for(i=0;i<5;i++){
semctl(semid,i,SETVAL,s);
}
//建立五個程序——一個父程序+4個子程序
int num=0;//為每個程序編號,父程序初始化編號為0
for(i=1;i<5;i++){
pid_t pid=fork();
if(pid == 0){
num=i;
break;
}
}
phil(num);
}
執行結果:
本文主要介紹了system v訊號量的一些基本概念,目的是能夠對訊號量有一個基本的初步認識,文中如有不當之處,歡迎大家指正。