天天看點

linux程序間通信之信号量及信号量的應用

信号量概述

信号量與已經介紹過的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;
  
}
           

繼續閱讀