《场景化落地:用 Linux 共享内存解决进程间高效数据传输问题(终篇)》

**前引:**共享内存是 Linux 进程间通信中效率最高的方式,但 "内存映射原理""权限配置""同步机制" 等知识点常让新手望而却步。本文从基础概念拆解入手,先讲清共享内存的工作逻辑,再通过 "创建→挂载→读写→销毁" 完整实操案例,帮你从零掌握核心用法,无论你是 Linux 入门者还是需要夯实基础的开发者,都能快速实现从 "懂概念" 到 "能实操" 的跨越!

目录

【一】共享内存介绍

【二】整体流程与特点

流程:

特点:

【三】使用步骤

(1)申请共享内存

(1)ftok()

(2)shmget()

(2)挂接

(3)去关联

(4)释放共享内存

(5)例如

【四】共享内存效率与内核

【五】信号量与高效


【一】共享内存介绍

共享内存:也属于一种进程通信方式,让多个进程通过访问同一块内存实现通信的方式

【二】整体流程与特点

流程:

总的流程分为以下几步:(已存在的共享内存通过"Key"直接用即可,不用重新申请)

需要先申请共享内存,然后与它建立联系,不想用了就去除关联,最后看情况释放共享内存即可

特点:

(1)共享内存申请之后,如果不主动释放,那它就会等到系统关闭才主动释放------切记

(2)共享内存的通信不会出现阻塞的情况(这是较于两种管道通信最大的区别)

即:可以同时实现数据写入,A进程不用阻塞等待B进程

(3)通信成功的关键是:读写双方必须约定相同的数据类型和解析规则

例如:A以字符串写入,B就以字符串读取,如果B是整型读取,就会出现乱码

(4)共享内存的释放:由创建者销毁,且创建者标记"销毁"之后,也会等失去所有挂载连接之后 才会释放这块共享内存。下面是指令版的查看共享内存和销毁:

ipcs -m 查看所有存在的共享内存

ipcrm -m "共享ID" 手动直接销毁该共享内存

【三】使用步骤

(1)申请共享内存

共享内存的申请涉及到两个函数:ftok()shmget()

(1)ftok()

函数原型:

cpp 复制代码
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

参数:都是自定义的,系统会根据它们调用算法形成唯一的Key,这个Key是较于操作系统的

(只要这两个参数相同,就会生成相同的Key->进而找到同一块共享内存)

返回值:

  • 成功:返回一个 key_t 类型的整数(就是生成的 "唯一编号")
  • 失败:返回 -1(比如路径不存在,或权限不够)

作用:生成 "唯一标识" 较于系统的钥匙Key

(2)shmget()

函数原型:

cpp 复制代码
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

参数:

第一个参数:通过调用ftok()获取的较于操作系统的Key

第二个参数:你理想的共享内存大小(每次最好是4KB,因为"块"以"4KB"为整数标准)

第三个参数:操作标志(权限 + 行为),常用组合:(使用新内存或者旧内存!)

  • IPC_CREAT | 0666:如果 key 对应的共享内存不存在,就新建一个,权限设为 666(所有人可读写,类似文件权限)
  • IPC_EXCL | IPC_CREAT:如果共享内存已存在,就报错(确保一定是新建的,避免误操作已有内存)
  • 单独传 0:只查找已存在的共享内存,不新建

作用:生成 "唯一标识" 较于用户的钥匙ID(其实也算"Key",只是对于用户层面的)

返回值:

  • 成功:返回一个 "共享内存 ID"(非负整数,后续操作共享内存都用这个 ID)
  • 失败:返回 -1(比如权限不够、内存不足、key 不存在且没加 IPC_CREAT)
(2)挂接

挂接:与该共享内存建立联系,比如你想怎么使用,需要用到接口(shmat()

函数原型:(需要强转)

cpp 复制代码
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);

参数:

第一个参数: "共享内存 ID"

第二个参数:告诉操作系统挂载到共享内存的哪个地方,一般推荐 NULL 参数

第三个参数:挂载权限,一般选择 0 (可读可写)

返回值:

  • 成功:返回共享内存在当前进程地址空间中的实际挂载地址(void*类型,可直接作为指针使用)
  • 失败:-1(可以通过检查errno获取错误原因,如地址冲突、权限不足等)

作用:搭建"窗口",真正使用共享内存

