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

进程间通信————system V 共享内存

1.进程通信的本质

使不同进程能够访问同一份共享资源,通过对这份资源的读写操作来实现数据交换或同步协作。

2.共享内存原理

共享内存的原理与动态库相似,其过程是操作系统先在物理内存中创建一份空间,再通过页表将该物理空间映射到进程地址空间的共享区,最后向应用层返回该映射的起始地址,用户便可直接访问。其他进程也能通过同样的方式,将这份内存通过页表映射到自己进程地址空间的共享区,进而获得起始地址并返回给应用层。从此往后两个进程就可以通过各自的列表访问同一块物理内存。

注意:以上的操作都不是进程直接做的,是由操作系统直接操作的。

进程间通信的底层实现依赖操作系统的核心管理能力,尤其是共享内存这类直接操作物理内存的机制,完全是由操作系统主导的。

因为后续可能有其他进程也想进行共享内存进行通信,所以操作系统还需要管理物理内存,而这一管理逻辑始终遵循 “先描述、再组织” 的方式:通过内核结构体(如专门定义的共享内存描述符)对每块共享内存的关键属性(如物理地址、大小、权限、引用关系等)进行精确刻画,以此建立对资源的 “认知”;进而,借助链表、数组或哈希表等数据结构将这些描述结构体有序组织,形成可高效检索、遍历与维护的管理体系。

3.总结步骤

3.1 创建与访问步骤

  1. 操作系统先在物理内存中开辟一块专用空间,作为进程间共享的基础资源;
  2. 借助页表机制,将这块物理空间映射到参与通信的进程各自地址空间的共享区域,建立虚拟地址与物理地址的关联;
  3. 向应用层返回映射后的虚拟地址起始位置,进程即可通过该地址直接读写共享内存,实现数据交互。

3.2 内存释放逻辑(去关联过程)

  1. 当进程不再使用时,先解除自身地址空间与共享内存的映射关系(断开页表关联);
  2. 待所有关联进程均完成解除映射后,操作系统最终释放对应的物理内存资源。

4.相关接口

4.1 ftok 生成共享内存键值

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

参数:

  • pathname:已存在的文件 / 目录路径(进程可访问,用于生成唯一标识)。
  • proj_id:8 位非 0 整数(1-255),相同路径 + 不同proj_id生成不同key。

返回值:

  • 成功返回key_t键值。
  • 失败返回-1(设errno)。

4.2 shmget 创建 / 获取共享内存

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

参数:

  • key:ftok生成的键值,标识共享内存。
  • size:共享内存大小(创建时需指定,获取时设为 0)。
  • shmflg:标志位(组合使用):
    • IPC_CREAT:不存在则创建,存在则获取。
    • IPC_CREAT | IPC_EXCL:确保创建新段(已存在则报错)。
    • 权限标志(如0666):指定读写权限。

返回值:

  • 成功返回shmid(共享内存标识符)。
  • 失败返回-1(设errno)。

4.3 shmat 映射共享内存到进程

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);

参数:

  • shmid:shmget返回的共享内存标识符。
  • shmaddr:映射的起始地址(NULL表示由系统自动分配,推荐)。
  • shmflg:连接方式(0为默认读写;SHM_RDONLY为只读)。

返回值:

  • 成功返回进程内的共享内存起始地址。
  • 失败返回(void *)-1。

4.4 shmdt 解除映射

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);

参数:shmaddr为shmat返回的共享内存起始地址。
返回值:

  • 成功返回0。
  • 失败返回-1(设errno)。

4.5 shmctl 控制 / 释放共享内存

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数:

  • shmid:共享内存标识符。
  • cmd:控制命令:
    • IPC_RMID:删除共享内存(仅所有者或 root 可执行),需所有进程解除映射后才释放物理内存。
    • IPC_STAT:获取共享内存状态(存到buf中)。
  • buf:状态结构体指针(IPC_RMID时设为NULL)。

返回值:

  • 成功返回0。
  • 失败返回-1(设errno)。

5.代码示例

5.1 创建一个共享内存支持两个进程进行通信。进程A 向共享内存当中写 “I am process A”,进程B 从共享内存当中读出内容并打印到标准输出。

正确分工:

  • 服务端(接收端)进程负责创建和销毁共享内存
  • 客户端(发送端)进程只需连接到存在的共享内存

com.hpp

