1、 什麼是信號量?
在對於臨界區資源管理過程中,爲了防止多個程序同時訪問一個共享資源而引發的一系列問題。比如:死鎖。
爲了解決這種問題,巨人們就發明了信號量。
信號量就是爲了解決在一個臨界區只有一個進程訪問它,也就是說信號量相當於交警,來協調進程對共享資源有序的訪問而不造成死鎖等行爲。
信號量是一個特殊的變量,程序對它訪問都是原子操作,並且她只能進行兩種操作,等待(P信號量)和發送(V信號量) 。
P(sv):如果sv的值大於0,說明資源可用,就給它減1,如果它等於0,就掛起該進程說明資源不可用,直到資源可用。
V(sv):如果sv值大於0,並且有其他進程因爲等待sv而被掛起,則該進程恢復運行,如果沒有進程等待,則sv加1
2、信號量的函數操作
- 2.1、semget函數
它的作用是創建一個新信號量集或獲取一個已有的信號量集
int semget(key_t key, int nsems, int semflg);
- key:每一個信號量集都有一個唯一的信號量集標識符id,這個id是通過一個鍵值key獲得,key值可以通過ftok函數獲得。而semget必須有一個key值,並由系統生成一個相應的進程標識符id作爲semget的返回值。如果多個進程共用同一個key值,那麼key將負責協調工作。
- nsems:指定信號量集的數目
- semflg:當信號量集不存在時創建一個信號量用IPC_CREAT,當信號量已經存在時用IPC_CREAT|IPC_EXCL,返回一個錯誤。
- semget成功返回一個相應信號量集標識符,失敗返回-1.
- 2.2、 semop函數
作用:改變信號量的值,只要是爲PV操作做準備的
int semop(int semid, struct sembuf *sops, unsigned nsops);
- semid:信號量集標識符id,semget的返回值
struct sembuf *sops:sops指向一個結構體數組的指針,這個結構體對一個特定的信號進行操作,因此你要熟悉該結構體裏的內容才能進行更好的操作。
struct sembuf
{
unsigned short sem_num; //信號量在信號集中的下標,0代表第一個信號,1代表第二個信號
short val(sem_op); //操作類型
short sem_flg; //操作標誌
}; 。若val>0進行V操作信號量值加val,表示進程釋放控制的資源
若val<0進行P操作信號量值減val,若(semval-val)<0(semval爲該信號量值),則調用進程掛起,直到資源可用;
若設置IPC_NOWAIT不會睡眠,進程直接返回EAGAIN錯誤
若val==0時阻塞等待信號量爲0,調用進程進入睡眠狀態,直到信號值爲0;若設置IPC_NOWAIT,進程不會睡眠,直接返回EAGAIN錯誤sem_flg:
1、若爲0 設置信號量的默認操作
2、IPC_NOWAIT設置信號量操作不等待
3、SEM_UNDO 選項會讓內核記錄一個與調用進程相關的UNDO記錄,如果該進程崩潰,則根據這個進程的UNDO記錄自動恢復相應信號量的計數值
- nsops:數組元素個數
- 2.3、semctl函數
作用:控制信號量集
int semctl(int semid,int senum,int cmd,...)
返回值:信號量的當前值
- senum:信號集中信號量的編號(下標),第一個信號量從0開始
- 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成員的值設置信號量集合中單個信號量的值 |
對於該函數,只有當cmd取某些特定的值的時候,纔會使用到第4個參數,第4個參數它通常是一個union semum結構,定義如下
union semun
{
int val;//當執行SETVAl命令時,用到該值,用於指定把信號設計爲什麼值
struct semid_ds *buf; // 當執行IPC_STAT/IPC_SET時,它代表內核中所使用內部信號量數據結構的一個複製 ,用到成員:buf
unsigned short *array; //當執行GETALL/SETALL命令時,他代表指向一個數組的指針,在設置或獲取集合中所有信號量的值的過程中,將會用到該數組,用到成員:array
struct seminfo *__buf; //設計IPC_INFO時,會使用,但是一般很少用到
}
3、哲學家就餐問題:
- 3.1:問題描述
假設有五位哲學家圍坐在一張圓形餐桌旁,做以下兩件事情之一:喫飯,或者思考。喫東西的時候,他們就停止思考,思考的時候也停止喫東西。餐桌中間有一大碗米飯,每兩個哲學家之間有一隻筷子。因爲用一隻筷子很難喫到米飯,所以假設哲學家必須用兩隻筷子喫東西。他們只能使用自己左右手邊的那兩隻筷子。所以現在就出現了問題。
- 3.2:問題分析
當五個哲學家在某個時刻同時申請到拿筷子操作時,並且申請成功了,這時他們都拿到了左手邊的一隻筷子,但是當他們申請右手邊筷子時,發現右手邊筷子沒有資源只能繼續等,這時五個哲學家都在等,這樣就產生了死鎖。
- 3.3 : 解決思路
3.3.1:給每個哲學家和筷子進行編號,從0開始到4,因爲senum中第一個信號量的下標是從0開始的。
3.3.2:每個筷子代表一個信號量
3.3.3:每個哲學家在取筷子時每次拿左右兩隻筷子,並且執行P操作使其它哲學家處於等待狀態,直到該哲學家喫飽,放下筷子(釋放資源)時,其它哲學家纔可以拿筷子,從而繼續循環。
3.3.4:通過信號量以及PV操作保證每次只有一個哲學家拿一雙筷子,其它哲學家都等待,直到該哲學家釋放資源。從而避免死鎖。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
int id;
union semun
{
int val;//設置信號量值
};
void P(int num)
{
struct sembuf sops[2] = //兩隻筷子啊
{
{num,-1,0},
{(num+1)%5,-1,0}
};
semop(id,sops,2);
}
void V(int num)
{
struct sembuf sops[2] = //兩隻筷子啊
{
{num,1,0},
{(num+1)%5,1,0}
};
semop(id,sops,2);
}
void zxj(int num)
{
while(1)
{
printf("%d哲學家思考中...\n",num);
sleep(rand()%5);
printf("%d哲學家餓了,準備拿筷子\n",num);
P(num);
printf("%d哲學家開始喫東西...\n",num);
sleep(rand()%5);
printf("%d哲學家喫飽了\n",num);
V(num);
}
}
int main()
{
srand(getpid());//以當前進程返回的pid值作爲產生隨機值的源頭
key_t key = ftok(".",'a');//"."代表當前路徑
if(key < -1)
{
perror("ftok\n");
return -1;
}
id = semget(key,5,IPC_CREAT|0644);
if(id == -1)
{
perror("perror semget\n");
exit(-2);
}
union semun su = {1};//每個哲學家的筷子數爲1
int i = 0;
for(i = 0;i < 5 ; i++)
{
semctl(id,i,SETVAL,su);
}
int num = 0;
for(i = 1;i < 5; i++ )
{
pid_t pid = fork();
if(pid == 0)
{
num = i;
break;
}
}
zxj(num);
return 0;
}