共享内存
2026/8/25大约 4 分钟
Linux 进程间通信:共享内存
1. 共享内存是什么
共享内存的核心就一句话:
多个进程把同一块物理内存映射到自己的虚拟地址空间,然后直接通过指针读写。
进程A 进程B
│ │
shmat() shmat()
↓ ↓
┌──────────────────────────────┐
│ 共享内存 │
│ "Hello Linux" │
└──────────────────────────────┘
↑ ↑
│ │
写 读相比 Pipe/FIFO 中的数据传递,共享内存减少了数据拷贝,适合大数据量通信。
2. 使用流程
共享内存只记住四个 API:
shmget()
↓
创建/获取共享内存
shmat()
↓
映射到进程地址空间,得到指针
读 / 写共享内存
↓
shmdt()
↓
解除当前进程的映射
shmctl()
↓
删除共享内存3. shmget():创建/获取
int shmid;
shmid = shmget(key, 2048, 0666 | IPC_CREAT);参数:
key
↓
找到哪块共享内存
2048
↓
共享内存大小
0666 | IPC_CREAT
↓
权限 + 不存在就创建返回:
shmid
↓
共享内存标识符注意:
shmid只是编号,不是内存地址。
4. shmat():映射
char *p;
p = shmat(shmid, NULL, 0);过程:
shmid
↓
找到共享内存
↓
映射到当前进程地址空间
↓
返回地址 p之后:
strcpy(p, "hello");就是直接往共享内存写数据。
读取:
printf("%s\n", p);就是直接从共享内存读数据。
因此:
shmid是共享内存的编号,p才是当前进程访问它的地址。
5. 两个进程为什么能访问同一块内存?
两个进程只要拿到同一个 Key:
key_t key = ftok("./file1", 'a');
int shmid = shmget(key, 2048, 0666 | IPC_CREAT);就可以获得同一个 shmid,然后:
p = shmat(shmid, NULL, 0);于是:
进程A 进程B
│ │
shmget(key) shmget(key)
↓ ↓
同一个 shmid 同一个 shmid
↓ ↓
shmat() shmat()
↓ ↓
└──────── 同一块共享内存 ────────┘6. shmdt() 和 shmctl()
解除映射
shmdt(p);表示:
当前进程不再访问这块共享内存。
不是删除。
删除共享内存
shmctl(shmid, IPC_RMID, NULL);表示:
删除这块 System V 共享内存。
所以:
shmdt() → 当前进程解除映射
shmctl() → 删除共享内存7. 共享结构体是最常见的用法
共享内存不只能存字符串,实际项目更常直接存结构体:
typedef struct {
float livingroom_temp;
float bedroom_temp;
int update_flag;
} temp_data;映射:
temp_data *shm;
shm = shmat(shmid, NULL, 0);写入:
shm->livingroom_temp = 25.3;
shm->bedroom_temp = 23.8;
shm->update_flag = 1;另一个进程直接读取:
printf("%.1f\n", shm->livingroom_temp);本质就是:
进程A写结构体
↓
┌──────────────────┐
│ temp_data │
│ 温度 │
│ 温度 │
│ update_flag │
└──────────────────┘
↓
进程B直接读8. 共享内存最大的坑:不同步
共享内存只解决:
数据共享。
不解决:
什么时候读、什么时候写。
例如:
进程A正在写:
25.3 → 26.1
进程B同时读取:
可能读到中间状态因此实际使用通常是:
共享内存
+
信号量 / 互斥锁例如:
A:
加锁
↓
写共享内存
↓
解锁
B:
等待锁
↓
读取共享内存所以:
共享内存负责“共享”,同步机制负责“协调”。
9. IPC_PRIVATE 的特殊用法
shmid = shmget(IPC_PRIVATE, 2048, 0666);用于创建一块新的共享内存。
如果随后:
fork();父子进程都知道这个 shmid,就可以:
父进程 ──shmat()──┐
↓
共享内存
↑
子进程 ──shmat()──┘适合父子进程之间快速共享数据。
10. 与 FIFO、消息队列的区别
FIFO:
write() → 字节流 → read()
消息队列:
msgsnd() → 消息 → msgrcv()
共享内存:
直接写共享内存 → 另一个进程直接读核心区别:
| IPC | 核心 |
|---|---|
| FIFO | 字节流 |
| 消息队列 | 消息 |
| 共享内存 | 直接共享数据 |
共享内存最大的优势:
数据量大时效率高。
最大的缺点:
必须自己解决同步问题。
11. 一句话记忆
shmget() → 创建/获取
shmat() → 映射并得到指针
↓
直接读写
↓
shmdt() → 解除映射
shmctl() → 删除共享内存 = 多个进程通过指针直接访问同一块内存;高效,但必须配合同步机制保证数据一致性。
完整例子
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <time.h>
key_t key;
//共享内存数据结构(存储温度数据)
typedef struct{
float livingroom_temp; //客厅温度
float bedroom_temp; //卧室温度
int update_flag; //数据更新标志(0=未更新,1=已更新)
}temp_data;
//传感器进程(写入数据)
void sensor_process(const char *room_name) {
int shmid = shmget(key, sizeof(temp_data), 0666);
temp_data *shm = (temp_data*)shmat(shmid, NULL, 0);
srand(time(NULL) + getpid()); //随机种子
while(1) {
float temp = 20.0+(rand()%100)/10.0;//生成20.0~30.0的随机温度
if(strcmp(room_name, "livingroom") == 0){
shm->livingroom_temp = temp;
}else{
shm->bedroom_temp = temp;
}
shm->update_flag = 1; //标记数据已更新
sleep(2); //每2秒更新一次
}
}
//显示进程(读取数据)
void display_process() {
int shmid = shmget(key, sizeof(temp_data), IPC_CREAT|0666);
temp_data *shm = (temp_data*)shmat(shmid, NULL, 0);
while(1) {
if(shm->update_flag==1){ //若update_flag为1, 表示有数据更新
printf("\n=== 家庭温度监控 ===\n");
printf("客厅: %.1f°C\n", shm->livingroom_temp);
printf("卧室: %.1f°C\n", shm->bedroom_temp);
shm->update_flag = 0; //重置标志位
}
sleep(1); //每秒检查一次
}
}
int main(){
key = ftok("./file1",'a');
int pid1, pid2;
int shmid=shmget(key, sizeof(temp_data), IPC_CREAT|0666);//创建共享内存
pid1 = fork();
if(pid1>0){ //显示进程
display_process();
exit(0);
}
else if(pid1==0){
pid2 = fork();
if(pid2>0){ //传感器进程1
sensor_process("livingroom");
exit(0);
}
else if(pid2==0){//传感器进程2
sensor_process("bedroom");
exit(0);
}
}
wait(NULL); //等待子进程
shmctl(shmid, IPC_RMID, NULL);//清理共享内存
return 0;
}
