Linux:進程間通信-信號量

Linux學習目錄


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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章