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

systme V共享内存(version1)

system V是有关共享内存的一个标准 , 具体的实现有 **共享内存 / 消息队列 / 信号量 **三种 .

一. 共享内存:

1.核心原理:

  • 在学习程序地址空间的时候 , 有一个示意图 , 在栈区和堆区之间存在一个共享区 , 这便是共享内存的来源 .
  • 包括程序在加载时 , 动态库的代码和数据也是处在段空间 , 以达到一份资源多个进程使用的目的.
  • 共享内存服务于进程间通信 , 本质还是让多个进程看到同一份资源 , 因此"共享"一词非常见名知义.

2.优缺点:

  • 优点 : 速度快 , 因为各个进程直接就能访问这块空间 , 不需要经过任何系统调用或者中间层
  • 缺点 : 通信双方对对方的行为是全然不知的 , 因此在缺乏并发编程中所需要的互斥性/顺序性 . 需要通过之后的信号量来解决.

二.共享内存相关函数:

1,ftok

  • ftok函数里包含了一套算法 , 通过接受传入的路径项目id来生成一个唯一的key值.
//声明和定义
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
//测试代码int i = ftok(".",1);int j = ftok(".",2);int k = ftok(".",1);std::cout << "i: "<< i << std::endl;std::cout << "j: " << j << std::endl;std::cout << "k: " << k  <<std::endl;
#运行结果(i和k由同样的路径和项目id生成了同样的key值)
i: 16981300
j: 33758516
k: 16981300

2,shmget

  • shmget函数用于在共享区申请一块共享内存 , 返回一个整形值shm_id来表示这块内存
  • 注意 : 如果是创建方,还需要在
//声明和定义
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);
//第一个参数key就是由ftok生成的值
//第二个参数size就是想要开辟的共享内存大小
//第三个参数shmfla就是通过位掩码传参,指定创建方式,创建方传递IPC_CREAT | IPC_EXCL //                                                                  |权限掩码
//                                          使用方传递 IPC_CREAT即可
//                                          IPC_CREAT用于申请空间
//                                          IPC_EXCL确保创建成功
//测试代码
int key = ftok("./common",1);
int id = shmget(key,4096,IPC_CREAT|IPC_EXCL);

3,shmat

  • shmat函数用于让进程和已申请的共享内存绑定 , 此后才能使用.
//函数声明和定义
#include <sys/shm.h>
void *shmat(int shmid, const void *_Nullable shmaddr, int shmflg);
//参数一shmid就是函数shmget的返回值
//参数二shmaddr用来指定共享内存的映射地址,可以填nullptr,让操作系统决定
//参数三shmflg是一些选项,这里暂时不用
//返回值是一个void*类型的指针,标识共享内存,可以强制类型转换后使用
//测试代码
int key = ftok("./common",1);
int shm_id = shmget(key,4096,IPC_CREAT|IPC_EXCL);char* ptr = (char*)shmat(shm_id,nullptr,0);//此后就可以通过ptr来访问共享内存了!

4,shmdt

shmdt函数用于让进程和共享内存解绑,共享内存只有在所有进程都解绑后才能真正释放.

//函数声明和定义
#include <sys/shm.h>
int shmdt(const void *shmaddr);
//返回值用来表示是否解绑成功
//参数一shmaddr就是shmat函数返回的共享内存的虚拟地址
//测试代码
int key = ftok("./common",1);
int shm_id = shmget(key,4096,IPC_CREAT|IPC_EXCL);    
char* ptr = (char*)shmat(shm_id,nullptr,0);int r = shmdt(ptr); 

5,shmctl

  • shmctl函数是用来执行对共享内存的一些控制操作的
//函数声明和定义
#include <sys/shm.h>
int shmctl(int shmid, int op, struct shmid_ds *buf);
//返回值用来标识异常情况,-1为异常,0为正常
//参数一就是shmget返回的shm_id值
//参数而是选项 , 常见的有IPC_RMID用来根据shm_id删除
//测试代码
int key = ftok("./common",1);
int shm_id = shmget(key,4096,IPC_CREAT|IPC_EXCL);    
char* ptr = (char*)shmat(shm_id,nullptr,0);
int r = shmdt(ptr); int ret = shmctl(shm_id,IPC_RMID,nullptr);

