【Linux】Linux 进程通信:System V 共享内存(最快方案)C++ 封装实战 + 通信案例,4 类经典 Bug 快速修复

【Linux】Linux 进程通信:System V 共享内存(最快方案)C++ 封装实战 + 通信案例,4 类经典 Bug 快速修复
前言:欢迎各位光临本博客,这里小编带你直接手撕**,文章并不复杂,愿诸君**耐其心性,忘却杂尘,道有所长!!!!
在这里插入图片描述

IF’Maxue个人主页
 🔥 个人专栏:
《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》

⛺️生活是默默的坚持,毅力是永久的享受。不破不立!

文章目录

二、System V共享内存:最快的进程间通信

System V是Linux内核支持的一套IPC(进程间通信)标准,其中共享内存是速度最快的IPC方式——因为数据直接存在“共享物理内存”中,进程读写无需拷贝(其他IPC如FIFO需要内核中转)。

1. System V共享内存核心概念

  • IPC本质:让不同进程“看到同一份资源”。共享内存的“资源”是一块物理内存,OS通过“页表”将这块物理内存映射到多个进程的虚拟地址空间中。
  • 优势:读写速度快(进程操作自己的虚拟地址,等同于操作共享物理内存,无内核拷贝)。
  • 劣势:无同步机制(比如两个进程同时写,会导致数据混乱,需配合其他IPC(如FIFO)实现同步)。

2. System V共享内存原理

(1)进程虚拟地址空间结构

每个进程都有独立的虚拟地址空间,分为内核区、栈、堆、共享库区等,默认情况下进程间的内存互不干扰。

image.png
(2)共享内存映射过程

OS通过3步让进程共享内存:

  1. OS在物理内存中开辟一块“共享物理内存”。
  2. 进程A的虚拟地址空间中,分配一块虚拟地址,通过“页表”映射到共享物理内存。
  3. 进程B的虚拟地址空间中,也分配一块虚拟地址,同样通过页表映射到同一块共享物理内存。
    最终效果:进程A写自己的虚拟地址,进程B读自己的虚拟地址,就能拿到相同的数据。
(3)共享内存的管理:先描述,再组织

OS用一个“内核结构体”(struct shmid_ds)描述共享内存的信息(如大小、权限、引用计数),再用链表/哈希表组织所有共享内存,方便管理。

引用计数:记录有多少进程正在使用这块共享内存。只有引用计数为0时,OS才会真正释放物理内存(避免进程还在使用时内存被删)。

image.png

3. System V共享内存核心接口

操作共享内存需4个核心接口:ftok(生成唯一key)、shmget(创建/获取共享内存)、shmat(将共享内存映射到进程虚拟地址)、shmdt(解除映射)、shmctl(管理共享内存,如删除)。

(1)生成唯一Key:ftok
  • 功能:将“文件路径”和“项目ID(proj_id)”组合成一个唯一的key_t类型值(key),用于标识共享内存(确保不同进程能找到同一块共享内存)。
    • pathname:必须是已存在的文件路径(比如"./")。
    • proj_id:1~255的整数(自定义,只要进程间一致即可)。
    • 返回值:成功返回key,失败返回-1。

示例:两个进程都用ftok("./", 100),会生成相同的key,从而找到同一块共享内存。

image.png


image.png

接口参数与返回值

image.png
(2)创建/获取共享内存:shmget
  • 功能:根据key,创建新的共享内存,或获取已存在的共享内存(返回一个“共享内存ID(shmid)”,后续操作都用shmid)。
    • key:由ftok生成的唯一标识。
    • size:共享内存的大小(必须是4KB的整数倍,OS按页分配内存,不足4KB会自动补齐)。
    • shmflg:标志位,常用组合:
      • IPC_CREAT:如果共享内存不存在,则创建;如果已存在,则获取。
      • IPC_CREAT | IPC_EXCL | 0664:如果共享内存已存在,则报错(确保创建的是全新的共享内存);0664是共享内存的权限(和文件权限一致)。
    • 返回值:成功返回shmid(非负整数),失败返回-1。

返回值说明

image.png

接口参数与返回值

image.png
(3)映射共享内存到进程:shmat
  • 功能:将shmid对应的共享内存,映射到当前进程的虚拟地址空间中,返回映射后的虚拟地址(进程通过这个地址读写共享内存)。
    • shmidshmget返回的共享内存ID。
    • shmaddr:指定映射到进程虚拟地址的哪个位置(一般设为NULL,让OS自动分配,避免冲突)。
    • shmflg:映射标志(一般设为0,默认权限)。
    • 返回值:成功返回映射后的虚拟地址(void*类型),失败返回(void*)-1

示例

image.png


image.png

接口参数与返回值

image.png
(4)解除映射:shmdt
  • 功能:将共享内存从当前进程的虚拟地址空间中“解绑”(不会删除共享内存,只是进程看不到它了)。
    • shmaddrshmat返回的虚拟地址(必须和映射时的地址一致)。
    • 返回值:成功返回0,失败返回-1。
  • 注意:解除映射后,共享内存的“引用计数”会减1;若引用计数变为0,OS也不会立刻删除共享内存(需调用shmctl删除)。