// com.hpp
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>// 定义共享内存相关常量
#define pathname "/practice/lesson-test/pro_communication/sharedmem"
#define proj_id 0x6666
#define size 4096// 错误码枚举
enum
{FIFO_ERROR = 1,UNLINK_ERROR,OPEN_ERROR,READ_ERROR,FTOK_ERROR,SHMGET_ERROR
};// 获取共享内存键值
key_t getKey()
{int key = ftok(pathname, proj_id);if (key == -1){perror("ftok");exit(FTOK_ERROR);}return key;
}// 辅助函数:获取共享内存标识符
int getshmidHelper(int shmflg)
{key_t key = getKey();int shmid = shmget(key, size, shmflg);if (shmid == -1){perror("shmget");exit(SHMGET_ERROR);}return shmid;
}// 创建共享内存(如果不存在)
int creatshmflg()
{return getshmidHelper(IPC_CREAT | IPC_EXCL | 0666);
}// 获取已存在的共享内存
int getshmflg()
{return getshmidHelper(IPC_CREAT);
}

processA.cpp

// processA.cpp
#include "com.hpp"int main()
{// 获取共享内存标识符int shmid = getshmflg();// 将共享内存映射到当前进程的地址空间char *shmaddr = (char *)shmat(shmid, nullptr, 0);// 循环发送消息while (true){// 从标准输入读取消息并写入共享内存cout << "A sends a message to B:";fgets(shmaddr, size, stdin);}// 解除共享内存映射shmdt(shmaddr);return 0;
}

processB.cpp

// processB.cpp
#include "com.hpp"int main()
{// 创建共享内存(如果不存在)int shmid = creatshmflg();// 将共享内存映射到当前进程的地址空间char *shmaddr = (char *)shmat(shmid, nullptr, 0);// 循环接收消息while (true){// 从共享内存读取消息并输出cout << "B received a message from A:" << shmaddr;sleep(1);}// 解除共享内存映射shmdt(shmaddr);// 删除共享内存shmctl(shmid, IPC_RMID, nullptr);return 0;
}

5.2 获取共享内存的属性

5.2.1 与共享内存相关的结构体

struct shmid_ds  用于存储共享内存段的详细属性信息。
struct ipc_perm  用于描述 IPC 资源(包括共享内存、消息队列、信号量)的权限和所有权。

struct shmid_ds {struct ipc_perm shm_perm;    /* 共享内存的所有权和权限信息 */size_t shm_segsz;   /* 共享内存段的大小(单位:字节) */time_t shm_atime;   /* 最后一次关联的时间 */time_t shm_dtime;   /* 最后一次调用 shmdt() 分离共享内存的时间(UNIX 时间戳) */time_t shm_ctime;   /* 最后一次修改共享内存属性的时间(如权限变更、大小调整等) */pid_t shm_cpid;    /* 创建该共享内存段的进程 PID(调用 shmget 创建的进程) */pid_t shm_lpid;    /* 最后一次执行关联或去关联进程 PID(最近操作的进程) */shmatt_t shm_nattch;  /* 当前附加到该共享内存段的进程数量(引用计数,0 表示无进程使用) */...                          /* 系统特定的扩展字段(不同内核版本可能有差异) */
};
struct ipc_perm {key_t __key;    /* 创建共享内存时使用的 key 值(由 ftok指定) */uid_t uid;      /* 共享内存当前所有者的有效用户 ID(可通过 IPC_SET 修改) */gid_t gid;      /* 共享内存当前所有者的有效组 ID(可通过 IPC_SET 修改) */uid_t cuid;     /* 共享内存创建者的有效用户 ID(创建后不可修改) */gid_ cgid;     /* 共享内存创建者的有效组 ID(创建后不可修改) */unsigned short mode;     /* 权限位 */unsigned short __seq;    /* 系统内部序列号(用于防止 IPC 资源重复,内核自动维护) */
};

5.2.2 代码实现

结合5.1的代码将processB.cc代码修改即可

