信号量概述
信号量與已經介紹過的IPC結構不同,它是一個計數器。信号量用于實作程序間的互斥與同步,而不是用于存儲程序間通信資料
特點:
- 信号量用于程序間同步,若要在程序間傳遞資料需要結合共享記憶體
- 信号量基于作業系統的P操作和V操作,程式對信号量的操作都是原子操作
- 每次對信号量的PV操作不僅限于對信号量值加1或減1,而且可以加減任意正整數
- 支援信号量組
臨界資源:多道程式系統中存在許多程序,它們共享各種資源,如:共享記憶體,然而有很多資源一次隻能供一個程序使用,一次僅允許一個程序使用的資源稱為臨界資源。許多實體裝置都屬于臨界資源,如:輸入機,列印機,錄音帶機等
信号量相關的api
最簡單的信号量是隻能取0和1的變量,這也是信号量最常見的一種形式,叫做二值信号量。而可以取多個正整數的信号量被稱為通用信号量。
Linux下的信号量函數都是在通用的信号量數組上進行操作,而不是在一個單一的二值信号量上進行操作
1.int semget(key_t key, int num_sems, int sem_flags);
功能:建立或擷取一個信号量組
傳回值:成功傳回信号量集ID,失敗傳回-1
參數說明:
- key:有兩種情況:1)0(IPC_PRIVATE):會建立新信号量集對象;2)大于0的32位整數:視參數sem_flags來确定操作,通常要求此值來源于ftok函數傳回的IPC鍵值
- num_sems:建立信号量集中信号量的個數,該參數隻在建立信号量集時有效
- sem_flags:有三種情況:1)0:取信号量集辨別符,若不存在則函數會報錯;2)IPC_CREAT:當sem_flags & IPC_CREAT 為真時,如果核心中不存在鍵值與key相等的信号量集,則建立一個信号量集;如果存在這樣的信号量集,傳回此信号量集的辨別符;3)IPC_CREAT | IPC_EXCL:如果核心中不存在鍵值與key相等的信号量集,則建立一個消息隊列;如果存在,則報錯
補充:上述sem_flags參數為模式标志參數,使用時需要與IPC對象存取權限(如0666)進行 | 運算來确定信号量集的存取權限
2.int semctl(int semid, int sem_num, int cmd, union semun arg);
功能:控制信号量的相關資訊
傳回值:成功傳回一個大于或等于0,失敗傳回-1
參數說明:
- semid:信号量集ID
- sem_num:信号量集數組上的下标,表示一個信号量
-
cmd:有如下設定方法
IPC_STAT :從信号量集上檢索semid_ds結構并存到semun聯合體參數的成員buf的位址中
IPC_SET:設定一個信号量集合的semid_ds結構中ipc_perm域的值,并從semun的buf中取出值
IPC_RMID:從核心中删除信号量集合
GETALL:從信号量集合中獲得所有信号量的值,并把其整數值存到semun聯合體成員的一個指針數組中
GETNCNT:傳回目前等待資源的程序個數
GETPID:傳回最後一個執行系統調用semop()程序的PID
GETVAL:傳回信号量集合内單個信号量的值
GETZCNT:傳回目前等待100%資源利用的程序個數
SETALL:與GETALL正好相反
SETVAL:用聯合體中val成員的值設定信号量集合中單個信号量的值
- 第四個參數是一個聯合體
union semun { int val; // SETVAL用的值 struct semid_ds *buf; // IPC_STAT和IPC_SET用的semid_ds結構 unsigned short *array; // SETALL和GETALL用的數組值 struct seminfo *__buf; // 為控制IPC_INFO提供的緩存 };
*3.int semop(int semid, struct sembuf semoparray, unsigned nsops);
功能:對信号量組進行操作,改變信号量的值
傳回值:成功傳回0,失敗傳回-1
參數說明:
- semid:信号量集辨別符
- semoparray:指向進行操作的信号量集結構體數組的首位址,此結構的具體說明如下:`
struct sembuf
{
short semnum; // 信号量集合中的信号量編号,0代表第1個信号量
short val; // 1)若val>0,進行V操作信号量值加val,表示程序釋放控制的資源;2)若val<0,進行P操作信号量值減val,若(semval-val)<0,(semval為該信号量值),則調用程序阻塞,知道資源可用;若設定IPC_NOWAIT不會睡眠,程序直接傳回EAGAIN錯誤;3)若val==0時阻塞等待信号量為0,調用程序進入睡眠狀态,直到信号量值為0;若設定IPC_NOWAIT,程序不會睡眠,直接傳回EAGAIN錯誤
short flg; // 1)0:設定信号量的預設動作;2)IPC_NOWAIT:設定信号量操作不等待;3)SEM_UNDO選項會讓核心記錄一個與調用程序相關的UNDO記錄,如果該程序崩潰,則根據這個程序的UNDO記錄自動恢複相應的信号量的計數值
};
- nsops:進行操作信号量的個數,即semoparray結構變量的個數,需大于或等于1.最常見設定此值等于1,隻完成對一個信号量的操作
例:
1.struct sembuf sem_get = {0,-1,IPC_NOWAIT};
将信号量對象中序号為0的信号量減1
2.struct sembuf sem_get = {0,1,IPC_NOWAIT};
将信号量對象中序号為0的信号量加1
demo:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <sys/types.h>
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
void pGetKey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = -1;
set.sem_flg = SEM_UNDO;
semop(id, &set, 1);
printf("getkey\n");
}
void vPutBackKey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = 1;
set.sem_flg = SEM_UNDO;
semop(id, &set, 1);
printf("put back the key\n");
}
int main()
{
key_t key;
key = ftok(".",2);
int semid;
semid = semget(key,1,IPC_CREAT|0666); // 建立/擷取信号量
union semun initsem;
initsem.val = 0; // 信号量的初始狀态為0,可以了解為一開始鎖已經被人拿走了
semctl(semid,0,SETVAL,initsem); // 初始化信号量,記得一定要做這步!0表示操作第0個信号量,SETVAL設定信号量的值,設定為initsem
int pid = fork();
if(pid > 0){
pGetKey(semid); // 拿鎖
printf("this is father\n");
vPutBackKey(semid); // 放鎖
semctl(semid,0,IPC_RMID); // 删除信号量
}
else if(pid == 0){
printf("this is child\n");
vPutBackKey(semid); // 放鎖
}
else{
printf("fork error\n");
}
return 0;
}