三.基于共享内存的简易进程间通信:

通过共享内存来实现进程间通信十分简单 , 只要通过相关函数让进程和某一块共享内存绑定后 , 在程序里就可以像通过指针访问堆空间那样访问共享内存了.

1. 设计核心:职责分离与高效通信

整个通信机制建立在一个基本原则上:数据要快,通知要准。

[!NOTE] 核心策略:

  • 共享内存负责跑数据:实现高性能

  • 命名管道负责发信号:实现严格同步

1.1. 高性能数据传输([[Shm Class]])

  • 共享内存的巨大优势在于,一旦建立,client 和 server 就能直接访问同一块内存区域。

  • 避免了传统 IPC 方式中内核态与用户态之间的数据拷贝,极大地提升了数据交换速度。

  • 在我们的设计中,client 不断地向这块内存写入新数据(A 到 Z),而 server 负责从中读取。数据是 Shm 类中的 char* ptr

1.2. 轻量级进程同步([[NamedPipe Class]])

[!同步的必要性]

  1. 在程序中 , 我希望一个进程写两个字符,另一个进程就读两个进程.
  2. 但是,两个进程虽然读取同一块共享内存,彼此之间却是独立的,也就是另一个进程一直在自顾自的读.
  3. 因此就需要一种方法,使得一个进程写入两个字符后能够通知另一个进程来读.
  4. 这里使用了管道作为传递这个信息的媒介
  • NamedPipe 在这里不承担大量数据传输任务,它充当一个信号机制(Notifier)

  • client 写入一个极小的字节(例如代码中的 ’c’),目的是唤醒正在等待的 server 进程。

  • server 通过 Wait() 阻塞等待管道数据。一旦数据到达,它立即知道共享内存中的数据已更新,可以开始读取。


2. 关键类的实现逻辑

2.1. Shm 类:共享内存的生命周期管理

Shm 类通过区分两个身份 CREATOR (Server)USER (Client) 来管理资源,确保资源的安全创建和释放。

步骤角色动作目的
分配CREATOR调用 Create()使用 IPC_EXCL 确保它是内存段的唯一创建者。
获取USER调用 Get()获取已存在的内存段 ID (shmget)。
挂载共同调用 Attach()使用 shmat 将内存段映射到各自进程地址空间。
销毁CREATOR析构函数中调用 Destory()只有 Server 负责使用 shmctl(IPC_RMID) 释放共享内存,避免 client 意外销毁。

2.2. NamedPipe 类:实现阻塞与唤醒

NamedPipe 类利用了 FIFO 文件在 open 和 read 时的阻塞特性来实现同步。

方法角色机制效果
Wait()Serveropen(O_RDONLY) 和 read()Server 进程阻塞,暂停 CPU 运行,等待数据。
Awake()Clientopen(O_WRONLY) 和 write(’c’)写入操作立即解除 server 的阻塞,唤醒它。

3. 程序的实际运行机制

  1. 启动阶段: server 进程创建共享内存和命名管道,随后进入 np.Wait() 状态,开始阻塞等待

  2. 数据写入: client 进程获取共享内存,开始循环。它向 ptr 地址写入一对新字符(例如 ’AA’),并确保字符串以 KaTeX parse error: Undefined control sequence: \0 at position 8: \text{'\̲0̲'} 结尾。

  3. 发送通知: 数据写入完成后,client 立刻调用 np.Awake() 向管道写入一个字节。

  4. 接收响应: 管道中的数据解除 server 的阻塞,server 进程被唤醒,并立即从共享内存中读取数据 (printf("%s\n", ptr);)。

  5. 循环往复: server 打印后再次进入 np.Wait() 沉睡,等待 client 的下一个信号。

四.消息队列:

消息队列和上面谈到的共享内存属于同一个system V标准 , 因此在设计和使用上极其的类似.

1.核心原理:

可以把消息队列想象成操作系统内核里的一个"快递站" , 由数据结构队列来实现 .