#include "com.hpp"  int main()
{int shmid = creatshmflg();char *shmaddr = (char *)shmat(shmid, nullptr, 0);// 定义struct shmid_ds结构体变量,用于存储共享内存的状态信息// 该结构体由内核维护,包含共享内存的大小、连接数、权限等属性struct shmid_ds shmds;// 循环读取并打印共享内存中的数据,同时获取共享内存状态信息while (true){// 从共享内存中读取进程A发送的消息并打印cout << "B received a message from A:" << shmaddr;// 休眠1秒,避免频繁打印sleep(1);// 通过shmctl系统调用获取共享内存的状态信息,存储到shmds中// IPC_STAT命令表示:获取共享内存的属性,并存入第三个参数指向的结构体shmctl(shmid, IPC_STAT, &shmds);// 打印共享内存的大小(字节数),shm_segsz是结构体中记录共享内存大小的成员cout << "shm size:" << shmds.shm_segsz << endl;// 打印当前连接到该共享内存的进程数(nattch = number of attach)cout << "shm nattch:" << shmds.shm_nattch << endl;// 以十六进制形式打印共享内存的大小(与上面的十进制对应,方便对比)printf("0x%x\n", shmds.shm_segsz);// 打印共享内存的权限模式(类似文件权限,如0666)cout << "shm mode:" << shmds.shm_perm.mode << endl;}shmdt(shmaddr);shmctl(shmid, IPC_RMID, nullptr);return 0;
}

6.相关命令

6.1 查看系统中共享内存信息

ipcs -m

输出信息说明:

  • key:共享内存的键值,用于唯一标识共享内存段
  • shmid:共享内存的唯一 ID,操作共享内存时需使用该 ID
  • owner:共享内存的所有者(用户)
  • perms:共享内存的访问权限(类似文件权限,如 644、755 等)
  • bytes:共享内存段的大小(单位:字节)
  • nattch:当前连接到该共享内存段的进程数
  • status:共享内存的状态(如是否被标记为删除)

6.2 删除共享内存标识

ipcrm -m [shmid]  # [shmid] 替换为实际的共享内存ID

具体来说,它会标记对应的共享内存段为待删除状态,当所有关联到该共享内存段的进程都通过 shmdt  系统调用完成拆离后,系统会真正释放该共享内存段所占用的资源(包括内存空间和相关内核数据结构)。

7.共享内存的特性

  1. 生命周期:共享内存的生命周期是随内核的,用户主动关闭或者内核重启,共享内存才会释放。
  2. 同步与互斥:无内置保护机制,需用户自行实现同步(如结合信号量、管道等),否则可能出现数据不一致问题。
  3. 通信效率:是所有进程间通信方式中速度最快的,因为数据直接在共享内存中读写,无需内核中转;仅需一次映射(shmat),之后操作无系统调用开销(拷贝次数极少)。
  4. 数据管理:共享内存中的数据完全由用户进程维护,操作系统不参与数据的读写或格式处理。

8.扩展:解决共享内存没有同步互斥的保护机制的问题

在共享内存的通信中,由于其本身没有同步互斥机制,可能出现 “读方没准备好就读” 或 “写方没写完就被读” 的问题。而命名管道(FIFO)可以通过 “信号传递” 的方式,为共享内存提供简单的同步机制,原理如下:

  • 命名管道的特性:命名管道是一种半双工的通信方式,支持进程间的阻塞读写 —— 当管道中没有数据时,读操作会阻塞;当管道满时,写操作会阻塞。

com.hpp

// com.hpp
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>  
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>    // 共享内存相关配置
#define pathname "/practice/lesson-test/pro_communication/sharedmem"  // ftok生成key的路径
#define proj_id 0x6666                                                // ftok的项目ID
#define size 4096                                                     // 共享内存大小(4KB,页大小整数倍)
// 命名管道相关配置
#define FIFO_FILE "./myfifo"  // 命名管道文件路径
#define MODE 0666             // 权限(所有者、组、其他用户均有读写权限)        
using namespace std;// 错误码枚举(便于定位错误类型)
enum
{FIFO_ERROR = 1,   // 创建管道失败UNLINK_ERROR,     // 删除管道失败OPEN_ERROR,       // 打开文件失败READ_ERROR,       // 读操作失败FTOK_ERROR,       // 生成key失败SHMGET_ERROR      // 获取共享内存失败
};// 初始化类:负责命名管道的创建与销毁
class Init
{
public:// 构造函数:程序启动时创建命名管道Init(){int ret = mkfifo(FIFO_FILE, MODE);if (ret == -1){perror("mkfifo");  exit(FIFO_ERROR);  }}// 析构函数:程序结束时删除命名管道~Init(){int m = unlink(FIFO_FILE);if (m == -1){perror("unlink");exit(UNLINK_ERROR);}}
};// 生成共享内存的唯一标识key
key_t getKey()
{int key = ftok(pathname, proj_id);if (key == -1){perror("ftok");exit(FTOK_ERROR);}return key;
}// 辅助函数:通过shmget获取共享内存ID(根据传入的标志位)
int getshmidHelper(int shmflg)
{key_t key = getKey();  int shmid = shmget(key, size, shmflg);if (shmid == -1){perror("shmget");exit(SHMGET_ERROR);}return shmid;
}// 创建新的共享内存(若已存在则报错)
int creatshmflg()
{return getshmidHelper(IPC_CREAT | IPC_EXCL | 0666);
}// 获取已存在的共享内存(若不存在则创建)
int getshmflg()
{return getshmidHelper(IPC_CREAT);
}

