Linux 进程间通信:共享内存详解
文章目录
- 1. 共享内存原理及使用场景
- 原理
- 使用场景
- 2. 共享内存相关API接口
- System V共享内存API
- 1. `shmget` - 创建/获取共享内存段
- 2. `shmat` - 附加共享内存段
- 3. `shmdt` - 分离共享内存段
- 4. `shmctl` - 控制共享内存段
- POSIX共享内存API
- 1. `shm_open` - 创建/打开共享内存对象
- 2. `shm_unlink` - 删除共享内存对象
- 3. `ftruncate` - 设置共享内存大小
- 4. `mmap` - 内存映射
- 5. `munmap` - 取消内存映射
- 3. 实例代码
- 示例1:System V共享内存
- 示例2:POSIX共享内存
- 4. 编译与运行
- 注意事项
1. 共享内存原理及使用场景
原理
共享内存是最高效的进程间通信(IPC)方式之一,它允许多个进程访问同一块物理内存区域。其核心原理是:
- 在内存中创建一块共享区域
- 将这块内存映射到多个进程的地址空间
- 进程可以直接读写这块内存,无需内核干预
由于数据不需要在进程和内核之间复制,共享内存的通信速度非常快。
使用场景
共享内存适合以下场景:
- 需要高性能的进程间通信
- 需要传输大量数据
- 进程间需要频繁交换数据
- 对实时性要求高的应用
典型应用包括:
- 数据库系统
- 科学计算应用
- 图形处理程序
- 实时数据处理系统
2. 共享内存相关API接口
System V共享内存API
1. shmget
- 创建/获取共享内存段
#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);
key
: 共享内存键值,通常使用ftok
生成或使用IPC_PRIVATE
size
: 共享内存段大小(字节)shmflg
: 权限标志,常用组合:IPC_CREAT | 0666
:创建共享内存,权限为rw-rw-rw-IPC_CREAT | IPC_EXCL | 0666
:独占创建,若已存在则失败
- 返回值:成功返回共享内存标识符,失败返回-1
2. shmat
- 附加共享内存段
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid
:shmget
返回的标识符shmaddr
: 指定附加地址,通常为NULL让系统选择shmflg
:SHM_RDONLY
: 只读方式附加0
: 读写方式附加
- 返回值:成功返回附加后的地址指针,失败返回(void*)-1
3. shmdt
- 分离共享内存段
int shmdt(const void *shmaddr);
shmaddr
:shmat
返回的地址指针- 返回值:成功返回0,失败返回-1
4. shmctl
- 控制共享内存段
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid
: 共享内存标识符cmd
: 控制命令IPC_STAT
: 获取状态信息IPC_SET
: 设置参数IPC_RMID
: 删除共享内存段
buf
: 指向shmid_ds
结构的指针- 返回值:成功返回0,失败返回-1
POSIX共享内存API
1. shm_open
- 创建/打开共享内存对象
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>int shm_open(const char *name, int oflag, mode_t mode);
name
: 共享内存对象名称,以/开头oflag
: 标志位,类似open
:O_CREAT
: 不存在则创建O_RDWR
: 读写方式打开O_EXCL
: 与O_CREAT一起使用,确保独占创建
mode
: 权限位- 返回值:成功返回文件描述符,失败返回-1
2. shm_unlink
- 删除共享内存对象
int shm_unlink(const char *name);
name
: 共享内存对象名称- 返回值:成功返回0,失败返回-1
3. ftruncate
- 设置共享内存大小
int ftruncate(int fd, off_t length);
fd
: 共享内存文件描述符length
: 要设置的大小- 返回值:成功返回0,失败返回-1
4. mmap
- 内存映射
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr
: 映射地址,通常为NULL让系统选择length
: 映射长度prot
: 保护模式PROT_READ
: 可读PROT_WRITE
: 可写
flags
:MAP_SHARED
: 共享映射MAP_PRIVATE
: 私有映射
fd
: 文件描述符offset
: 文件偏移量- 返回值:成功返回映射地址,失败返回MAP_FAILED
5. munmap
- 取消内存映射
int munmap(void *addr, size_t length);
addr
: 映射地址length
: 映射长度- 返回值:成功返回0,失败返回-1
3. 实例代码
示例1:System V共享内存
writer.c - 写入共享内存
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>#define SHM_SIZE 1024 // 共享内存大小int main() {key_t key = ftok("shmfile", 65); // 生成key// 创建共享内存段int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);if (shmid == -1) {perror("shmget failed");exit(EXIT_FAILURE);}// 附加到进程地址空间char *shm = (char*)shmat(shmid, NULL, 0);if (shm == (char*)-1) {perror("shmat failed");exit(EXIT_FAILURE);}printf("Write Data: ");fgets(shm, SHM_SIZE, stdin); // 写入数据printf("Data written in memory: %s\n", shm);// 等待读取进程读取while (*shm != '*') {sleep(1);}// 分离共享内存shmdt(shm);// 删除共享内存shmctl(shmid, IPC_RMID, NULL);return 0;
}
reader.c - 从共享内存读取
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>#define SHM_SIZE 1024int main() {key_t key = ftok("shmfile", 65); // 相同的key// 获取共享内存int shmid = shmget(key, SHM_SIZE, 0666);if (shmid == -1) {perror("shmget failed");exit(EXIT_FAILURE);}// 附加到进程地址空间char *shm = (char*)shmat(shmid, NULL, 0);if (shm == (char*)-1) {perror("shmat failed");exit(EXIT_FAILURE);}printf("Data read from memory: %s\n", shm);// 通知写入进程已完成读取*shm = '*';// 分离共享内存shmdt(shm);return 0;
}
示例2:POSIX共享内存
posix_writer.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>#define SHM_NAME "/my_shm"
#define SIZE 4096typedef struct {char data[SIZE];int ready; // 1表示数据已准备好,0表示未准备好
} SharedData;int main() {int shm_fd;SharedData *shared_data;// 创建共享内存对象shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);if (shm_fd == -1) {perror("shm_open failed");exit(EXIT_FAILURE);}// 配置共享内存大小if (ftruncate(shm_fd, sizeof(SharedData)) == -1) {perror("ftruncate failed");close(shm_fd);shm_unlink(SHM_NAME);exit(EXIT_FAILURE);}// 内存映射shared_data = mmap(NULL, sizeof(SharedData), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);if (shared_data == MAP_FAILED) {perror("mmap failed");close(shm_fd);shm_unlink(SHM_NAME);exit(EXIT_FAILURE);}// 初始化共享数据shared_data->ready = 0;printf("Writer: Enter data to write to shared memory: ");if (fgets(shared_data->data, SIZE, stdin) == NULL) {perror("fgets failed");munmap(shared_data, sizeof(SharedData));close(shm_fd);shm_unlink(SHM_NAME);exit(EXIT_FAILURE);}// 标记数据已准备好shared_data->ready = 1;printf("Writer: Data written to memory: %s", shared_data->data);// 等待读者读取完成printf("Writer: Waiting for reader to finish...\n");while (shared_data->ready == 1) {sleep(1);}printf("Writer: Reader has finished reading.\n");// 清理if (munmap(shared_data, sizeof(SharedData)) == -1) {perror("munmap failed");}if (close(shm_fd) == -1) {perror("close failed");}if (shm_unlink(SHM_NAME) == -1) {perror("shm_unlink failed");}return 0;
}
posix_reader.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>#define SHM_NAME "/my_shm"
#define SIZE 4096typedef struct {char data[SIZE];int ready; // 1表示数据已准备好,0表示未准备好
} SharedData;int main() {int shm_fd;SharedData *shared_data;// 打开共享内存对象shm_fd = shm_open(SHM_NAME, O_RDWR, 0666);if (shm_fd == -1) {perror("shm_open failed");exit(EXIT_FAILURE);}// 内存映射shared_data = mmap(NULL, sizeof(SharedData), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);if (shared_data == MAP_FAILED) {perror("mmap failed");close(shm_fd);exit(EXIT_FAILURE);}// 等待数据准备好printf("Reader: Waiting for data to be ready...\n");while (shared_data->ready == 0) {sleep(1);}// 读取数据printf("Reader: Data read from memory: %s", shared_data->data);// 通知写入者已完成读取shared_data->ready = 0;// 清理if (munmap(shared_data, sizeof(SharedData)) == -1) {perror("munmap failed");}if (close(shm_fd) == -1) {perror("close failed");}return 0;
}
4. 编译与运行
# 编译
gcc posix_writer.c -o posix_writer -lrt
gcc posix_reader.c -o posix_reader -lrt# 运行(需要先运行writer)
./posix_writer
# 在另一个终端
./posix_reader
注意事项
-
共享内存没有内置的同步机制,通常需要配合信号量或互斥锁使用
-
使用完毕后应及时释放资源,避免内存泄漏
-
在多进程访问时要注意数据一致性问题
-
System V共享内存是持久的,即使没有进程附加也会保留,需要显式删除
-
POSIX共享内存通常挂载在
/dev/shm
目录下