当前位置: 首页 > news >正文

Linux编程:6、进程通信-信号量与共享内存

一、信号量

1. 基本概念

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


2. 核心操作(PV 操作)

  • P 操作
    • 若信号量的值 > 0,将其减 1;
    • 若信号量的值 = 0,当前进程被挂起。
  • V 操作
    • 若有进程因等待该信号量被挂起,唤醒其中一个进程;
    • 若没有等待进程,将信号量的值加 1。
  • 特性:PV 操作是 “原子操作”,执行过程不会被中断。

3. 关键接口函数

  • semget(创建 / 获取信号量)

  • semctl(控制信号量)

    • 功能:设置信号量值、删除信号量等;
    • 许多linux系统都没有 union semun 类型的定义,需要手动定义:
  • 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()获取当前进程IDsleep(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 4096typedef 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") == 0if(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;
}

运行结果:

http://www.lryc.cn/news/586869.html

相关文章:

  • OpenLayers 入门指南【二】:坐标系与投影转换
  • linux进程信号II
  • Node.js特训专栏-实战进阶:16. RBAC权限模型设计
  • 基于YOLOv7的改进模型:集成Swin Transformer和ASFF模块
  • 26-计组-数据通路
  • 【软件开发】使用 Spring WebFlux 进行请求校验
  • iOS ish app 打印时间
  • HJ8 合并表记录 10:35
  • Vue中的render()函数
  • 【LeetCode数据结构】单链表的应用——反转链表问题、链表的中间节点问题详解
  • 为什么要有延时回调?
  • 【实证分析】上市公司绿色战略数据集(2000-2023年)
  • 如何设计一个合理的 Java Spring Boot 项目结构
  • C++ 强制类型转换
  • 【读书笔记】《C++ Software Design》第六章深入剖析 Adapter、Observer 和 CRTP 模式
  • 开机自动启动同花顺,并设置进程优先级为高
  • Linux驱动开发1:设备驱动模块加载与卸载
  • 【Linux学习笔记】认识信号和信号的产生
  • JAVA JVM虚拟线程
  • HTML 初体验
  • 软件文档体系深度解析:工程视角下的文档架构与治理
  • OneCode3.0 VFS分布式文件管理API速查手册
  • jenkins使用Jenkinsfile部署springboot+docker项目
  • 代码随想录|图论|15并查集理论基础
  • Docker一键安装中间件(RocketMq、Nginx、MySql、Minio、Jenkins、Redis)脚步
  • SDN软件定义网络架构深度解析:分层模型与核心机制
  • Redis缓存设计与性能优化指南
  • 解码冯・诺依曼:操作系统是如何为进程 “铺路” 的?
  • [Nagios Core] CGI接口 | 状态数据管理.dat | 性能优化
  • 基于Redis Streams的实时消息处理实战经验分享