信号量是什么?
AI解释:信号量(Semaphore)是操作系统中用于 进程同步与互斥 的经典工具,由荷兰计算机科学家 Edsger Dijkstra 在 1965 年提出。它本质上是一个 非负整数变量,通过原子操作(P 操作和 V 操作)实现对共享资源的访问控制。
System-V版本的信号量相关API:
Ftok
函数定义:
cpp
key_t ftok(const char *pathname, int proj_id);
函数作用:
获取唯一的key值标识符。
参数解释:
- 传入一个有效的文件路径和一个
<255
的整数数字,返回一个具有唯一性的key
。
semget
函数定义:
cpp
int semget(key_t key, int nsems, int semflg);
函数作用:
获取或者创建信号量集的文件描述符 fd
。
参数解释:
key
:ftok
调用成功返回的key
。nsems
:要创建/获取的信号量集合中的信号量数量cnt
。semflg
:- 传入
(IPC_CREATE | IPC_EXCL | 文件权限)
表示创建信号量集合,并返回fd
。 - 传入
IPC_CREATE
表示获取指定的信号量集合fd
。
- 传入
semctl
函数定义:
cpp
int semctl(int semid, int semnum, int cmd, ...);
函数作用:
控制指定的信号量集合,删除或者修改。
参数解释:
semid
:指定的信号量集的文件描述符fd
。semnum
:要控制的信号量集合中的下标(数组下标从0开始)。cmd
:-
设置为
IPC_RMID
时,表示删除指定信号量集,可忽略semnum
(设为0)和可变参数。 -
设置为
SETVAL
时,表示设置信号量的值,需手动创建联合体union semun
并设置val
字段:cppunion 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 */ } sem_un; sem_un.val = val; // 设置val字段 semctl(fd, i, SETVAL, sem_un); // 传入可变参数
-
semop
函数定义:
cpp
int semop(int semid, struct sembuf *sops, unsigned nsops);
函数作用:
对信号量进行 PV 操作。
PV操作:
使用系统提供的 struct sembuf
结构体:
cpp
struct sembuf {
unsigned short sem_num; /* 信号量下标 */
short sem_op; /* 操作值(-1为P操作,1为V操作) */
short sem_flg; /* 标志,一般设为SEM_UNDO */
};
参数解释:
sem_num
:信号量集合中信号量的下标。sem_op
:-1
表示 P 操作,1
表示 V 操作(本质是对信号量值进行加减)。sem_flg
:一般设置为SEM_UNDO
,表示异常时销毁信号量。semid
:信号量集描述符。sops
:struct sembuf
数组地址,支持批量 PV 操作。nsops
:数组大小。
实现基于简单建造者模式的信号量封装
日志模块 gitee:https://gitee.com/LOG_C/log
cpp
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <string.h>
#include "Log.hpp"
using namespace ns_log;
#define CREATE_SEM (IPC_CREAT | IPC_EXCL | 0666)
#define GET_SEM (IPC_CREAT)
#define P_OP (-1) /* P操作 */
#define V_OP (1) /* V操作 */
const std::string SEM_PATH = "../tmp"; // 需要先创建有效目录
const int proj_id = 123;
const int default_sem_nums = 1;
std::string ToHex(int num) {
char buf[64];
sprintf(buf, "0x%x", num);
return buf;
}
class Semaphore {
private:
int _fd;
void PV(int op) {
struct sembuf sem_buf;
sem_buf.sem_num = 0;
sem_buf.sem_op = op;
sem_buf.sem_flg = SEM_UNDO;
int n = semop(_fd, &sem_buf, 1);
if (n < 0) {
LOG(DEBUG, "op = %d失败\n", op);
return;
}
}
public:
Semaphore(int fd) : _fd(fd) {}
void P() { PV(P_OP); LOG(DEBUG, "P操作done\n"); }
void V() { PV(V_OP); LOG(DEBUG, "V操作done\n"); }
~Semaphore() {
if (_fd > 0) {
int n = semctl(_fd, 0, IPC_RMID);
if (n < 0) {
LOG(ERROR, "semctl的IPC_RMID操作异常!,异常信息:%s\n", strerror(errno));
}
LOG(DEBUG, "销毁信号量done\n");
}
}
};
using SemPtr = std::shared_ptr<Semaphore>;
class SemaphoreBuilder {
private:
int _val;
bool Init(int fd, int num, int val) {
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
} sem_un;
sem_un.val = val;
for (int i = 0; i < num; i++) {
int ret = semctl(fd, i, SETVAL, sem_un);
if (ret < 0) {
LOG(ERROR, "semctl的SETVAL操作异常!,异常信息:%s\n", strerror(errno));
return false;
}
}
return true;
}
public:
SemaphoreBuilder() {}
SemaphoreBuilder& SetVal(int val) { _val = val; return *this; }
SemPtr Build(int flag, int num = default_sem_nums) {
LOG(DEBUG, "开始build信号量\n");
key_t key = ftok(SEM_PATH.c_str(), proj_id);
if (key == -1) {
LOG(ERROR, "ftok操作异常!,异常信息:%s\n", strerror(errno));
return nullptr;
}
LOG(INFO, "frok的key为:%s\n", ToHex(key).c_str());
int sem_fd = semget(key, num, flag);
if (sem_fd == -1) {
LOG(ERROR, "semget操作异常!,异常信息:%s\n", strerror(errno));
return nullptr;
}
if (flag == CREATE_SEM) {
Init(sem_fd, num, _val);
}
return std::make_shared<Semaphore>(sem_fd);
}
};
理解建造者模式
定义:
将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。通过分解复杂对象的构建步骤,每一步创建一部分,最后组合成完整对象。
角色:
- 产品(Product) :
- 被构建的复杂对象,包含多个组成部分(属性和行为)。
- 抽象建造者(Builder) :
- 定义构建复杂对象各部分的接口,以及返回最终产品的方法。
- 具体建造者(Concrete Builder) :
- 实现抽象建造者接口,具体构建对象的各部分,生成不同类型或形式的产品。
- 指挥者(Director) :
- 负责调用具体建造者构建对象,不依赖具体类,仅通过建造者接口操作。
重构后的建造者模式(信号量场景)
cpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <string.h>
#include "Log.hpp"
using namespace ns_log;
#define CREATE_SEM (IPC_CREAT | IPC_EXCL | 0666)
#define GET_SEM (IPC_CREAT)
#define P_OP (-1) /* P操作 */
#define V_OP (1) /* V操作 */
const std::string SEM_PATH = "../tmp";
const int proj_id = 123;
const int default_sem_nums = 1;
enum { FTOK_ERROR = 1, SEMGET_ERROR, SEMCTL_ERROR };
std::string ToHex(int num) {
char buf[64];
sprintf(buf, "0x%x", num);
return buf;
}
// 产品:信号量
class Semaphore {
private:
int _fd;
void PV(int who, int op) {
struct sembuf sem_buf;
sem_buf.sem_num = who;
sem_buf.sem_op = op;
sem_buf.sem_flg = SEM_UNDO;
int n = semop(_fd, &sem_buf, 1);
if (n < 0) {
LOG(DEBUG, "op = %d失败\n", op);
return;
}
}
public:
Semaphore(int fd) : _fd(fd) {}
void P(int who) { PV(who, P_OP); LOG(DEBUG, "P操作done\n"); }
void V(int who) { PV(who, V_OP); LOG(DEBUG, "V操作done\n"); }
int Fd() const { return _fd; }
~Semaphore() {
if (_fd > 0) {
int n = semctl(_fd, 0, IPC_RMID);
if (n < 0) {
LOG(ERROR, "semctl的IPC_RMID操作异常!,异常信息:%s\n", strerror(errno));
}
LOG(DEBUG, "销毁信号量done\n");
}
}
};
// 抽象建造者
class Builder {
public:
virtual ~Builder() {}
virtual void BuildKey() = 0; // 获取键值
virtual void SetPerm(int perm) = 0; // 设置权限
virtual void SetSemNum(int num) = 0; // 设置信号量数量
virtual void SetVal(const std::vector<int>& init_vals) = 0; // 初始化值
virtual void Init() = 0; // 初始化信号量
virtual void Build(int flag) = 0; // 构建信号量
};
// 具体建造者
class SemaphoreBuilder : public Builder {
private:
bool init(int fd, int index, int val) {
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
} sem_un;
sem_un.val = val;
int ret = semctl(fd, index, SETVAL, sem_un);
if (ret < 0) {
LOG(ERROR, "semctl的SETVAL操作异常!,异常信息:%s\n", strerror(errno));
return false;
}
return true;
}
public:
std::shared_ptr<Semaphore> GetSemaphore() { return _sem; }
void BuildKey() override {
_key = ftok(SEM_PATH.c_str(), proj_id);
if (_key == -1) {
LOG(ERROR, "ftok操作异常!,异常信息:%s\n", strerror(errno));
exit(FTOK_ERROR);
}
LOG(INFO, "frok的key为:%s\n", ToHex(_key).c_str());
}
void SetPerm(int perm) override { _perm = perm; }
void SetSemNum(int num) override { _sem_cnts = num; }
void SetVal(const std::vector<int>& init_vals) override { _init_vals = init_vals; }
void Init() override {
if (_sem_cnts > 0 && _sem_cnts == _init_vals.size()) {
for (int i = 0; i < _sem_cnts; i++) {
if (!init(_sem->Fd(), i, _init_vals[i])) {
exit(SEMCTL_ERROR);
}
}
}
}
void Build(int flag) override {
int sem_fd = semget(_key, _sem_cnts, flag);
if (sem_fd == -1) {
LOG(ERROR, "semget操作异常!,异常信息:%s\n", strerror(errno));
exit(SEMGET_ERROR);
}
_sem = std::make_shared<Semaphore>(sem_fd);
}
private:
std::shared_ptr<Semaphore> _sem;
key_t _key;
int _perm;
int _sem_cnts;
std::vector<int> _init_vals;
};
// 指挥者
class Director {
public:
void construct(
std::shared_ptr<SemaphoreBuilder>& sem_builder,
int flag,
int num,
const std::vector<int>& vals,
int perm
) {
sem_builder->BuildKey();
sem_builder->SetPerm(perm);
sem_builder->SetVal(vals);
sem_builder->SetSemNum(num);
sem_builder->Build(flag);
if (flag == CREATE_SEM) {
sem_builder->Init();
}
}
};