接口参数与返回值

image.png
(5)管理共享内存:shmctl
  • 功能:对共享内存进行管理,如查看信息、修改权限、删除共享内存(最常用的是删除)。
    • shmidshmget返回的共享内存ID。
    • cmd:命令,常用IPC_RMID(删除共享内存)。
    • buf:指向struct shmid_ds的指针(用于传递/接收共享内存的信息,删除时可设为NULL)。
    • 返回值:成功返回0,失败返回-1。
    • 注意:调用shmctl(shmid, IPC_RMID, NULL)后,共享内存会被“标记为删除”,但不会立刻消失——直到所有进程都shmdt解除映射(引用计数为0),OS才会真正释放物理内存。

删除共享内存示例

image.png

接口参数与返回值

image.png

4. 共享内存实战:查看与删除(命令行)

(1)查看系统中的共享内存:ipcs -m
  • 功能:列出当前系统中所有System V共享内存的信息,包括shmid、key、大小、引用计数等。
    • shmid:共享内存ID(删除时用)。
    • keyftok生成的标识。
    • owner:创建共享内存的用户。
    • segsz:共享内存大小(4KB的整数倍)。
    • nattch:引用计数(当前有多少进程映射了这块内存)。

示例

image.png
(2)删除共享内存:ipcrm -m shmid
  • 功能:根据shmid删除共享内存(等同于代码中的shmctl(shmid, IPC_RMID, NULL))。
    • 注意:共享内存的生命周期“随内核”——即使创建它的进程退出,共享内存也会留在系统中(需手动ipcrm删除,或代码中shmctl删除),避免内存泄漏。

示例

image.png

5. 共享内存实战:Server与Client通信

(1)封装共享内存类(Shm.hpp)

为简化代码,用C++类封装共享内存的操作(创建/获取、映射、读写、删除)。

删除共享内存:提供Destroy(调用shmctl)成员函数,在析构函数中调用Detach

image.png

映射与解除映射:提供Attach(调用shmat)和Detach(调用shmdt)成员函数。

image.png

shmget失败(比如共享内存已存在),报错信息如下:

image.png

创建/获取共享内存:在构造函数中调用ftokshmget

image.png
(2)服务器端(server.cc):读共享内存

核心逻辑:创建共享内存 → 映射到虚拟地址 → 循环读数据 → 解除映射 → 删除共享内存。
代码示例:

image.png


image.png
(3)客户端(client.cc):写共享内存

核心逻辑:获取已存在的共享内存 → 映射到虚拟地址 → 写数据 → 解除映射(无需删除共享内存)。
代码示例:

image.png
(4)通信效果

客户端写入数据后,服务器能立刻读到共享内存中的内容(无需内核中转,速度极快)。

image.png

6. 共享内存的同步问题与解决

共享内存本身没有同步机制——如果服务器还没读,客户端就写了新数据,会覆盖旧数据;如果两个进程同时写,会导致数据混乱。

解决方法:结合FIFO实现同步(用FIFO作为“信号通道”,控制读写顺序)。
核心思路:

  1. 创建一个FIFO(同步通道)。
  2. 客户端:写共享内存 → 向FIFO写一个“信号”(比如1字节)。
  3. 服务器:从FIFO读“信号”(阻塞等待) → 读共享内存。
    这样就能确保“客户端写完,服务器才读”,避免数据覆盖。
(1)同步通道(FIFO)创建
image.png
(2)服务器端(同步读)

服务器先从FIFO读“信号”(阻塞等待客户端写完),再读共享内存。

image.png


image.png
(3)客户端(同步写)

客户端先写共享内存,再向FIFO写“信号”(唤醒服务器读)。

image.png


image.png

7. 常见问题与Bug解决

(1)权限拒绝(Permission denied)

调用shmat时若报错“权限拒绝”,是因为共享内存的权限不足(创建时shmflg没加权限)。
Bug效果:

image.png


解决方法:创建共享内存时,在shmflg中加上权限(如0664)。

image.png


修改后效果(映射成功):

image.png
(2)共享内存大小不是4KB整数倍

OS按“页”(4KB)分配共享内存,若size不是4KB的整数倍,OS会自动向上补齐(比如size=4097,实际分配8192字节)。
ipcs -m查看时,segsz会显示用户设置的size(而非补齐后的大小),容易误导。

image.png


建议:创建共享内存时,手动将size设为4KB的整数倍(比如size=4096size=8192),避免浪费。

(3)sleep导致的同步问题

若用sleep控制读写顺序(比如客户端sleep(1)后写,服务器sleep(2)后读),可能因时间差导致同步失败(比如sleep时间不够,服务器还没准备好,客户端就写了)。
Bug效果:

image.png


解决方法:不用sleep,改用FIFO等同步机制(如前所述),确保“写后再读”。

image.png
(4)共享内存删除后仍能访问

