1. System V信號量簡介
SystemV信號量主要用於解決生產者和消費者問題,一個信號量能夠控制多個資源,說它是信號量集也不爲過。
2. API接口介紹
2.1 創建或打開信號量集
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
/**
* @brief 創建信號量或者返回已存在的信號量
*
* @params key 與信號量關聯的key,三種生成方式,包括IPC_PRIVATE,注意key和信號量不是綁定關係,相同的key產生的標識符不一定相同,比如系統重啓的情況
* @params nsems 集合中信號量的個數
* @params shmflg 標誌位和權限控制標誌位,可以多個用or運算。IPC_CREAT、 IPC_EXCL
*
* @returns 成功返回信號量集標識符,失敗返回-1
*/
int semget(key_t key, int nsems, int semflg);
2.2 操作信號量集
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
/**
* @brief 操作信號量集
*
* @params semid 信號量集標識符
* @params sops 具體的操作行爲,指向一個數組,每個元素都是sembuf結構,該結構指定了要操作哪個信號量以及相應的值等。
*
* @params nsops 表名sembuf的數量
*
* @returns 成功返回0,失敗返回-1,注意這裏的操作是原子性的,如果操作多個信號量,要麼全部成功,要麼什麼都不做
*/
int semop(int semid, struct sembuf *sops, size_t nsops);
struct sembuf {
unsigned short int sem_num; // 指定操作信號量的下標,從0開始
short sem_op; // 信號量的值
short sem_flg; // 操作標誌位,可選 IPC_NOWAIT and SEM_UNDO,前者表示非阻塞,後者標誌當進程退出後,系統自動恢復對這個信號量的操作。
};
2.3 控制操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
/**
* @brief 操作信號量集
*
* @params semid 信號量集標識符
* @params semnum 要操作的信號量序號,下標從0開始
* @params cmd 控制行爲,可選值IPC_STAT,IPC_RMID, IPC_SET, IPC_INFO
* @params 可選參數,這裏的參數結構體需要用戶自定義。
* @returns 不同的cmd返回不通值
*/
int semctl(int semid, int semnum, int cmd, ...);
// 可選參數,需要用戶自定義
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) */
};
3. 示例
下面是信號量集的一個簡單示例,分爲生產者和消費者,其中生產者生產資源,消費者消費資源
生產者:
/*
** Name: sem_wait.c
** Desc: 生產者
** Author: masonf
** Date: 20200626
*/
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
// 自定義結構體
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int main()
{
int ret = 0;
key_t key = 10240;
int sem_id;
// 創建或獲取信號量集,其中包含一個信號量,這裏key是固定的
sem_id = semget(key, 1, IPC_CREAT);
if (sem_id == -1)
{
perror("create sem error");
return -1;
}
// 設置第0個信號量,資源數加1
struct sembuf sem_operation[1]={0};
sem_operation[0].sem_op=1;
sem_operation[0].sem_num = 0;
// 初始化信號量的值爲1
if (semop(sem_id, sem_operation, 1) == -1)
{
perror("init sem error");
// 初始化失敗則刪除信號量集
semctl(sem_id, 1, IPC_RMID);
return -1;
}
int food_nums = 1;
while (food_nums < 5)
{
printf("start producing foods\n");
// 生產食物
sem_operation[0].sem_num=0; // 操作第一個信號量,下標從0開始
sem_operation[0].sem_op = 1;
while (semop(sem_id, sem_operation, 1) == -1)
{
printf("producing error, continue\n");
continue;
}
++food_nums;
}
// 刪除信號量集
//semctl(sem_id, 1, IPC_RMID);
printf("work done, Go home!\n");
return 0;
}
消費者
/*
** Name: sen_wait.c
** Desc: 消費者
** Author: mason
** Date: 20200626
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
// 自定義結構體
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int main()
{
int ret = 0;
key_t key = 10240;
int sem_id;
// 創建或獲取信號量集
sem_id = semget(key, 1, IPC_CREAT);
if (sem_id == -1)
{
perror("create sem error");
return -1;
}
//操作信號量,默認是阻塞
struct sembuf sem_operation[1]={0};
sem_operation[0].sem_num=0;
sem_operation[0].sem_op=-1; // 申請一個資源
//sem_operation[0].sem_flg=SEM_UNDO;
// 消費,每次消費一個
while (semop(sem_id, sem_operation, 1) != -1)
{
printf("consuming foods\n");
}
return 0;
}
編譯運行
4. 注意事項
4.1 信號量的創建和初始化操作是分開的,這點需要注意,好在有一種解決方法,信號量集相關的結構體中有一個sem_otime字段,初始化爲0,可以通過檢查sem_otime是否爲0來判斷是否已經經過初始化
2. 資源限制
SEMMSL 系統範圍內信號量集的最大數量,可通過/proc/sys/kernel/sem查看
SEMMSL 單個信號量集中信號量的最大個數
SEMMNS 系統範圍內信號量的最大數量
5.參考文檔
1. https://man7.org/linux/man-pages/man2/semop.2.html
2. 《Linux環境編程 從應用到內核》
3. 《Unix網絡編程 卷二 進程間通信》
================================================================================================
Linux應用程序、內核、驅動、後臺開發交流討論羣(745510310),感興趣的同學可以加羣討論、交流、資料查找等,前進的道路上,你不是一個人奧^_^。...