processA.cpp

// processA.cpp
#include "com.hpp"int main()
{// 获取共享内存ID(若不存在则创建)int shmid = getshmflg();// 将共享内存映射到当前进程地址空间(系统自动分配地址,读写权限)char *shmaddr = (char *)shmat(shmid, nullptr, 0);// 打开命名管道(只写模式),用于通知进程B有新消息int fd = open(FIFO_FILE, O_WRONLY);if (fd == -1){perror("open");exit(OPEN_ERROR);}// 向共享内存写入消息while (true){cout << "A sends a message to B:";  // 提示输入// 从标准输入读取字符串,写入共享内存fgets(shmaddr, size, stdin);// 向管道写入一个字符(如'c'),通知B已写入新消息(同步机制)write(fd, "c", 1);}// 解除共享内存映射shmdt(shmaddr);// 关闭管道文件描述符close(fd);return 0;
}

processB.cpp

// processB.cpp
#include "com.hpp"int main()
{Init init;  // 创建Init对象,构造函数中创建命名管道,析构时删除// 创建共享内存(若已存在则报错,确保是新创建的共享内存)int shmid = creatshmflg();// 将共享内存映射到当前进程地址空间(系统自动分配地址,读写权限)char *shmaddr = (char *)shmat(shmid, nullptr, 0);// 打开命名管道(只读模式),用于接收进程A的消息通知int fd = open(FIFO_FILE, O_RDONLY);if (fd == -1){perror("open");exit(OPEN_ERROR);}// 循环接收并打印消息while (true){char c;  // 用于接收管道中的通知字符// 从管道读取通知(阻塞等待,直到A写入数据)int n = read(fd, &c, sizeof(c));if (n == 0)  // 管道另一端关闭(A退出){break;}else if (n < 0)  // 读操作出错{break;}// 从共享内存读取A发送的消息并打印cout << "B received a message from A:" << shmaddr;sleep(1);  // 休眠1秒,避免频繁打印}// 解除共享内存映射shmdt(shmaddr);// 删除共享内存shmctl(shmid, IPC_RMID, nullptr);// 关闭管道文件描述符close(fd);return 0;
}

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

相关文章:

  • Python day27
  • 在rsync + inotify方案中,如何解决海量小文件同步效率问题?
  • 从视觉到智能:RTSP|RTMP推拉流模块如何助力“边缘AI系统”的闭环协同?
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘nbconvert’问题
  • Java设计模式-通俗举例
  • 铜金矿数据分组优化系统设计与实现
  • 扩展和插件功能
  • 网络 编程
  • C#_运算符重载 operator
  • Joint.cpp - OpenExo
  • Windows 11 下 Anaconda 命令修复指南及常见问题解决
  • MCP error -32000: Connection closed
  • ESP32学习-按键中断
  • 【unitrix】 6.20 非零整数特质(non_zero.rs)
  • Laravel 分页方案整理
  • 小智源码分析——音频部分(二)
  • 数据开源 | “白虎”数据集首批开源,迈出百万数据征途第一步
  • 阿里云正式开源 LoongSuite:打造 AI 时代的高性能低成本可观测采集套件
  • 自学嵌入式 day36 数据库
  • Java面试宝典:MySQL事务底层和高可用原理
  • MR-link-2:多效性顺式孟德尔随机化分析!
  • <PLC><西门子><modbusTCP>在西门子S7-1200系列PLC中,如何设置modbusTCP通讯?
  • 介绍一下static关键字
  • Go 原理之 GMP 并发调度模型
  • 未授权访问漏洞靶场(redis,MongoDB,Memcached...)
  • 从0到1学PHP(一):PHP 基础入门:开启后端开发之旅
  • 境外期货Level2高频Tick历史行情数据获取与应用指南
  • 墨者:SQL注入实战-MySQL
  • 中兴云电脑W101D2-晶晨S905L3A-2G+8G-安卓9-线刷固件包
  • **线程与进程的区别与联系**