假设进程A和B之间要通信 , 言下之意就是要能看到同一份资源 , 只需要访问这个共享的队列即可.

  • 发送 : 进程A把发送的数据打成一个包裹(队列节点) , 交给操作系统 , 由操作系统将这个节点链接到队列之中.
  • 接受 : 进程B通过访问这个队列 , 拿到进程A发送的包裹(访问到指定队列节点) , 这样就完成了一次通信.

[!困境]
进程B怎么知道快递站的那个包裹(队列里的那个节点)是自己的呢?

[!解法]

  • 快递站的包裹里除了有我们需要的物件 , 包裹面上还有这个包裹的相关信息.
  • 因此 , 进程A在发送数据时 , 除了发送数据本身 , 再附带上数据的身份信息即可 . 对于数据来说,这个包裹就是一个结构体—既包含物件(数据),也包含身份信息(属性)

这个过程是双向的 , 进程A可以发信息给进程B , B同样可以发送给A

2.接口与命令

消息队列的接口和共享内存惊人地相似,这就是“标准”的力量,学起来有很好的可迁移性

  1. 获取 Key: 同样使用 ftok 来生成一个全局唯一的 key 值,让不同进程能找到同一个资源 。
  2. 创建/获取 (msgget): 类似于 shmget,通过 keyflag (如 IPC_CREAT | 0666) 来创建或获取一个消息队列的标识符 (ID) 。
  3. 发送/接收 (msgsnd / msgrcv):
    • 发送 (msgsnd): 指定要往哪个队列 ID 发送数据。你需要自己定义一个结构体,里面必须包含一个 long 类型的 mtype (消息类型) 和一个数据缓冲区 mtext
    • 接收 (msgrcv): 除了指定从哪个队列 ID 接收,还要多传一个 msgtyp 参数,告诉内核你只想接收哪种类型的消息 [cite: 16]。
    • 一个细节: msgsndmsgrcv 中的 msgsz 参数,严格来说指的是消息正文 (mtext) 的大小,而不是整个结构体的大小 [cite: 18]。
  4. 控制/删除 (msgctl): 和 shmctl 一样功能强大。
    • IPC_RMID: 立刻删除消息队列 [cite: 14]。
    • IPC_STAT: 从内核拷贝消息队列的属性信息到一个用户空间的结构体 (msqid_ds) 中 。
  5. 命令行工具:
    • 查看: ipcs -q
    • 删除: ipcrm -q <msqid>

3.简单代码实践:

