一、信号量
1. 基本概念
- 定义:用于实现进程间互斥与同步的机制,分为两种类型:
- 二进制信号量:只能取 0 或 1,最常用;
- 通用信号量:可以取多个正整数值。
- 核心作用:解决多个进程对共享资源(如串口设备)的 “互斥访问” 问题,避免冲突。


2. 核心操作(PV 操作)
- P 操作:
- 若信号量的值 > 0,将其减 1;
- 若信号量的值 = 0,当前进程被挂起。
- V 操作:
- 若有进程因等待该信号量被挂起,唤醒其中一个进程;
- 若没有等待进程,将信号量的值加 1。
- 特性:PV 操作是 “原子操作”,执行过程不会被中断。
3. 关键接口函数
-
semget(创建 / 获取信号量):

-
semctl(控制信号量):
- 功能:设置信号量值、删除信号量等;

- 许多linux系统都没有 union semun 类型的定义,需要手动定义:
// linux上面没有默认的semun,需要自己定义 typedef 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) */ } semun; int val:用于SETVAL命令,设置信号量的值。
struct semid_ds *buf:用于IPC_STAT和IPC_SET命令,获取或设置信号量集的状态。
unsigned short *array:用于GETALL和SETALL命令,获取或设置所有信号量的值。
struct seminfo *__buf:用于IPC_INFO命令(Linux 特定),获取系统信号量限制和状态信息。
- 功能:设置信号量值、删除信号量等;
-
semop(执行 PV 操作):

4. 代码实战
contorl.cpp
#include <cstdlib>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <unistd.h>
// linux上面没有默认的semun,需要自己定义
typedef 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) */
} semun;
/*
* 初始化信号量
* 参数:
* sem_id - 信号量标识符
* 返回值:
* 成功返回true,失败返回false
*/
bool sem_init(int sem_id)
{
semun sem_union;
sem_union.val = 1; // 将信号量初始化为1,表示资源可用
// 设置信号量的值
int res = semctl(sem_id, 0, SETVAL, sem_union);
if (res == -1) {
return false;
}
return true;
}
/*
* 删除信号量
* 参数:
* sem_id - 信号量标识符
* 返回值:
* 成功返回true,失败返回false
*/
bool sem_del(int sem_id)
{
semun sem_union;
// 注意:删除信号量不需要设置sem_union的值,但有些系统要求传递有效的union
// 这里保留赋值是为了兼容性考虑
// 删除信号量集
int res = semctl(sem_id, 0, IPC_RMID, sem_union);
if (res == -1) {
return false;
}
return true;
}
int main()
{
// 创建一个信号量集,包含1个信号量
// 1234 - 信号量的键值,用于标识信号量
// 0666 | IPC_CREAT - 权限为0666(所有用户可读可写),如果不存在则创建
int sem_id = semget(1234, 1, 0666 | IPC_CREAT);
if (sem_id == -1) {
perror("创建信号量(1234)失败!");
exit(1);
}
printf("创建信号量(1234)成功!\n");
// 初始化信号量
if (!sem_init(sem_id)) {
perror("信号量初始化失败!");
exit(1);
}
// 主循环:程序持续运行,直到用户输入"stop"
while (1) {
char str[256] = {0};
printf("正在工作,如果想停止请输入stop: ");
scanf("%s", str);
if (strcmp(str, "stop") == 0) {
break;
}
}
// 删除信号量,释放系统资源
if (!sem_del(sem_id)) {
perror("信号量删除失败!");
exit(1);
}
return 0;
}
work.cpp
#include <cstdlib>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <unistd.h>
/*
struct sembuf 是信号量操作的核心结构体,用于定义对信号量的具体操作
成员说明:
- sem_num:要操作的信号量编号(在信号量集中的索引,单个信号量时通常取0)
- sem_op:操作类型(-1表示P操作/等待,1表示V操作/释放)
- sem_flg:操作标志(SEM_UNDO表示进程退出时自动撤销操作,避免信号量状态异常)
*/
/**
* 信号量P操作(等待操作)
* 功能:尝试获取信号量,若信号量值为0则阻塞等待,直到可用
* 参数:sem_id - 信号量标识符
* 返回值:成功返回true,失败返回false
*/
bool sem_p(int sem_id)
{
struct sembuf sem_buf; // 定义信号量操作结构体
sem_buf.sem_num = 0; // 操作第0个信号量(信号量集的第一个)
sem_buf.sem_op = -1; // P操作:信号量值减1(获取资源)
sem_buf.sem_flg = SEM_UNDO; // 进程异常退出时自动恢复信号量状态
// 执行信号量操作,参数分别为:信号量ID、操作结构体、操作数量(1个)
if (semop(sem_id, &sem_buf, 1) == -1) {
perror("sem P failed"); // 输出P操作失败原因
return false;
}
return true;
}
/**
* 信号量V操作(释放操作)
* 功能:释放信号量,将信号量值加1,唤醒等待的进程
* 参数:sem_id - 信号量标识符
* 返回值:成功返回true,失败返回false
*/
bool sem_v(int sem_id)
{
struct sembuf sem_buf; // 定义信号量操作结构体
sem_buf.sem_num = 0; // 操作第0个信号量
sem_buf.sem_op = 1; // V操作:信号量值加1(释放资源)
sem_buf.sem_flg = SEM_UNDO; // 进程异常退出时自动恢复信号量状态
// 执行信号量操作
if (semop(sem_id, &sem_buf, 1) == -1) {
perror("sem V failed"); // 输出V操作失败原因
return false;
}
return true;
}
int main()
{
// 获取已创建的信号量(键值1234,包含1个信号量,权限0666)
// 注意:此处依赖control程序先创建并初始化信号量,否则会失败
int sem_id = semget(1234, 1, 0666);
if (sem_id == -1) {
perror("信号量还没有创建好"); // 提示信号量未就绪
exit(1); // 退出程序
}
// 无限循环:持续通过信号量控制资源访问
while (1) {
sem_p(sem_id); // 执行P操作,获取信号量(进入临界区)
// 临界区操作:打印当前进程ID及工作状态
printf("进程-%d 正在工作...\n", getpid()); // getpid()获取当前进程ID
sleep(3); // 模拟工作耗时3秒
printf("进程-%d 工作结束\n", getpid());
sleep(3); // 模拟后续处理耗时3秒
sem_v(sem_id); // 执行V操作,释放信号量(退出临界区)
}
return 0;
}
运行结果:

