目录
一、信号量和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 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,22. 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非阻塞模式:若操作无法立即完成(如资源不足),直接返回错误 EAGAINSEM_UNDO进程退出时自动撤销操作(防止死锁),例如进程崩溃后自动恢复信号量原始值
3. nsops(操作数量):
- 作用:指定sops,数组的长度(即要执行的操作数量)
三、封装Sem
// Sem.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#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 ignored
int 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;
// 使用简单的建造者模式,用它来构建一个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(semid, flag);
}
~SemaphoreBuilder(){}
private:
int _val;
};
// Write.cc
#include
#include
#include
#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;
}

浙公网安备 33010602011771号