//client.cpp
#include <iostream>
#include <sys/ipc.h> //ftok
#include<sys/msg.h> //msgget msgsnd 
struct my_msgbuf
{
long mtype;
char mtext[2];
};int main()
{//基于消息队列的进程间通信(写端)int key = ftok(PATH_NAME,PRO_ID);int msg_id = msgget(key,CLIENT);my_msgbuf data;data.mtype = 1;data.mtext[0] = 'a';data.mtext[1] = '\0';msgsnd(msg_id,&data,2,0);sleep(5);
}
//server.cpp
#include <iostream>
#include <sys/ipc.h> //ftok
#include<sys/msg.h> //msgget  msgrcv
struct my_msgbuf
{long mtype;char mtext[2];
};
int main()
{// 基于消息队列的进程间通信int key = ftok(PATH_NAME, PRO_ID);int msg_id = msgget(key, SERVER);my_msgbuf data;//msgrcv(msg_id,&data,2,1,0);msgrcv(msg_id,&data,1,1,0);data.mtext[1] = '\0';std::cout << "接收到信息 : " << data.mtext << std::endl;sleep(6);msgctl(msg_id,IPC_RMID,nullptr);

4.和共享内存的差别:

共享内存消息队列
效率进程直接操作内存,飞快涉及用户态->内核态->用户态,略慢
数据同步机制完全没有数据以结构体对象传递 , 保证写入的原子性

五.并发编程的基础概念:

在了解信号量前 , 势必要涉猎一点并发编程的概念 . 共享内存速度虽快,却缺乏保护机制 .
比如一个进程在像共享内存里写 , 另一个也在写 , 这样就会互相覆盖,导致数据丢失.

1. 共享资源 :

进程间通信的本质就是让不同的进程看到同一份资源 , 也就是共享了 , 这个好理解.

2.临界资源:

乍一看, "临界"一词很抽象 , 但是对于临界资源的英文翻译是critical resource , 即关键的资源 .

[!临界资源-critical resource]
英文翻译首先为其奠定了基调 , 即十分重要和关键 .

随后可以这样理解 , 试想一种情况 : 进程A期望给进程B发送一段字符串"aabbcc" ,这就是很关键的内容 , 如果进程B读到的是不完整的"aa",那就会出各种问题 .

因此临界区的概念属于逻辑上的划分,如下图
![[临界区.png]]

3.互斥:

[!ATM机的例子]
银行的 ATM 小隔间。ATM 机就是临界资源,取钱的一系列操作就是临界区。门锁就是互斥机制。你进去后把门一锁,在你出来之前,谁也进不去,保证了你取钱过程的独占和安全

要想解决这种情况 , 就需要一种机制 , 使得同一块资源在同一时间只能被一个进程修改

4.同步:

同步机制在一定程度上确保了临界资源的完整性

  • 互斥强调独占 , 而同步强调顺序.
  • 它指的是当多个进程或者线程在访问资源时,要严格遵循一定的顺序.
  • 比如让进程A写一点,然后进程B才能读,进程B读完后进程A才能继续写

5.原子性:

原子性是用来描述操作的不可分割性 , 即一次数据操纵(读or写)要么成功失败 , 不存在中间态(读一部分or写一部分).

6.总结:

总之 , 为了确保在多进程下安全的共享资源 , 就需要明确临界资源的概念 , 通过互斥来保证一个地方一次只能来一个同步来保证必须一个一个来 . 最后通过原子性保证这些动作在执行过程中不要被打断 ,从而实现目的.

六.信号量:

信号量像一个红绿灯 , 限定一个地方现在能不能让进程过去.

两种类型 :

  1. 二元信号量 : 如果一块资源只能由一个进程独占 , 那就可以用两态来标识资源的占据与否 .
  2. 多元信号量 : 如果一块资源被划分为了很多块 , 就可以用一个信号量来标识这块区域资源的占用情况 . 类似于一个计数器 , 每占用一块 , 计数器-- . 计数器归零,则不允许新的进程来访问

类比电影院:

![[Pasted image 20251011114020.png|500]]

![[Pasted image 20251011114159.png|500]]

http://www.dtcms.com/a/469647.html

相关文章:

  • 万网网站制作wordpress投稿管理系统
  • python(47) : 快速截图[Windows工具(2)]
  • VSCODE GDB调试
  • 江西企业网站定制wordpress网页效果
  • CCF-GESP 等级考试 2024年6月认证C++三级真题解析
  • 前端学习1(学习时间:30分钟简单)
  • vlan范围
  • 北京顺义做网站编程正规学校有哪几所
  • 跨平台向量库:Linux Windows 上一条龙部署 PostgreSQL 向量扩展
  • 基于YOLO与DeepSort的高效行人跟踪算法研究
  • [GazeTracking] 视线追踪核心逻辑 | Dlib库
  • docker安装Xinference
  • 从标准化到个性化:基于代理模式的智能办公工具技术实现与趋势
  • AI(学习笔记第十一课) 使用langchain的multimodality
  • 如何配置iis网站国企500强完整名单
  • ppt做的最好的网站有哪些品牌建设方案和思路
  • [cpprestsdk] http_client_config | GET | request()
  • HQChart使用教程30-K线图如何对接第3方数据46-DRAWTEXT_FIX数据结构
  • 奥特蛋的做网站门户网站的发布特点
  • Unity网络开发--超文本传输协议Http(2)
  • Java后端开发核心技术选型指南(优化版)
  • 数字图像处理-图像的傅里叶变换
  • 分布式缓存架构:从原理到生产实践
  • 现代计算机视觉任务综合概述:定义、方法与应用
  • ai 作物分类
  • sm2025 模拟赛11 (2025.10.7)
  • WebSocket 是什么原理?为什么可以实现持久连接?
  • 【开题答辩全过程】以 宝成花园物业管理系统为例,包含答辩的问题和答案
  • CORS配置实战:SpringBoot与Vite项目的本地联调解决方案
  • 长沙学做网站建设wordpress图片全部压缩