结论:可以看出,每次都需要一个进程从开始到完整的结束为止,才会开始运行新的进程,信号量可以实现进程间互斥与同步的机制,且PV操作具有原子性
二、共享内存
1. 基本概念
- 原理:多个进程将同一块物理内存映射到各自的地址空间,直接读写该内存实现通信;
- 特点:
- 无内置同步机制,需程序员手动处理(如通过 “生产者 - 消费者” 逻辑控制读写顺序);
- 适用于传递大块数据,效率较高。
- 共享内存的原理

2. 关键接口函数
-
shmget(创建 / 获取共享内存)

-
shmat(映射共享内存):

-
shmctl(控制共享内存):

-
shmdt(解除映射):

3. 代码实战
- 场景:两个进程通过共享内存传递文本数据;
- 实现:
- 定义共享数据结构
struct shared_data,包含is_new_data(标志是否有新数据)和text(存储数据);- 进程 1(shm1.cpp):读取共享内存中的数据,读完后将is_
new_data设为 0;- 进程 2(shm2.cpp):写入数据后将is_
new_data设为 1,等待对方读取后再写入新数据,实现简单同步。
common.h
#ifndef COMMON_H
#define COMMON_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#define TEXT_SIZE 4096
typedef struct shared_data
{
int is_new_data; // 1: 有新的数据 0: 数据已经被读取
char text[TEXT_SIZE];
} shared_data;
#endif // !COMMON_H
shm1.cpp
#include "common.h"
int main()
{
// 创建或获取共享内存段,键值为1234,大小为struct shared_data的大小
// 0666表示读写权限,IPC_CREAT表示若不存在则创建
int shmid = shmget(1234, sizeof(struct shared_data), 0666 | IPC_CREAT);
// 检查共享内存创建/获取是否失败
if (shmid == -1) {
perror("shmget failed.");
exit(1);
}
// 将共享内存段映射到当前进程的地址空间
// 第二个参数为0,表示由操作系统自动选择映射地址
// 第三个参数为0,表示默认具有读写权限
void* shared_memory = shmat(shmid, 0, 0);
// 检查映射是否失败
if(shared_memory == (void*)-1) {
perror("shmat failed.");
exit(1);
}
// 打印映射后的内存地址,用于调试
printf("A映射后的地址: %lld\n", (long long)shared_memory);
// 运行标志,控制循环读取共享内存
int running = 1;
// 将共享内存转换为shared_data结构体类型的指针
shared_data* shared_buf = static_cast<shared_data*>(shared_memory);
// 初始化随机数种子,用于模拟不同的处理延迟
srand(getpid());
// 循环读取共享内存中的数据
while (running)
{
// 检查生产者是否写入了新数据
if(shared_buf->is_new_data) {
// 读取并打印新数据
printf("A读取到的数据: %s", shared_buf->text);
// 模拟数据处理过程,随机睡眠0-2秒
sleep(rand() % 3);
// 标记数据已处理,通知生产者可以写入新数据
shared_buf->is_new_data = 0;
// 判断是否接收到结束信号
// 注意:此处使用strcmp比较字符串,若返回0表示相等
// 原代码逻辑有误,应改为strcmp(shared_buf->text, "end") == 0
if(strcmp(shared_buf->text, "end") == 0) {
running = 0; // 接收到结束信号,退出循环
}
}
}
// 将共享内存从当前进程的地址空间分离
if(shmdt(shared_memory) == -1) {
perror("shmdt failed.");
exit(1);
}
// 删除共享内存段,释放系统资源
// 注意:只有当所有进程都分离后,共享内存才会真正被删除
if(shmctl(shmid, IPC_RMID, 0) == -1)
{
perror("shmctl IPC_RMID failed");
exit(1);
}
return 0;
}
shm2.cpp
#include "common.h"
int main()
{
// 创建或获取共享内存段,键值为1234,大小为struct shared_data的大小
// 0666表示读写权限,IPC_CREAT表示若不存在则创建
int shmid = shmget(1234, sizeof(struct shared_data), 0666 | IPC_CREAT);
// 检查共享内存创建/获取是否失败
if (shmid == -1) {
perror("shmget failed.");
exit(1);
}
// 将共享内存段映射到当前进程的地址空间
// 第二个参数为0,表示由操作系统自动选择映射地址
// 第三个参数为0,表示默认具有读写权限
void* shared_memory = shmat(shmid, 0, 0);
// 检查映射是否失败
if (shared_memory == (void*)-1) {
perror("shmat failed.");
exit(1);
}
// 打印映射后的内存地址,用于调试
printf("B映射后的地址: %lld\n", (long long)shared_memory);
// 运行标志,控制循环写入共享内存
int running = 1;
// 将共享内存转换为shared_data结构体类型的指针
shared_data* shared_buf = static_cast<shared_data*>(shared_memory);
// 用于临时存储用户输入的缓冲区
char buff[BUFSIZ];
// 循环等待用户输入并写入共享内存
while (running) {
// 检查消费者是否已处理完上一批数据
if (shared_buf->is_new_data) {
// 数据未处理,等待1秒后重试
sleep(1);
printf("等待数据被处理\n");
continue;
}
// 提示用户输入数据
printf("请输入需要发送的数据: ");
// 从标准输入读取一行数据
fgets(buff, BUFSIZ, stdin);
// 将用户输入复制到共享内存的text字段
// 使用strncpy防止缓冲区溢出,TEXT_SIZE为预设的最大长度
strncpy(shared_buf->text, buff, TEXT_SIZE);
// 标记数据已更新,通知消费者可以读取
shared_buf->is_new_data = 1;
// 检查用户是否输入"end"作为结束指令
// 比较前3个字符,避免包含换行符的影响
if(strncmp(buff, "end", 3) == 0) {
running = 0; // 设置结束标志,退出循环
}
}
// 将共享内存从当前进程的地址空间分离
if (shmdt(shared_memory) == -1) {
perror("shmdt failed.");
exit(1);
}
// 删除共享内存段(实际为标记删除,所有进程分离后才真正释放)
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl IPC_RMID failed");
exit(1);
}
return 0;
}
运行结果:


1077

被折叠的 条评论
为什么被折叠?



