一、共享内存是什么?
想象两个程序(进程)需要交换数据,共享内存就像在白板上写字:
-
创建一块公共内存区域(白板)
-
两个进程都能直接读写这块内存
-
最快的进程通信方式(省去数据复制步骤)
二、为什么用共享内存?
通信方式 | 速度 | 复杂度 | 数据拷贝次数 |
---|---|---|---|
管道 | 慢 | 简单 | 2次 |
消息队列 | 中等 | 中等 | 2次 |
共享内存 | 快 | 较高 | 0次 |
三、核心API详解(附代码)
1. shmget
- 创建/获取共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
-
参数:
-
key
:唯一标识符,常用ftok("文件路径", 字符)
生成 -
size
:共享内存大小(字节) -
shmflg
:权限标志,如IPC_CREAT | 0666
-
-
返回值:共享内存ID(成功时),-1(失败)
// 示例:创建1KB共享内存
key_t key = ftok("/tmp", 'A');
int shmid = shmget(key, 1024, IPC_CREAT | 0666);
if(shmid == -1) {
perror("shmget失败");
exit(1);
}
2. shmat
- 连接共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg);
-
参数:
-
shmid
:shmget
返回的ID -
shmaddr
:连接地址(通常填NULL
让系统自动分配) -
shmflg
:0
(读写)或SHM_RDONLY
(只读)
-
-
返回值:共享内存首地址(成功时),
(void*)-1
(失败)
char *shm_ptr = (char*)shmat(shmid, NULL, 0);
if(shm_ptr == (char*)-1) {
perror("连接失败");
exit(1);
}
3. shmdt
- 断开连接
int shmdt(const void *shmaddr);
// 示例:断开连接
if(shmdt(shm_ptr) == -1) {
perror("断开失败");
}
4. shmctl
- 控制操作
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
-
常用操作:
-
删除:
shmctl(shmid, IPC_RMID, NULL)
-
查看状态:
shmctl(shmid, IPC_STAT, &buf)
-
// 示例:删除共享内存
if(shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("删除失败");
}
四、完整通信流程
五、实战代码示例
writer.c(写入方)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#define SHM_SIZE 1024
int main() {
// 生成唯一key
key_t key = ftok("/tmp", 'x');
// 创建共享内存
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
if(shmid == -1) {
perror("shmget失败");
exit(1);
}
// 连接共享内存
char *data = shmat(shmid, NULL, 0);
if(data == (char*)-1) {
perror("连接失败");
exit(1);
}
// 写入数据
printf("写入消息: ");
fgets(data, SHM_SIZE, stdin);
printf("已写入: %s\n", data);
// 等待读取方完成
printf("按回车删除共享内存...\n");
getchar();
// 清理
shmdt(data);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
reader.c(读取方)
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#define SHM_SIZE 1024
int main() {
// 生成相同key
key_t key = ftok("/tmp", 'x');
// 获取共享内存
int shmid = shmget(key, SHM_SIZE, 0666);
if(shmid == -1) {
perror("获取失败");
exit(1);
}
// 连接共享内存
char *data = shmat(shmid, NULL, SHM_RDONLY);
if(data == (char*)-1) {
perror("连接失败");
exit(1);
}
// 读取数据
printf("读取到消息: %s\n", data);
// 断开连接
shmdt(data);
return 0;
}
六、必须知道的注意事项
-
同步问题(最重要!)
-
共享内存没有内置锁机制
-
必须用额外同步工具:
// 推荐方案:信号量 sem_t *sem = sem_open("/mysem", O_CREAT, 0666, 1); sem_wait(sem); // 进入临界区 /* 操作共享内存 */ sem_post(sem); // 离开临界区
-
-
内存生命周期
-
进程退出后共享内存仍然存在
-
必须手动删除(
shmctl + IPC_RMID
) -
查看系统共享内存:
ipcs -m
-
手动删除:
ipcrm -m [shmid]
-
-
键值冲突预防
-
使用
ftok
生成key的要点:
key_t ftok(const char *path, int proj_id);
-
path
必须存在且可访问 -
proj_id
用非0字符(如'A')
内存对齐问题
-
不同进程访问结构体时需对齐:
struct SharedData { int id; char name[20]; } __attribute__((aligned(8))); // 8字节对齐
安全建议
-
设置最小必要权限(如0600)
-
避免存储敏感数据
-
使用
shmctl(SHM_LOCK)
锁定物理内存
-
七、常见错误排查
错误现象 | 可能原因 | 解决方案 |
---|---|---|
Permission denied | 权限不足 | 检查shmflg (如0666) |
Invalid argument | 大小不合法 | 确保size>0 |
File exists | 已存在相同key的内存 | 使用IPC_EXCL 检测 |
读取到乱码 | 未同步写入完成 | 添加同步机制 |
No space left on device | 系统共享内存上限 | 调整/proc/sys/kernel/shmmax |
八、最佳实践总结
-
创建流程
ftok
→shmget
→shmat
→ 使用 →shmdt
→shmctl(删除)
-
连接技巧
// 只读模式连接(保护数据) void *ptr = shmat(shmid, NULL, SHM_RDONLY); // 指定地址连接(特殊需求) void *ptr = shmat(shmid, (void*)0x500000, 0);
-
实时监控命令
# 查看共享内存 ipcs -m # 删除所有共享内存(危险!) ipcrm -a
-
大小限制检查
# 查看系统限制 cat /proc/sys/kernel/shmmax # 最大单块内存 cat /proc/sys/kernel/shmall # 总内存限制
📌 终极提示:共享内存像一把双刃剑 - 用好了速度飞起,用错了灾难重重。务必配合同步机制使用!