目录
- [1、System V信号量](#1、System V信号量)
-
- (1)信号量的本质与核心原理
- (2)PV原语(均为原子操作)
-
- [a. P原语(申请资源)](#a. P原语(申请资源))
- [b. V原语(归还资源)](#b. V原语(归还资源))
- [(3)System V信号量接口](#(3)System V信号量接口)
-
- [a. 创建信号量集 semget](#a. 创建信号量集 semget)
- [b. 控制信号量 semctl](#b. 控制信号量 semctl)
- [c. PV操作 semop](#c. PV操作 semop)
- (4)信号量管理
- [2、System V 消息队列](#2、System V 消息队列)
-
- (1)消息队列特点
- (2)消息队列接口函数
-
- [a. 创建 / 获取消息队列 msgget](#a. 创建 / 获取消息队列 msgget)
- [b. 消息队列控制 msgctl](#b. 消息队列控制 msgctl)
- [c. 发送消息 msgsnd](#c. 发送消息 msgsnd)
- [d. 接收消息 msgrcv](#d. 接收消息 msgrcv)
- (3)命令行管理
之前我分享过基于System V的共享内存的使用,上篇文章也引入mmap分析了其底层实现,即共享映射文件缓冲区使得不同进程看到同一块区域,本篇文章会继续分享System V标准下的信号量和消息队列。整体难度不大,不需记忆接口,了解即可,因此本文借助AI生成,仅做简单记录和介绍。
1、System V信号量
(1)信号量的本质与核心原理
无论是POSIX还是System V标准下,信号量本质是一个计数器,即资源的预定机制,主要用于进程间同步与互斥管理,在用环形队列实现生产者消费者模型中有很大用处。
信号量操作分为P操作(申请资源)与V操作(归还资源)。
信号量计数器s的含义:
- s > 0:表示当前可用资源的个数
- s = 0:表示无可用资源,且无等待进程
- s < 0:表示无可用资源,|s|为等待队列中进程的个数
内核中信号量可由结构体表示:
cpp
struct semaphore {
int value;
struct PCB* queue;
};
(2)PV原语(均为原子操作)
a. P原语(申请资源)
- 执行 value--
- 判断 value < 0:若成立,说明资源不足
- 将当前进程加入等待队列并阻塞
- 进程调度切换,等待被唤醒
cpp
void P(struct semaphore* sem) {
sem->value--;
if (sem->value < 0) {
add_to_queue(sem->queue, current_process);
block_current_process();
}
}
b. V原语(归还资源)
- 执行 value++
- 判断 value <= 0:若成立,说明有进程在等待
- 从等待队列唤醒一个进程至就绪态
- 唤醒后的进程可重新尝试申请信号量
cpp
void V(struct semaphore* sem) {
sem->value++;
if (sem->value <= 0) {
struct PCB* proc = remove_from_queue(sem->queue);
wake_up_process(proc);
}
}
(3)System V信号量接口
a. 创建信号量集 semget
cpp
int semget(key_t key, int nsems, int semflg);
- key:唯一标识,与共享内存规则一致
- nsems:信号量集中信号量的个数
- semflg:支持 IPC_CREAT 与 IPC_EXCL
- 返回值:成功返回 semid,失败返回 -1
b. 控制信号量 semctl
cpp
int semctl(int semid, int semnum, int cmd, ...);
- semid:信号量集标识符
- semnum:信号量下标,从0开始
- cmd:
- SETVAL:初始化单个信号量,该操作不具备原子性
- IPC_RMID:删除整个信号量集,第二个参数无意义
- 返回值:失败返回 -1
c. PV操作 semop
cpp
int semop(int semid, struct sembuf* sops, size_t nsops);
- sops:操作结构体数组
- nsops:数组长度
- 可同时对多个信号量执行原子操作,例如一个P、一个V
- 返回值:成功 0,失败 -1
(4)信号量管理
- 查看信号量:ipcs -s
- 删除信号量:ipcrm -s
System V 信号量生命周期随内核,进程退出不会自动释放,必须手动删除,通常在析构函数中完成释放逻辑。
2、System V 消息队列
(1)消息队列特点
a. 允许任意进程间通信
消息队列是内核提供的 System V IPC 机制,不要求进程之间存在亲缘关系,只要多个进程通过同一个 key 访问同一个消息队列,即可实现数据收发,支持无关联进程间通信。
b. 消息自带类型标识,可按类型选择性接收
每条消息必须以一个 long 类型的消息类型字段开头,接收方可以指定只接收某一类型的消息,不匹配类型的消息会继续保留在队列中,也可以不指定类型直接读取最早消息,实现消息分类与分发。
c. 支持全双工通信
通信双方可以使用不同消息类型分别进行发送和接收,无需切换读写方向,双向通信互不干扰,天然实现全双工。
d. 生命周期随内核
消息队列创建后会持续存在于内核中,进程退出不会自动删除,只有通过调用msgctl(IPC_RMID) 或系统重启才会销毁。
e. 面向数据块传输
以消息为单位传输,每条消息独立,不同于管道的字节流传输,边界清晰。
(2)消息队列接口函数
a. 创建 / 获取消息队列 msgget
cpp
int msgget(key_t key, int msgflg);
- key:消息队列的唯一标识符,可通过 ftok 生成或手动指定
- msgflg:权限位 + 创建标志
- IPC_CREAT:队列不存在则创建,存在则返回已存在队列 ID
- IPC_EXCL:与 IPC_CREAT 配合使用,队列已存在则报错,确保新建队列
- 例:IPC_CREAT | 0666
- 返回值:成功返回消息队列 ID msqid,失败返回 -1
b. 消息队列控制 msgctl
cpp
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- cmd:
- IPC_RMID:删除消息队列,此时 buf 传 NULL
- IPC_STAT:获取队列属性信息,存入 buf
- IPC_SET:设置队列属性
- 常用于删除队列:msgctl(msqid, IPC_RMID, NULL);
c. 发送消息 msgsnd
cpp
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
- msgp:指向自定义消息结构体指针,结构体必须以 long mtype 开头
- msgsz:消息数据部分长度,不包含 mtype 字段
- msgflg:一般为 0(阻塞发送),或 IPC_NOWAIT(非阻塞)
- 消息结构体示例:
cpp
struct msgbuf {
long mtype;
char mtext[1024];
};
d. 接收消息 msgrcv
cpp
int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
- msgtyp:要接收的消息类型
- 0:接收队列中第一条任意类型消息
-
0:接收指定类型的第一条消息
- <0:接收类型 ≤ |msgtyp| 的最小类型消息
- msgsz:数据缓冲区大小
- msgflg:一般为 0(阻塞),或 IPC_NOWAIT、MSG_NOERROR 等
(3)命令行管理
- 查看消息队列:ipcs -q
- 删除消息队列:ipcrm -q msqid