基本概念
首先要注意,信號量和信號是完全兩碼事。信號量是一個計數器,常用於處理進程或線程的同步問題,特別是對臨界資源訪問的同步。臨界資源可以簡單地理解爲在某一時刻只能由一個進程或線程進行操作的資源。通常,程序對共享資源的訪問的代碼只是很短的一段,但就是這一段代碼引發了進程之間的競態條件。這段代碼稱爲關鍵代碼段,或者臨界區。對進程同步,也就是確保任一時刻只有一個進程能進入關鍵代碼段。
信號量的值與相應資源的使用情況有關,當它的值大於0時,表示當前可用資源的數量,當它的值小於0時,其絕對值表示等待使用該資源的進程個數。信號量的值僅能由pv操作來改變。在Linux下,pv操作通過調用函數semop實現。
信號量是一種特殊的變量,它只支持2種操作:P操作(進入臨界區)和V操作(退出臨界區)。假設有信號量SV,則對它的P、V操作含義如下:
- P(SV),如果SV的值大於0,意味着可以進入臨界區,就將它減1;如果SV的值爲0,意味着別的進程正在訪問臨界區,則掛起當前進程的執行;;
- V(SV),當前進程退出臨界區時,如果有其他進程因爲等待SV而掛起,則喚醒之;如果沒有,則將SV加1,之後再退出臨界區。
與信號量相關的數據類型和函數
semid:信號集的標識符
struct semid_ds:信號集數據結構
struct sembuf:對應一個特定信號的操作
union semun:共用體變量,用以標示特定的操作
semget:創建或打開信號集
sem_p:信號集的p操作函數
sem_v:信號集的v操作函數
semop:pv操作通過調用函數semop實現
semctl:信號集的控制函數,比如刪除信號集,對信號集的數據結構進行設置,獲取信號集中信號值等。
semget系統調用
此係統調用創建一個新的信號量集,或者獲取一個已經存在的信號量集。
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
key是一個鍵值,用以標識一個全局唯一的信號集,要通過信號量通信的進程需要使用相同的鍵值來創建/獲取該信號量。
num_sems指定要創建/獲取的信號量集中信號量的數目。如果是創建信號量,則該值必須被指定。如果是獲取已經存在的信號量,則可以將其設置爲0。
sem_flags指定一組標誌。
semget調用成功會返回一個正整數值,它是信號量集的標識符;失敗時返回-1。
semop系統調用
此係統調用改變信號量的值,即執行P、V操作。
#include <sys/sem.h>
int semop(int sem_id, struct sembuf *sem_ops,size_t num_sem_ops);
sem_id就是上述semget調用返回的信號量集標識符。
sem_ops指向一個sembuf結構體類型的數組。
semctl系統調用
此係統調用允許調用者對信號量進行直接控制。
#include <sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...);
sem_id信號量集標識符。
sem_num指定被操作的信號量在信號量集中的編號。
command指定要執行的命令。
//在父、子進程之間使用信號量來進行同步
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
union semun{
int val; //用於SETVAL命令,也即本例中的命令
struct semid_ds *buf; //用於IPC_STAT和IPC_SET命令
unsigned short int *array; //用於GETALL和SETALL命令
struct seminfo *__buf; //用於IPC_INFO命令
};
//op爲-1時執行p操作,op爲1時執行v操作
void pv(int sem_id,int op)
{
struct sembuf sem_b;
sem_b.sem_num=0; //信號集中第0個信號(也就是信號集中的第一個信號)
sem_b.sem_op=op; //指定操作類型,可以是1,0,-1。
sem_b.sem_flg=SEM_UNDO; //SEM_UNDO含義是,當進程退出時取消正在進行的semop操作。
semop(sem_id,&sem_b,1);
}
int main(int argc,char *argv[])
{
//IPC_PRIVATE是個特殊的鍵值(0)。這個名稱有點誤導(由於歷史原因),並不是“私有的”,應該稱爲IPC_NEW。
int sem_id=semget(IPC_PRIVATE,1,0666);
union semun sem_un;
sem_un.val=1;
//將信號量的semval值設置爲semun.val,同時內核數據中的semid_ds.sem_ctime被更新
//SETVAL操作的是單個信號量,是由標識符sem_id指定的信號量集中的第sem_num個信號量(本例即爲第0個信號量)
semctl(sem_id,0,SETVAL,sem_un);
pid_t id=fork();
if(id<0){
return 1;
}else if(id==0){
printf("Child try to get binary sem\n");
pv(sem_id,-1);
printf("Child get the sem and would release it after 5 seconds\n");
sleep(5);
pv(sem_id,1);
exit(0);
}else{
printf("Parent try to get binary sem\n");
pv(sem_id,-1);
printf("Parent get the sem and would release it after 5 seconds\n");
sleep(5);
pv(sem_id,1);
}
waitpid(id,NULL,0);
semctl(sem_id,0,IPC_RMID,sem_un);
return 0;
}
運行結果: