

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

【二】整体流程与特点
流程:
总的流程分为以下几步:(已存在的共享内存通过"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(可使用),更像资源的使用状态!
**效率:**信号量(可调用共享资源的执行流数量)+"原子性"------便形成了"互斥",共享访问达成高效
(注:"信号量"的学习与后面的"信号"无关)
