@bit::Shadow
✧(≖ ◡ ≖✿
目录
[System V共享内存1.0](#System V共享内存1.0)
[第二个参数size : 创建共享内存的大小单位是字节。](#第二个参数size : 创建共享内存的大小单位是字节。)
[@使用shmget和ftok()创建共享内存并使用ipcs -m查询](#@使用shmget和ftok()创建共享内存并使用ipcs -m查询)
[System V共享内存2.0](#System V共享内存2.0)
[三种删除方式对比shmdt() shmctl() ipcrm -m shmid](#三种删除方式对比shmdt() shmctl() ipcrm -m [shmid])
System V共享内存1.0
优点:作为最快的进程间通信方式。
缺点:阻塞读取难以控制。
内存视图:

共享内存被两个进程维护,不仅因此,共享内存其实可以被许多个进程维护访问。
相关接口
shmget创建:
cpp
//系统调用
int shmget(ley_t key, size_t size, int shmflg);
shmget意为shared memory get。
☆☆☆返回值(int shmid):成功返回作为用户级操作共享内存的标识符。
第二个参数size : 创建共享内存的大小单位是字节。
size(关系内存大小)与4096字节的关系:
4096是操作系统管理内存页(Page)的最小单位,共享内存的大小是内存页大小的整数倍。因此常将size设置为接近4096整数倍大小。
第三个参数shmflg:宏参数设置创建共享内存的模式
①IPC_CREAT: 如果目标内存(key标识)存在,就打开此共享内存,如果目标内存(key标识)不存在,就创建共享内存。
②IPC_EXCL(++单独使用无意义++ ):exclusive"独占的"。
③IPC_CRAET | IPC_EXCL:如果共享内存存在就报错 设置错误码返回,如果不存在就创建。
涉及共享内存创建必须指定权限因此:(IPC_CRAET | 0666)和(IPC_CRAET | IPC_EXCL | 0666)。
总结:对于第三个参数对当下进程目的经常要求哪个进程必须执行的是创建工作,哪个是获取因此有:是获取(IPC_CRAET),还是创建(IPC_CRAET | IPC_EXCL)。
第一个参数key:ftok()标识共享内存的操作系统标识
key:用户层约定值,被用户用于创建共享内存链接多个进程的标志位,是操作系统接受的关系内存的特征标识符。必须配合ftok()算法函数来创建。
ftok()原型:
cpp
// 路径 任意值
key_t ftok(const char* pathname, int proj_id);
//typedef int key_t
注意:可能创建失败,必须检验返回值!
共享内存查询指令
bash
ipcs -m #查询共享内存的情况

nattch指映射到物理内存后(shmget无法实现)该共享内存被几个进程(的虚拟内存)挂接。
@使用shmget和ftok()创建共享内存并使用ipcs -m查询
bash
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
int main() {
// 使用shmget和ftok()创建共享内存并使用ipcs -m查询
key_t key = ftok(".", 324);
if (key == -1) ERR_EXIT("ftok");
//创建
int shmid = shmget(key, 4096 * 2, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1) ERR_EXIT("shmget");
while(1)
{}
return 0;
}
运行后查询

*为什么必须要用户级创建?
用户层创建是实现通信前提的必要条件。系统内若直接可以创建必然已经实现了进程间通信,这是鸡生蛋蛋生鸡的问题。
System V共享内存2.0
生命周期
- 共享内存是内核级进程间通信的通道,程序结束进程退出不会自动回收资源。不像"管道"是进程级通信(手动退出会回收与进程退出也会自动回收管道)。
- 所以共享内存在代码内无删除的情况下进程退出依旧存在,需要手动删除。(ipcrm -m shamid)。
内存管理接口shmctl()
cpp
// 宏指定
int shmctl(int shmid, int cmd, struct shmid_ds* buf);
// shmget返回值
返回-1:管理失败
cmd:指定宏。例如:删除---> IPC_RMID
buf:忽略。传nullptr即可
cpp
void test2()
{
//shmctl的IPC_RMID删除共享内存
int n = shmctl(shmid, IPC_RMID, nullptr);
if (n == -1) ERR_EXIT("shmctl");
}
shmat()建立映射
使得申请的物理内存与共享内存建立映射
cpp
void* shmat(int shmid, const char* shmaddr, int shmflg);
成功:返回挂接后的虚拟地址,用于shmdt()的参数。
失败:返回-1。
shmaddr:直接设置固定的虚拟地址作为挂接的目标。nullptr即可。此参数不是我学习领域需要考虑的。
shmflg:设置0。共享内存权限(0默认权限)。
挂接后ipcr -m shmid查询的nattch加1。
cpp
void test3() {
void* n = shmat(shmid, NULL, 0);
if (n == (void*)-1) ERR_EXIT("shmat");
/*验证错误越界访问!
void* n = shmat(shmid, nullptr, 0);
if (*((int*)n) == -1) ERR_EXIT("shmat");
*/
}
挂接演示mp4:
创建:无->0---挂接0->1---shmctl删除------ 无 !
挂接与shmctl删除
shmdt()柔性删除
cpp
int shmdt(void* shmaddr);
// shmat的返回对象
特点:
- 只影响当前进程。
- 当引用计数从1变为0且当前进程退出后,真正销毁。
三种删除方式对比shmdt() shmctl() ipcrm -m shmid
shmdt是柔性删除优先影响使得nattch减1。
shmctl与ipcrm -m shmid是较强力删除:
标记共享内存为"待删除"(dest),影响所有访问进程------新进程挂接会失败,已经挂接的进程仍然可以访问。
☆☆☆访问共享内存
访问共享内存的方式,就是利用shmat()返回的++虚拟地址读取与写入++。
三个System V的内核组织形式
三者System V共享内存,Systemd V消息队列,System V信号量。
前言:struct结构体内的柔性数组int arr0
性质:GCC支持,C++中不符合标准但编译器通常支持。
图解
ipc_id_ary内柔性指针数组维护多个System V系列

共享内存的弊端:
1.缺乏同步机制(最核心的痛点)
共享内存完全没有内置的同步或互斥(互斥:在任何时刻只允许一个执行流访问)机制。
-
问题所在: 内核只负责提供这块内存,但它不知道、也不管谁在读、谁在写。如果进程 A 正在向共享内存写入一个 10MB 的结构体,写到一半时,进程 B 突然跑来读取,那么进程 B 读到的就是一堆损坏的脏数据(数据竞争/Data Race)。
-
代价: 开发者必须自己引入信号量(Semaphore) 、互斥锁(Mutex)或文件锁 来人为协调读写顺序。这++大大增加了代码的编写难度和出错概率++。
2.极易引发死锁与程序崩溃
由于需要引入额外的同步锁,死锁的风险随之而来。
-
死锁风险: 如果进程 A 获取了控制共享内存的信号量,但在释放信号量之前因为异常(比如 Core Dump、段错误、被
kill -9)突然退出了,那么这个锁将被永久死锁,其他等待访问该内存的进程全部会被卡死。 -
崩溃扩散: 共享内存是直接映射到进程的虚拟地址空间的。如果进程 A 往共享内存里写了一个错误的野指针,进程 B 读取并顺着这个指针去访问,会导致进程 B 也跟着发生段错误(Segment Fault)崩溃。
共享内存的重要性:
共享内存绝对是多进程开发、高并发系统以及底层性能优化中的重中之重,甚至可以说是最考验开发者内功的"雷区"。
在实际开发中,共享内存往往承载着系统最核心的高速数据通道 (比如音视频流媒体传输、高频交易系统的订单队列、数据库缓冲池等)。对它的操作之所以是重点,是因为它伴随着极高的高风险 与高复杂度。
感谢支持,持续更新
欢迎关注