调用shmctl(shmid, IPC_RMID, NULL)后,共享内存被“标记为删除”,但只要有进程还在映射(引用计数>0),进程仍能访问它;只有所有进程都shmdt后,共享内存才会真正消失。
示例:服务器删除共享内存后,客户端仍能读数据(直到客户端shmdt)。

image.png


image.png


image.png

8. 共享内存的内核数据结构(补充)

OS用struct shmid_ds描述共享内存的详细信息,这个结构体包含以下核心字段:

image.png
  • shm_perm:权限相关信息(如所有者、组、权限)。
  • shm_segsz:共享内存大小(用户设置的size)。
  • shm_nattch:引用计数(当前映射的进程数)。
  • shm_atime:最后一次shmat的时间。
  • shm_dtime:最后一次shmdt的时间。
  • shm_ctime:最后一次修改(如权限、大小)的时间。

当调用shmget创建共享内存时,OS会初始化这个结构体,并将key存入shm_perm.__key字段,用于标识共享内存。

image.png


image.png

Read more

面试官问 C++ 多态?虚函数重写 + 虚表指针 + 动态绑定,核心考点全覆盖

面试官问 C++ 多态?虚函数重写 + 虚表指针 + 动态绑定,核心考点全覆盖

✨ 孤廖:个人主页 🎯 个人专栏:《C++:从代码到机器》 🎯 个人专栏:《Linux系统探幽:从入门到内核》 🎯 个人专栏:《算法磨剑:用C++思考的艺术》 折而不挠,中不为下 文章目录 * 正文: * 1. 多态的概念 * 2. 多态的定义及实现 * 2.1 虚函数 * 2.2 虚函数的重写/覆盖 * 2.3 虚函数重写的⼀些其他问题 * 2.4 override 和 final关键字 * 2.5 重载/重写/隐藏的对⽐ * 3. 纯虚函数和抽象类 * 4. 多态的原理 * 4.1 多态是如何实现的 * 4.2

By Ne0inhk

STL 容器:vector 动态数组的全面解析

STL 容器:vector 动态数组的全面解析 在C++ STL的序列式容器中,vector无疑是最常用、最灵活、最贴合日常开发需求的容器之一。无论是笔试面试中的算法题,还是实际项目中的数据存储,vector都能凭借其“动态扩容”“随机访问”的核心优势,成为开发者的首选。 很多开发者对vector的认知停留在“可以自动变长的数组”,但其实它的底层实现、扩容机制、接口用法以及性能优化,都有很多值得深究的细节。今天这篇博客,我们就来全面拆解vector——从基础用法到底层原理,从实操示例到避坑指南,让你真正吃透这个STL“高频容器”,做到灵活运用、规避隐患。 一、vector 是什么?核心定位与优势 vector 是 STL 标准模板库中的序列式容器,底层基于动态数组实现,本质是封装了动态分配内存的模板类。它可以存储任意数据类型(内置类型int、float,自定义结构体、类等),支持动态扩容(无需手动管理内存大小),同时提供了便捷的接口,实现数据的增、删、

By Ne0inhk
C++寻位映射的究极密码:哈希扩展

C++寻位映射的究极密码:哈希扩展

文章目录 * 1.位图 * 1.1 位图的结构 * 1.2 位图映射的比特位标记成1 * 1.3 位图映射的比特位标记成0 * 1.4 位图映射判断为1 or 0 * 2.布隆过滤器 * 2.1 布隆过滤器的结构 * 2.2 布隆过滤器的哈希函数 * 2.3 布隆过滤器的插入 * 2.4 布隆过滤器映射判断为true or false * 2.5 布隆过滤器的优缺点 * 3.常见面试题 * 3.1 哈希切割 * 3.1.1 问题一 * 3.1.2 问题二 * 3.

By Ne0inhk
Rust赋能Android蓝牙协议栈:从C++到安全高效的重构之路

Rust赋能Android蓝牙协议栈:从C++到安全高效的重构之路

在移动设备生态中,蓝牙协议栈是连接物理世界与数字世界的关键桥梁,从无线耳机、智能手环到车载系统,其稳定性、安全性与效率直接决定用户体验。长期以来,Android蓝牙协议栈核心模块基于C++开发,凭借接近硬件的性能优势支撑了数十亿设备的运行。但随着物联网设备爆发式增长、蓝牙5.3/5.4等新协议落地,C++固有的内存安全缺陷与并发管理难题愈发凸显。2021年起,Google开始在Android蓝牙协议栈中引入Rust重构核心模块,这一技术选型并非偶然,而是工程实践中安全与效率平衡的必然结果。 目录 一、Android蓝牙协议栈的C++之困 1.1 内存安全漏洞:蓝牙模块的阿喀琉斯之踵 1.2 并发管理复杂:多设备连接下的稳定性难题 1.3 代码可维护性下降:遗产代码的演进瓶颈 二、Rust:破解困局的关键特性赋能 2.1 所有权模型 2.2 并发安全:无数据竞争的天生优势 2.3 零成本抽象与可维护性:

By Ne0inhk