1. 程式人生 > 實用技巧 >【linux】系統程式設計-3-system-V IPC 訊號量

【linux】系統程式設計-3-system-V IPC 訊號量

目錄


前言

  • 原文連結
  • 知識點
    • 訊息佇列、訊號量共享記憶體 被統稱為 system-V IPC
      • 以上都是“持續性”資源,即它們被建立之後, 不會因為程序的退出而消失
  • 說明:
    • 以下 訊號量 如無說明,均為 system-V IPC 訊號量

5. 訊號量

5.1 概念

  • 訊號量
    • 訊號量可以理解為一個計數器
    • 主要用於保護資源
  • 原子操作:單指令的操作稱為原子操作,單條指令的執行時不會被打斷的

5.2 工作原理

  • 訊號量就是兩種操作:P 操作和 V 操作
    • P 操作:就是申請資源,資源 -1
    • V 操作:就是釋放資源,資源 +1
    • 資源為 0 時,無資源申請

5.3 操作函式

  • 使用 semget() 建立或獲取一個訊號量
  • 使用 semop() 進行 PV 操作
  • 使用 semctl() 進行一系列控制操作

5.3.1 semget()

  • 使用 semget() 建立或獲取一個訊號量
  • 通過命令 man 瞭解更多
  • 函式原型:int semget(key_t key, int nsems, int semflg);
    • key:訊號量鍵值,可以自定義一個鍵值,也可以使用 IPC_PRIVATE 建立一個沒有 key
      的訊號量
    • nsems訊號量數目
    • semflg:表示建立的訊號量的模式標誌引數,主要有IPC_CREAT,IPC_EXCL和許可權mode,如:
      • IPC_CREAT:沒有關鍵字 key 的訊號量就新建一個,有就直接開啟
      • IPC_CREAT | IPC_EXCL:訊號量不存在,則新建一個,如果訊號量存在,則報錯
      • IPC_CREAT | 0666:(注:訊號量不在意執行許可權)
    • 返回:
      • 成功:返回訊號量識別符號
      • 失敗:返回 -1,原因記錄在變數 error
        • EACCES:沒有訪問該訊號量集的許可權
        • EEXIST:訊號量集已經存在,無法建立
        • EINVAL:引數nsems的值小於0或者大於該訊號量集的限制;或者是該key關聯的訊號量集已存在,並且nsems大於該訊號量集的訊號量數
        • ENOENT:訊號量集不存在,同時沒有使用IPC_CREAT
        • ENOMEM :沒有足夠的記憶體建立新的訊號量集
        • ENOSPC:超出系統限制
  • 建立訊號量也受下面值限制:(通過命令 ipcs -l 可查)
    • SEMMNI:系統中訊號量總數的最大值
    • SEMMSL:每個訊號量中訊號量元素個數的最大值
    • SEMMNS:系統中所有訊號量中的訊號量元素總數的最大值。

5.3.2 semop()

  • 使用 semop() 進行 PV 操作
  • 通過命令 man 瞭解更多
  • 函式原型:int semop(int semid, struct sembuf *sops, size_t nsops);
    • semid:訊號量識別符號
    • sops:指向儲存訊號操作結構的陣列指標,訊號操作結構的原型如下:
      struct sembuf
      {
          unsigned short int sem_num;   /* 訊號量的序號從0 ~ nsems-1 */
          short int sem_op;            /* 對訊號量的操作,>0, 0, <0 */
          short int sem_flg;            /* 操作標識:0, IPC_WAIT, SEM_UNDO */
      };
      
      • sem_num:標識訊號量中第幾個訊號量,從 0 開始
      • sem_op:對訊號量所進行的操作型別
        • 0:V 操作(回收資源),把 sem_op 的值加到該訊號量的訊號量當前值 semval 上

        • = 0:表示程序要阻塞等待,直至訊號量當前值 semval 變為 0
          • 如果沒有設定 IPC_NOWAIT ,則呼叫該操作的程序或者執行緒將暫時睡眠,直到訊號量的值為0
          • 如果設定 IPC_NOWAIT,則程序或者執行緒不會睡眠,函式返回錯誤EAGAIN
        • < 0:P 操作(申請資源),如果其絕對值大於訊號值 semval ,則操作會阻塞;直到訊號值 semval 大於等於 sem_op 的絕對值
    • nsops:訊號操作標誌
      • 0:正常操作
      • SEM_UNDO:程式結束時(不論正常或異常),保證訊號值會被重設為 semop() 呼叫前的值。目的是避免資源永遠鎖定。
      • 訊號操作結構的數量,恆大於或等於1
    • 返回:
      • 成功:返回 0
      • 失敗:返回 -1,原因記錄在變數 error
        • E2BIG:一次對訊號的運算元超出系統的限制
        • EACCES:呼叫程序沒有權能執行請求的操作,並且不具有CAP_IPC_OWNER權能
        • EAGAIN:訊號操作暫時不能滿足,需要重試
        • EFAULT:sops或timeout指標指向的空間不可訪問
        • EFBIG:sem_num指定的值無效
        • EIDRM:訊號集已被移除
        • EINTR:系統呼叫阻塞時,被訊號中斷
        • EINVAL:引數無效
        • ENOMEM:記憶體不足
        • ERANGE:訊號所允許的值越界

