【linux】系統程式設計-3-system-V IPC 訊號量
阿新 • • 發佈:2020-12-29
目錄
前言
- 原文連結
- 知識點
- 訊息佇列、訊號量 和 共享記憶體 被統稱為 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:超出系統限制
- key:訊號量鍵值,可以自定義一個鍵值,也可以使用 IPC_PRIVATE 建立一個沒有 key
- 建立訊號量也受下面值限制:(通過命令
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);
}
參考:
* 野火