【Linux】System V —— 基于建造者模式的信号量
目录
一、信号量和P、V原语
1.1 信号量常规理解
1.2 信号量值含义
1.3 信号量结构体伪代码
1.4 P原语
1.5 V原语
1.6 信号量集结构体
二、信号量操作接口
2.1 semget
2.2 semctl
2.3 semop
三、封装Sem
一、信号量和P、V原语
信号量和P、V原语由Dijkstra(迪杰斯特拉)提出。
1.1 信号量常规理解
- 执行流间互斥和同步
1.2 信号量值含义
- S>0:S 表示可用资源的个数
- S=0:表示无资源可用,无等待进程
- S<0:|S| 表示等待队列中进程个数
1.3 信号量结构体伪代码
// 信号量本质是一个计数器struct semaphore
{int value;pointer_PCB queue;
}
1.4 P原语
P(s)
{s.value = s.value--;if (s.value < 0){// 该进程状态置为等待状状态// 将该进程的PCB插⼊⼊相应的等待队列s.queue末尾}
}
1.5 V原语
V(s)
{s.value = s.value++;if (s.value > 0){// 唤醒相应等待队列s.queue中等待的⼀⼀个进程// 改变其状态为就绪态// 并将其插⼊OS就绪队列}
}
1.6 信号量集结构体
The semid_ds data structure is defined in<sys / sem.h> as follows :
struct semid_ds
{struct ipc_perm sem_perm; /* Ownership and permissions */time_t sem_otime; /* Last semop time */time_t sem_ctime; /* Last change time */unsigned long sem_nsems; /* No. of semaphores in set */
};The ipc_perm structure is defined as follows(the highlighted fields are settable using IPC_SET) :
struct ipc_perm
{key_t __key; /* Key supplied to semget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */
};
二、信号量操作接口
2.1 semget
NAME
semget - get a System V semaphore set identifier
SYNOPSIS
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
int semget(key_t key, int nsems, int semflg);
RETURN VALUE
On success, semget() returns the semaphore set identifier (a nonnegative integer). On failure, -1 is returned, and errno is set to indicate the error.
参数介绍:
- key:信号量集的键值,同消息队列和共享内存
- nsems:信号量集中信号量的个数
- semflg:同消息队列和共享内存
2.2 semctl
NAME
semctl - System V semaphore control operations
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
This function has three or four arguments, depending on op. When there are four, the fourth has the type union semun. The calling program must define this union as follows:
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) */
};RETURN VALUE
On success, semctl() returns a nonnegative value depending on op as follows:GETNCNT
the value of semncnt.GETPID the value of sempid.
GETVAL the value of semval.
GETZCNT
the value of semzcnt.IPC_INFO
the index of the highest used entry in the kernel's internal array recording information about all semaphore sets. (This information can be used with repeated SEM_STAT or
SEM_STAT_ANY operations to obtain information about all semaphore sets on the system.)SEM_INFO
as for IPC_INFO.SEM_STAT
the identifier of the semaphore set whose index was given in semid.SEM_STAT_ANY
as for SEM_STAT.All other op values return 0 on success.
On failure, semctl() returns -1 and sets errno to indicate the error.
参数介绍:
1. semid:
- 信号量集的标识符(由 semget() 创建)
2. semnum:
- 信号量在集合中的索引(从0开始)
- 对单个信号量操作时:指定目标信号量
- 对整组操作时(如 IPC_RMID):此参数被忽略
3. cmd:控制命令
常用命令及作用:
命令 | 功能 | 是否需要第四个参数 |
IPC_STAT | 获取信号量集状态信息 | 是(struct semid_ds* ) |
IPC_SET | 修改信号量集权限 | 是(struct semid_ds* ) |
IPC_RMID | 立即删除信号量集 | 否 |
GETVAL | 获取单个信号量值 | 否 |
SETVAL | 设置单个信号量值 | 是(int ) |
GETALL | 获取所有信号量值 | 是(unsigned short* ) |
SETALL | 设置所有信号量值 | 是(unsigned short* ) |
GETPID | 获取最后操作进程PID | 否 |
GETNCNT | 获取等待信号量增加的进程数 | 否 |
GETZCNT | 获取等待信号量为零的进程数 | 否 |
4. 第四个参数(可变参数):
- 类型:联合体 semun(需用户自定义)
-
// 定义示例union semun {int val; // SETVAL 使用struct semid_ds *buf; // IPC_STAT/IPC_SET 使用unsigned short *array; // GETALL/SETALL 使用 };
- 作用:根据 cmd 传递特定数据
2.3 semop
NAME
semop, semtimedop - System V semaphore operations
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
RETURN VALUE
On success, semop() and semtimedop() return 0. On failure, they return -1, and set errno to indicate the error.
参数介绍:
1. semid:
- 信号量集的标识符(由 semget() 创建)
- 需确保进程有操作权限(通过
semget()
的权限位设置)
2. sops(操作结构体指针):
- 类型:struct sembuf *
- 结构体定义:
-
struct sembuf {unsigned short sem_num; // 信号量在集合中的索引short sem_op; // 操作类型(见下文)short sem_flg; // 操作标志 };
成员详解:
1. sem_num:
- 信号量在信号量集中的索引(从
0
开始)- 例如:集合包含 3 个信号量时,有效值为
0
,1
,2
2. sem_op(操作类型):
值 操作说明 原子性保证 正数 增加信号量值(释放资源),例如 sem_op=3
使信号量值 $S \leftarrow S + 3$立即执行,不阻塞 负数 减少信号量值(请求资源),例如 sem_op=-2
需满足 $S \geq 2$,否则阻塞不满足条件时阻塞进程 0 等待信号量值变为 $0$,若 $S \neq 0$ 则阻塞 常用于同步操作 3. sem_flg(操作标志):
标志 说明 IPC_NOWAIT
非阻塞模式:若操作无法立即完成(如资源不足),直接返回错误 EAGAIN
SEM_UNDO
进程退出时自动撤销操作(防止死锁),例如进程崩溃后自动恢复信号量原始值
3. nsops(操作数量):
- 作用:指定sops,数组的长度(即要执行的操作数量)
三、封装Sem
// Sem.hpp#pragma once#include <iostream>
#include <string>
#include <memory>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>#define GET_SEM IPC_CREAT // 不给权限
#define BUILD_SEM (IPC_CREAT | IPC_EXCL | 0X666)const std::string pathname = "/tmp";
int project_id = 99;// 只关注使用和删除
class Semaphore
{
public:Semaphore(int semid, int flag):_semid(semid), _flag(flag){}void P() {struct sembuf bs; // 该结构体系统提供bs.sem_num = 0;bs.sem_op = -1;bs.sem_flg = SEM_UNDO;int n = ::semop(_semid, &bs, 1);if(n < 0) {perror("semop-P");}}void V() {struct sembuf bs; // 该结构体系统提供bs.sem_num = 0;bs.sem_op = 1;bs.sem_flg = SEM_UNDO;int n = ::semop(_semid, &bs, 1);if(n < 0) {perror("semop-V");}}~Semaphore() {if(_flag == GET_SEM) return;// 让信号量自动销毁// 如果销毁信号量集合:The argument semnum is ignoredint n = ::semctl(_semid, 0, IPC_RMID);if(n < 0) {perror("semctl");}std::cout << "sem set destory!" << std::endl;}
private:int _semid;int _flag;
};using sem_ptr = std::shared_ptr<Semaphore>;// 使用简单的建造者模式,用它来构建一个sem
class SemaphoreBuilder
{
public:SemaphoreBuilder():_val(-1){}SemaphoreBuilder& SetVal(int val) {_val = val;return *this; // 支持连续访问}sem_ptr Build(int flag) {if(_val < 0) {std::cout << "you must init first" << std::endl;return nullptr;}// 1. 申请key值key_t key = ::ftok(pathname.c_str(), project_id);if(key < 0) {perror("ftok");exit(1);}// 2. 根据初始值,创建一个信号量集int semid = ::semget(key, 1, flag);if(semid < 0) {perror("semget");exit(2);}if(BUILD_SEM == flag) {// 3. 初始化信号量union semun { // 该联合体系统不提供,需要自己定义int val;struct semid_ds *buf;unsigned short *array;struct seminfo *__buf;}un;un.val = _val; // 设置初始值int n = ::semctl(semid, 0, SETVAL, un);if(n < 0) {perror("semctl-set");exit(3);}}return std::make_shared<Semaphore>(semid, flag);}~SemaphoreBuilder(){}
private:int _val;
};
// Write.cc#include <cstdio>
#include <ctime>
#include <unistd.h>
#include "Sem.hpp"int main()
{SemaphoreBuilder sab;auto fsem = sab.SetVal(1).Build(BUILD_SEM); // 创建信号量集合,只有一个信号量,初始化为1,就是当成锁来用if (fork() == 0){auto csem = sab.Build(GET_SEM);int cnt = 9;while (cnt--){csem->P();std::cout << "c, sleep" << std::endl;sleep(1);std::cout << "csem: " << cnt << std::endl;sleep(1);csem->V();}exit(0);}int cnt = 16;while (cnt--){fsem->P();std::cout << "f, sleep" << std::endl;sleep(1);std::cout << "fsem: " << cnt << std::endl;sleep(1);fsem->V();}return 0;
}