5.3.3 semctl()

  • 使用 semctl() 進行一系列控制操作
  • 通過命令 man 瞭解更多
  • 函式原型:int semctl(int semid, int semnum, int cmd, ...);
    • semid:System V訊號量的識別符號
    • semnum:表示訊號量集中的第 semnum 個訊號量。它的取值範圍: 0 ~ nsems-1
    • cmd:操作命令,主要有以下命令:
      • IPC_STAT:獲取此訊號量集合的semid_ds結構,存放在第四個引數的buf中
      • IPC_SET:通過第四個引數的buf來設定訊號量集相關聯的semid_ds中訊號量集合許可權為sem_perm中的uid,gid,mode
      • IPC_RMID:從系統中刪除該訊號量集合
      • GETVAL:返回第semnum個訊號量的值
      • SETVAL:設定第semnum個訊號量的值,該值由第四個引數中的val指定
      • GETPID:返回第semnum個訊號量的sempid,最後一個操作的pid
      • GETNCNT:返回第semnum個訊號量的semncnt。等待semval變為大於當前值的執行緒數
      • GETZCNT:返回第semnum個訊號量的semzcnt。等待semval變為0的執行緒數
      • GETALL:去訊號量集合中所有訊號量的值,將結果存放到的array所指向的陣列
      • SETALL:按arg.array所指向的陣列中的值,設定集合中所有訊號量的值
    • 第四個引數為一個可選的聯合體:
      union semun {
          int              val;    /* Value for SETVAL */
          struct semid_ds *buf;    /*Buffer for IPC_STAT, IPC_SET*/
          unsigned short  *array;  /*Array for GETALL, SETALL*/
          struct seminfo  *__buf;  /*Buffer for IPC_INFO (Linux-specific)*/
      };
      

5.4 例程

  • 只有一個資源
  • 等待子程序釋放了資源後,父程序才繼續往下執行
  • 訊號量操作封裝檔案

#include <sys/sem.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "sem.h"

/**
  * @brief  初始化第一個訊號量的資源數
  * @param  sem_id:訊號量識別符號
  * @param  init_value:初始化值
  */
int init_sem(int sem_id, int init_value)
{    
    union semun sem_union;    
    sem_union.val = init_value; /*init_value 為初始值*/
    if (semctl(sem_id, 0, SETVAL, sem_union) == -1)    
    {
        perror("Initialize semaphore");
        return -1;
    }
    return 0;
}

/**
  * @brief  從系統中刪除訊號量的函式
  * @param  sem_id:訊號量識別符號
  */
int del_sem(int sem_id)
{
    union semun sem_union;    
    if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)    
    {
        perror("Delete semaphore");
        return -1;
    }
}

/**
  * @brief  對第一個訊號量進行 P 操作
  * @param  sem_id:訊號量識別符號
  */
int sem_p(int sem_id)
{
    struct sembuf sops;
    sops.sem_num = 0; /* 單個訊號量的編號應該為 0 */    
    sops.sem_op = -1; /* 表示 P 操作 */    
    sops.sem_flg = SEM_UNDO; /* 若程序退出,系統將還原訊號量*/

    if (semop(sem_id, &sops, 1) == -1)    
    {
        perror("P operation");
        return -1;
    }
    return 0;
}

/**
  * @brief  對第一個訊號量進行 V 操作
  * @param  sem_id:訊號量識別符號
  */
int sem_v(int sem_id)
{
    struct sembuf sops;
    sops.sem_num = 0; /* 單個訊號量的編號應該為 0 */    
    sops.sem_op = 1; /* 表示 V 操作 */    
    sops.sem_flg = SEM_UNDO; /* 若程序退出,系統將還原訊號量 */

    if (semop(sem_id, &sops, 1) == -1)    
    {
        perror("V operation");
        return -1;
    }
    return 0;
}
  • 訊號量demo APP
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "sem.h"
#define DELAY_TIME 5 /* 子程序釋放訊號量延時時間 */
int main(void)
{
    pid_t result;
    int sem_id;
    sem_id = semget((key_t)6666, 1, 0666 | IPC_CREAT); /* 建立一個訊號量*/
    init_sem(sem_id, 0); // 初始化已建立的訊號量的資源數為0
    /*呼叫 fork()函式*/
    result = fork();
    if(result == -1)
    {
        perror("Fork\n");
    }    
    else if (result == 0) /*返回值為 0 代表子程序*/    
    {
        printf("Child process will wait for %d seconds...\n", DELAY_TIME);
        sleep(DELAY_TIME);
        printf("The returned value is %d in the child process(PID = %d)\n",result, getpid());
        sem_v(sem_id); // V 操作,釋放資源
    }
    else /*返回值大於 0 代表父程序*/    
    {
        sem_p(sem_id); // 申請資源
        printf("The returned value is %d in the father process(PID = %d)\n",result, getpid());
        sem_v(sem_id); // 釋放資源
        del_sem(sem_id); // 刪除訊號量
    }
    exit(0);
}

參考:

* 野火