Linux16-进程间的通信--共享内存
12.4共享内存
共享内存是一种高效的进程间通信(IPC)机制,允许多个进程访问同一块物理内存区域,从而实现快速的数据共享。
核心特性
- 高性能:进程直接读写内存,无需系统调用开销
- 零拷贝:数据不需要在内核和用户空间之间复制
需要同步机制:多个进程同时访问时需要信号量等同步工具
- 内核持久性:共享内存段独立于进程存在,除非显式删除

12.4.1共享内存相应函数
我们使用shmget函数来创建共享内存:
int shmget(key_t key,size_t,int shmflg);
- key :共享内存键值,可以使用 ftok() 生成或 IPC_PRIVATE
- size :共享内存段大小(字节)
- shmflg :权限标志,如 IP C_CREAT | 0666
我们使用shmat函数映射到物理内存段:
void *shmat(int shmid,const void* shmaddr,int shmflg);
- shmid 共享内存标识符
- shmaddr:指定映射地址通常设为NULL
- shmflg:访问模式如SHM)RDONLY只读 可为0
- 返回值为指向共享内存的指针
我们使用shmdt函数来分离共享内存:
int shmdt(const void *shmaddr);
- 返回值-1为分离失败
我们使用shmctl来控制共享内存:
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
- shmid 共享内存标识符
cmd:
- IPC_RMID :删除共享内存段
- IPC_STAT :获取状态信息
- IPC_SET :设置参数
struct shmid_ds 是Linux内核中用于描述共享内存段状态的数据结构,定义在 <sys/shm.h> 头文件中。
struct shmid_ds {struct ipc_perm shm_perm; /* 操作权限结构 */int shm_segsz; /* 段大小(字节) */__kernel_time_t shm_atime; /* 最后附加时间 */__kernel_time_t shm_dtime; /* 最后分离时间 */__kernel_time_t shm_ctime; /* 最后修改时间 */__kernel_ipc_pid_t shm_cpid; /* 创建者进程ID */__kernel_ipc_pid_t shm_lpid; /* 最后操作进程ID */unsigned short shm_nattch; /* 当前附加计数 */unsigned short shm_unused; /* 兼容性填充 */void *shm_unused2; /* 保留字段 */void *shm_unused3; /* 保留字段 */};
1. 权限控制结构 - shm_perm
struct ipc_perm {key_t __key; /* 共享内存键值 */uid_t uid; /* 所有者有效用户ID */gid_t gid; /* 所有者有效组ID */uid_t cuid; /* 创建者有效用户ID */gid_t cgid; /* 创建者有效组ID */unsigned short mode; /* 权限模式 */unsigned short __seq; /* 序列号 */};
作用:控制对共享内存段的访问权限,类似于文件权限控制。
2. 大小信息 - shm_segsz
- 类型: int
- 含义:共享内存段的大小,以字节为单位
- 示例:如果创建时指定大小为1024字节,则 shm_segsz 值为1024
3. 时间戳信息
| 成员 | 类型 | 含义 |
|---|---|---|
| shm_atime | __kernel_time_t | 最后一个进程附加(attach)到该共享内存的时间 |
| shm_dtime | __kernel_time_t | 最后一个进程分离(detach)的时间 |
| shm_ctime | __kernel_time_t | 共享内存段最后修改(创建或权限变更)的时间 |
4. 进程标识信息
| 成员 | 类型 | 含义 |
|---|---|---|
| shm_cpid | __kernel_ipc_pid_t | 创建该共享内存段的进程ID |
| shm_lpid | __kernel_ipc_pid_t | 最后对该共享内存执行操作的进程ID |
| shm_nattch | unsigned short | 当前附加到该共享内存段的进程数量 |
12.4.2共享内存示例
生产者a.c代码:
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#include "sem.h"int main(){int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);if (shmid == -1){exit(1);}char *s = shmat(shmid, NULL, 0);if (s == (char *)-1){exit(1);}while (1){printf("input: ");fgets(s, 127, stdin);if (strncmp(s, "end", 3) == 0){break;}}shmdt(s);}
消费者b.c代码:
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#include "sem.h"int main(){int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);if (shmid == -1){exit(1);}char *s = shmat(shmid, NULL, 0);if (s == (char *)-1){exit(1);}while (1){if (strncmp(s, "end", 3) == 0)break;printf("read: %s", s);sleep(1);}shmdt(s);shmctl(shmid, IPC_RMID, NULL);}
运行结果:

由运行结果可以发现当生产者不去改变映射段内容时,消费者仍在打印上次输入的内容,那么如何实现a输入一次,b打印一次呢,我们就要引入上此学习到的信号量。
a.c代码如下:
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#include "sem.h"int main(){sem_init();int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);if (shmid == -1){exit(1);}char *s = shmat(shmid, NULL, 0);if (s == (char *)-1){exit(1);}while (1){sem_p(0);printf("input: ");fgets(s, 127, stdin);if (strncmp(s, "end", 3) == 0){sem_v(1);break;}sem_v(1);}shmdt(s);}
b.c代码如下:
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#include "sem.h"int main(){sem_init();int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);if (shmid == -1){exit(1);}char *s = shmat(shmid, NULL, 0);if (s == (char *)-1){exit(1);}while (1){sem_p(1);if (strncmp(s, "end", 3) == 0)break;printf("read: %s", s);sleep(1);sem_v(0);}shmdt(s);sem_destroy();shmctl(shmid, IPC_RMID, NULL);}
运行结果:

可知实现a输入一次,b打印一次。
12.4.3总结
1. 同步机制必须
共享内存不提供进程间同步,需要配合其他机制:
// 通常需要信号量来同步访问#include <semaphore.h>sem_t *sem = sem_open("/mysem", O_CREAT, 0644, 1);// 写入前加锁sem_wait(sem);strcpy(shm_ptr, data);sem_post(sem);
2. 内存对齐和字节序
不同架构的机器可能有不同的字节序,需要处理兼容性。
3. 资源管理
- 及时调用 shmdt() 分离内存
- 使用 shmctl(IPC_RMID) 删除不再使用的共享内存
- 避免内存泄漏
4. 系统限制
检查系统共享内存限制:
bash# 查看系统IPC限制ipcs -l# 查看当前共享内存段ipcs -m
共享内存架构类型
UMA(统一内存访问)
所有处理器共享一个统一的物理内存,每个处理器都可以均匀地访问内存中的任何数据。
NUMA(非统一内存访问)
内存被划分为多个节点,每个节点有一个本地内存。处理器可以快速访问本地内存中的数据,但访问远程内存时会有延迟。
共享内存是Linux进程间通信的最高效机制,核心要点包括:
- 高性能优势:直接内存访问,零拷贝设计
- 使用步骤:创建( shmget ) → 连接( shmat ) → 使用 → 分离( shmdt ) → 控制( shmctl )
- 同步必要性:必须配合信号量等同步机制防止数据竞争
- 资源管理:及时分离和删除,避免资源泄漏
- 架构选择:根据性能需求选择UMA或NUMA架构
掌握共享内存的使用对于开发高性能的多进程应用程序至关重要,特别是在需要处理大数据量或对性能要求极高的场景中。
