六十天Linux从0到项目搭建(第二十四天)(共享内存)
1 共享内存的管理机制详解
共享内存是最高效的进程间通信(IPC)方式之一,操作系统需要完善地管理系统中的所有共享内存资源。以下是共享内存的管理机制:
1. 共享内存的系统级管理
核心问题:系统需要同时管理多个共享内存段
解决方案:
-
**"先描述,再组织"**的经典OS设计模式
-
为每个共享内存段创建内核数据结构
-
所有共享内存结构体被组织起来统一管理
2. 共享内存的内核数据结构(伪代码)
struct shmid_ds { struct ipc_perm shm_perm; // 权限信息 size_t shm_segsz; // 共享内存大小 time_t shm_atime; // 最后attach时间 time_t shm_dtime; // 最后detach时间 time_t shm_ctime; // 最后change时间 pid_t shm_cpid; // 创建者PID pid_t shm_lpid; // 最后操作PID unsigned short shm_nattch; // 当前attach的进程数 // 指向实际物理内存的指针 void *shm_internal; };
3. 共享内存的完整构成
共享内存 = 内核数据结构(struct shmid_ds) + 实际内存空间
4. 共享内存的生命周期管理
创建/获取共享内存
int shmget(key_t key, size_t size, int shmflg);
-
key
:使用ftok()
生成的唯一键值 -
size
:共享内存大小 -
shmflg
:权限标志(如IPC_CREAT)
生成key的方法
key_t ftok(const char *pathname, int proj_id);
-
pathname
:存在的文件路径 -
proj_id
:项目ID(1个字节,0-255)
附加/分离共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg); int shmdt(const void *shmaddr);
5. 系统如何管理多个共享内存
-
全局共享内存表:内核维护一个所有共享内存段的表
-
唯一标识:每个共享内存段有唯一的
shmid
-
引用计数:跟踪有多少进程附加到该内存
-
生命周期:
-
显式删除:
shmctl(shmid, IPC_RMID, NULL)
-
当最后一个进程分离且显式标记删除时,内存才真正释放
-
6. 多进程使用共享内存的要点
-
不是只能一对进程使用:多个进程可以同时附加到同一共享内存
-
并发访问控制:需要额外同步机制(如信号量)保证数据一致性
-
系统限制:通过
ipcs -l
可查看系统允许的共享内存总量
示例:创建和使用共享内存
// 生成key key_t key = ftok("/tmp/mem.key", 'A'); // 创建共享内存(1KB) int shmid = shmget(key, 1024, IPC_CREAT | 0666); // 附加到进程地址空间 char *shm_ptr = (char *)shmat(shmid, NULL, 0); // 使用共享内存 strcpy(shm_ptr, "Hello Shared Memory!"); // 分离共享内存 shmdt(shm_ptr); // 删除共享内存(通常由一个进程负责) shmctl(shmid, IPC_RMID, NULL);
操作系统通过这种精细的管理机制,使得共享内存既高效又安全地被多个进程使用。
2 共享内存的生命周期与管理机制
1. Key与shmid的关系
Key:
-
内核中使用的标识符,由
ftok()
生成 -
用于创建/获取共享内存时的"匹配键"
-
类比:类似于文件的inode编号,是系统级的唯一标识
shmid:
-
共享内存段的实际句柄
-
由
shmget()
返回,用于后续操作(shmat/shmdt等) -
类比:类似于文件描述符(fd),是进程级的操作句柄
2. 共享内存的生命周期特性
关键特性:
-
不随进程终止而释放:即使创建进程退出,共享内存依然存在
-
随操作系统持续存在:直到显式删除或系统重启
-
引用计数机制:通过
shm_nattch
记录当前附加的进程数
现象解释:
-
创建进程退出后共享内存仍然存在,这是设计使然
-
允许多个无关进程在不同时间访问同一共享内存
3. 共享内存的管理方式
(1) 使用指令删除
ipcs -m # 查看所有共享内存 ipcrm -m <shmid> # 删除指定共享内存 ipcrm -M <key> # 通过key删除共享内存
(2) 系统调用删除
#include <sys/shm.h> shmctl(shmid, IPC_RMID, NULL); // 标记删除共享内存
删除的实际行为:
-
立即将共享内存标记为"待销毁"
-
当最后一个使用它的进程调用
shmdt()
分离后,内存才真正释放 -
新的
shmget()
调用将无法获取该共享内存
4. 生命周期管理的最佳实践
创建策略:
// 推荐方式:确保创建全新的共享内存 shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0666); if(shmid == -1 && errno == EEXIST) { // 已存在则获取现有ID shmid = shmget(key, size, 0666); }
删除策略:
-
设计一个"清理进程"负责最终删除
-
或者使用
atexit()
注册退出时的清理函数 -
重要数据应配合信号量等同步机制
5. 内核实现原理
数据结构关系
用户层 内核层 shmid ----> struct shmid_ds ---> 实际物理内存 ↑ 全局shm_table链表节点
生命周期控制:
-
创建:分配struct shmid_ds并加入全局链表
-
使用:通过shmid找到对应结构体
-
删除:从链表移除并标记,引用计数为0时释放内存
6. 与文件系统的对比
特性 | 共享内存 | 文件系统 |
---|---|---|
标识符 | key/shmid | inode/fd |
生命周期 | 显式删除或系统重启 | 文件被unlink且无打开 |
访问方式 | 直接内存访问 | read/write系统调用 |
同步需求 | 必须额外同步 | 部分由内核保证 |
共享内存的这种设计使其成为最高效但也最需要谨慎管理的IPC方式,开发人员必须明确管理其生命周期,避免造成内存泄漏。