(3)去关联

即失去与该共享内存的关联,代表不再使用它

函数原型:

cpp 复制代码
#include <sys/shm.h>
int shmdt(const void *shmaddr);

参数:通过 shmat()挂接该共享内存的指针

返回值:

  • 成功:返回0(表示已成功解除关联)
  • 失败:返回-1(可通过errno查看错误原因,例如shmaddr不是有效的挂载地址、权限不足等)

作用:失去与该共享内存的关联

(4)释放共享内存

函数原型:

cpp 复制代码
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数:

第一个参数: "共享内存 ID"

第二个参数:对共享内存执行的操作(我们暂时学习两个即可)

命令 作用
IPC_STAT 读取共享内存的状态信息,存储到buf指向的struct shmid_ds结构体中
IPC_RMID 标记共享内存为 "待销毁",当所有进程都卸载后,内核会彻底删除它

第三个参数:

buf是一个指向struct shmid_ds类型的指针,该结构体用于存储或设置共享内存的详细信息

  • cmd=IPC_STAT时:buf用于接收状态(内核会将共享内存的信息写入buf
  • cmd=IPC_RMID时:buf无用,通常传NULL即可

返回值:

  • 成功:返回0(无论执行哪种cmd,成功均返回 0)
  • 失败:返回-1(可通过errno查看错误原因,如权限不足、shmid无效等)

作用:对共享内存执行的操作

(5)例如

我们将Key的获取封装一下:

cpp 复制代码
const char* ptr_ftok="Hello Linux";

const int pid_ftok =1024;

创建共享内存:这里判断省略的是上篇学习的"简易日志"

使用共享内存:这里判断省略的是上篇学习的"简易日志"

【四】共享内存效率与内核

为何是这样?共享内存与文件描述表都是在进程地址空间上的,可以直接跳过文件表访问共享内存

【五】信号量与高效

什么是"互斥"?

当共享内存被两个进程访问时,如果A需要持续写入3秒的数据,但是B进程在二秒的时候就进行了读取,这就导致通信结果不理想,因此为了避免这种情况,任何时刻,只能一个进程(属于执行流)访问该共享资源

什么是"临界资源"?

被多个执行流(进程、线程)都能访问的共享资源,而这些资源,通常是代码、数据

什么是"临界区"?

简单来说:临界区就是执行流正在访问共享资源中的代码数据

因此就算有100行代码在"临界资源",但是被访问的那5行才叫临界区

什么是"信号量"?

信号量是一个计数器,来记录该"临界资源"还可以被多少执行流访问 通常执行两个操作:

"P"操作:有执行流访问临界资源,计数-1

"V"操作:该执行流退出访问临界资源,计数器+1

什么是"原子性"?

"原子性"是较于"P"和"V"操作的执行特性:要么不执行,要么执行完

什么是"二元信号量"?

如果信号量的计数器只能是 0(资源无法使用) 或 1(可使用),更像资源的使用状态

**效率:**信号量(可调用共享资源的执行流数量)+"原子性"------便形成了"互斥",共享访问达成高效

(注:"信号量"的学习与后面的"信号"无关)

相关推荐
LCG元2 小时前
实战:打造你的专属 Linux 工作环境(Oh My Zsh + 插件推荐)
linux
java_logo2 小时前
PERL Docker 容器化部署指南
linux·运维·docker·容器·eureka·centos·perl
敲上瘾2 小时前
C++ ODB ORM 完全指南:从入门到实战应用
linux·数据库·c++·oracle·db
一直向钱2 小时前
Ubuntu 服务器的无法使用WinSCP低版本连接登录
linux·服务器·ubuntu
歪歪1002 小时前
解决多 Linux 客户端向 Windows 服务端的文件上传、持久化与生命周期管理问题
linux·运维·服务器·开发语言·前端·数据库·windows
麦嘟学编程2 小时前
快速上手配置Zookeeper
linux·分布式·zookeeper
一匹电信狗2 小时前
【C++11】右值引用+移动语义+完美转发
服务器·c++·算法·leetcode·小程序·stl·visual studio
乌萨奇也要立志学C++3 小时前
【Linux】进程间通信(二)命名管道(FIFO)实战指南:从指令操作到面向对象封装的进程间通信实现
linux·服务器
自己的九又四分之三站台3 小时前
进程 & 端口排查速查手册
运维