POSIX 共享内存
高性能计算:需要快速数据交换的场景实时系统:低延迟要求的应用大数据处理:避免数据复制开销进程池通信:工作进程间共享状态关键注意事项:必须实现同步:使用信号量、互斥锁等机制注意内存对齐:避免性能下降妥善清理资源:防止内存泄漏考虑可移植性:不同系统的行为可能略有差异安全考虑:共享内存对所有有权限的进程可见通过合理使用 POSIX 共享内存,可以构建出高效、可扩展的进程间通信架构。
·
POSIX 共享内存详解
POSIX 共享内存是一种基于内存映射文件的进程间通信机制,它是最快的 IPC 方式,因为进程可以直接访问同一块物理内存,无需数据复制。
一、核心概念
1.1 工作原理
进程A地址空间 物理内存 进程B地址空间
↓ 共享内存区 ↓
[映射区域] ←--------→ [共享数据区] ←--------→ [映射区域]
1.2 关键特性
- 零拷贝:数据直接在内存中共享,无需内核缓冲区复制
- 文件系统接口:通过
/dev/shm(Linux)路径访问 - 内存映射:使用
mmap()将共享内存对象映射到进程地址空间 - 同步要求:需要配合信号量等同步机制使用
二、核心 API 详解
2.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:共享内存对象名称,必须以/开头,如"/myshm"oflag:标志位,常用组合:O_RDONLY:只读打开O_RDWR:读写打开O_CREAT:不存在则创建O_EXCL:与O_CREAT一起使用,确保创建新对象O_TRUNC:如果对象已存在,将其截断为0
mode:权限位,如0666
返回值: 成功返回文件描述符,失败返回 -1
示例:
// 创建新的共享内存对象
int fd = shm_open("/myshm", O_CREAT | O_RDWR, 0666);
// 打开已存在的共享内存对象(只读)
int fd = shm_open("/myshm", O_RDONLY, 0);
// 创建并确保是新对象
int fd = shm_open("/myshm", O_CREAT | O_EXCL | O_RDWR, 0666);
2.2 ftruncate() - 设置共享内存大小
#include <unistd.h>
#include <sys/types.h>
int ftruncate(int fd, off_t length);
说明:
- 新创建的共享内存对象大小为 0
- 必须使用
ftruncate()设置所需大小 length通常设置为数据结构或缓冲区的大小
2.3 mmap() - 映射共享内存到进程空间
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
参数详解:
| 参数 | 说明 |
|---|---|
addr |
建议的映射地址,通常设为 NULL 让系统选择 |
length |
映射长度,通常与共享内存大小相同 |
prot |
保护模式:PROT_READ - 可读PROT_WRITE - 可写PROT_EXEC - 可执行PROT_NONE - 不可访问 |
flags |
映射特性:MAP_SHARED - 对共享内存必须使用MAP_PRIVATE - 创建私有写时复制映射MAP_ANONYMOUS - 匿名映射(不用于 shm)MAP_FIXED - 使用指定地址 |
fd |
共享内存对象的文件描述符 |
offset |
偏移量,通常为 0 |
返回值: 成功返回映射起始地址,失败返回 MAP_FAILED(即 (void *)-1)
2.4 munmap() - 解除内存映射
#include <sys/mman.h>
int munmap(void *addr, size_t length);
说明:
- 解除映射不会删除共享内存对象
- 进程退出时会自动解除所有映射
2.5 shm_unlink() - 删除共享内存对象
#include <sys/mman.h>
int shm_unlink(const char *name);
说明:
- 删除对象名称,使其不再可访问
- 实际删除发生在所有进程都关闭该对象后
- 类似文件系统的
unlink()
三、完整使用示例
3.1 基础示例:共享计数器
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>
#define SHM_NAME "/counter_shm"
#define SHM_SIZE sizeof(int)
int main() {
int shm_fd;
int *counter;
// 1. 创建共享内存对象
shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (shm_fd == -1) {
perror("shm_open");
exit(1);
}
// 2. 设置共享内存大小
if (ftruncate(shm_fd, SHM_SIZE) == -1) {
perror("ftruncate");
exit(1);
}
// 3. 映射共享内存
counter = (int *)mmap(NULL, SHM_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED, shm_fd, 0);
if (counter == MAP_FAILED) {
perror("mmap");
exit(1);
}
// 4. 初始化计数器
*counter = 0;
// 5. 递增计数器
for (int i = 0; i < 10; i++) {
(*counter)++;
printf("Writer: Counter = %d\n", *counter);
sleep(1);
}
printf("Writer: Final counter value = %d\n", *counter);
// 6. 清理
munmap(counter, SHM_SIZE);
close(shm_fd);
// 可选:删除共享内存对象
// shm_unlink(SHM_NAME);
return 0;
}
reader.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#define SHM_NAME "/counter_shm"
#define SHM_SIZE sizeof(int)
int main() {
int shm_fd;
int *counter;
// 1. 打开共享内存对象
shm_fd = shm_open(SHM_NAME, O_RDWR, 0);
if (shm_fd == -1) {
perror("shm_open");
exit(1);
}
// 2. 映射共享内存
counter = (int *)mmap(NULL, SHM_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED, shm_fd, 0);
if (counter == MAP_FAILED) {
perror("mmap");
exit(1);
}
// 3. 读取并修改计数器
for (int i = 0; i < 5; i++) {
printf("Reader: Current counter = %d\n", *counter);
(*counter)++; // 也修改计数器
sleep(2);
}
printf("Reader: Final value = %d\n", *counter);
// 4. 清理
munmap(counter, SHM_SIZE);
close(shm_fd);
return 0;
}
3.2 进阶示例:共享数据结构
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#define SHM_NAME "/structured_shm"
// 共享数据结构
typedef struct {
int id;
char name[32];
double value;
time_t timestamp;
int active;
} shared_data_t;
int main() {
int shm_fd;
shared_data_t *data;
// 创建共享内存
shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
ftruncate(shm_fd, sizeof(shared_data_t));
// 映射共享内存
data = (shared_data_t *)mmap(NULL, sizeof(shared_data_t),
PROT_READ | PROT_WRITE,
MAP_SHARED, shm_fd, 0);
pid_t pid = fork();
if (pid == 0) {
// 子进程 - 写入数据
srand(time(NULL));
for (int i = 0; i < 3; i++) {
data->id = i;
snprintf(data->name, 32, "Process_%d", i);
data->value = (double)rand() / RAND_MAX * 100.0;
data->timestamp = time(NULL);
data->active = 1;
printf("Child wrote: id=%d, name=%s, value=%.2f\n",
data->id, data->name, data->value);
sleep(1);
}
data->active = 0; // 标记结束
} else {
// 父进程 - 读取数据
while (data->active) {
printf("Parent read: id=%d, name=%s, value=%.2f, time=%s",
data->id, data->name, data->value,
ctime(&data->timestamp));
sleep(2);
}
// 清理
munmap(data, sizeof(shared_data_t));
close(shm_fd);
shm_unlink(SHM_NAME);
}
return 0;
}
四、同步机制集成
4.1 使用 POSIX 信号量同步
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <unistd.h>
#define SHM_NAME "/synced_shm"
#define SEM_NAME "/sync_sem"
typedef struct {
int data[10];
int read_index;
int write_index;
int count;
} buffer_t;
int main() {
// 1. 创建并初始化信号量
sem_t *sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
// 2. 创建共享内存
int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
ftruncate(shm_fd, sizeof(buffer_t));
buffer_t *buf = (buffer_t *)mmap(NULL, sizeof(buffer_t),
PROT_READ | PROT_WRITE,
MAP_SHARED, shm_fd, 0);
// 初始化缓冲区
buf->read_index = 0;
buf->write_index = 0;
buf->count = 0;
pid_t pid = fork();
if (pid == 0) {
// 生产者进程
for (int i = 0; i < 20; i++) {
sem_wait(sem); // 获取互斥锁
// 检查缓冲区是否满
if (buf->count < 10) {
buf->data[buf->write_index] = i;
buf->write_index = (buf->write_index + 1) % 10;
buf->count++;
printf("Produced: %d (count: %d)\n", i, buf->count);
}
sem_post(sem); // 释放互斥锁
usleep(500000); // 500ms
}
} else {
// 消费者进程
for (int i = 0; i < 20; i++) {
sem_wait(sem);
// 检查缓冲区是否空
if (buf->count > 0) {
int value = buf->data[buf->read_index];
buf->read_index = (buf->read_index + 1) % 10;
buf->count--;
printf("Consumed: %d (count: %d)\n", value, buf->count);
}
sem_post(sem);
usleep(800000); // 800ms
}
// 清理
munmap(buf, sizeof(buffer_t));
close(shm_fd);
shm_unlink(SHM_NAME);
sem_close(sem);
sem_unlink(SEM_NAME);
}
return 0;
}
4.2 使用互斥锁(pthread_mutex_t)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <pthread.h>
#define SHM_NAME "/mutex_shm"
typedef struct {
pthread_mutex_t mutex;
pthread_mutexattr_t mutex_attr;
int shared_value;
} shared_struct_t;
int main() {
int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
ftruncate(shm_fd, sizeof(shared_struct_t));
shared_struct_t *shared = (shared_struct_t *)mmap(
NULL, sizeof(shared_struct_t),
PROT_READ | PROT_WRITE,
MAP_SHARED, shm_fd, 0
);
// 初始化跨进程互斥锁
pthread_mutexattr_init(&shared->mutex_attr);
pthread_mutexattr_setpshared(&shared->mutex_attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&shared->mutex, &shared->mutex_attr);
shared->shared_value = 0;
pid_t pid = fork();
if (pid == 0) {
// 子进程
for (int i = 0; i < 5; i++) {
pthread_mutex_lock(&shared->mutex);
shared->shared_value++;
printf("Child: value = %d\n", shared->shared_value);
pthread_mutex_unlock(&shared->mutex);
sleep(1);
}
} else {
// 父进程
for (int i = 0; i < 5; i++) {
pthread_mutex_lock(&shared->mutex);
shared->shared_value += 10;
printf("Parent: value = %d\n", shared->shared_value);
pthread_mutex_unlock(&shared->mutex);
sleep(2);
}
// 清理
pthread_mutex_destroy(&shared->mutex);
pthread_mutexattr_destroy(&shared->mutex_attr);
munmap(shared, sizeof(shared_struct_t));
close(shm_fd);
shm_unlink(SHM_NAME);
}
return 0;
}
五、高级主题
5.1 共享内存与指针
警告: 在共享内存中存储指针是危险的,因为指针指向的地址在另一个进程中可能无效。
解决方案: 使用偏移量代替指针
typedef struct {
int data_offset; // 相对于结构体起始的偏移量
int data_size;
} shm_header_t;
void write_to_shm(char *shm_base) {
shm_header_t *header = (shm_header_t *)shm_base;
char *data_ptr = shm_base + header->data_offset;
// 使用 data_ptr 访问数据
strcpy(data_ptr, "Hello from shared memory");
}
// 初始化时设置偏移量
void init_shm(char *shm_base) {
shm_header_t *header = (shm_header_t *)shm_base;
header->data_offset = sizeof(shm_header_t); // 数据紧接在头部之后
header->data_size = 1024;
}
5.2 匿名共享内存
// 使用 MAP_ANONYMOUS 创建匿名共享内存(不基于文件)
void *shared_mem = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1, 0);
特点:
- 不依赖文件系统
- 只能用于父子进程间共享(因为需要继承内存映射)
- 进程退出后自动释放
5.3 性能优化技巧
// 1. 使用大页面(如果可用)
void *mem = mmap(NULL, 2 * 1024 * 1024, // 2MB
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_HUGETLB,
fd, 0);
// 2. 内存对齐(提高缓存效率)
typedef struct __attribute__((aligned(64))) {
int counter;
char padding[60]; // 填充到64字节
} cache_aligned_t;
// 3. 避免虚假共享
typedef struct {
int writer_data __attribute__((aligned(64)));
int reader_data __attribute__((aligned(64)));
} no_false_sharing_t;
六、错误处理与调试
6.1 完整错误处理示例
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
void* create_shared_memory(const char *name, size_t size) {
int fd;
void *addr;
// 创建共享内存对象
fd = shm_open(name, O_CREAT | O_RDWR, 0666);
if (fd == -1) {
fprintf(stderr, "shm_open failed: %s\n", strerror(errno));
return MAP_FAILED;
}
// 设置大小
if (ftruncate(fd, size) == -1) {
fprintf(stderr, "ftruncate failed: %s\n", strerror(errno));
close(fd);
shm_unlink(name);
return MAP_FAILED;
}
// 映射内存
addr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
fprintf(stderr, "mmap failed: %s\n", strerror(errno));
close(fd);
shm_unlink(name);
return MAP_FAILED;
}
// 可以关闭文件描述符,映射仍然有效
close(fd);
return addr;
}
void cleanup_shared_memory(const char *name, void *addr, size_t size) {
if (munmap(addr, size) == -1) {
fprintf(stderr, "munmap failed: %s\n", strerror(errno));
}
if (shm_unlink(name) == -1) {
fprintf(stderr, "shm_unlink failed: %s\n", strerror(errno));
}
}
6.2 调试工具
# 查看系统中的共享内存对象
ls -l /dev/shm/ # Linux
ls -l /tmp/ # 某些系统
# 查看内存映射
cat /proc/$PID/maps # 查看进程的内存映射
# 使用 ipcs 命令(System V IPC)
ipcs -m # 查看共享内存段
# 使用 lsof 查看打开的文件
lsof | grep /dev/shm
七、实际应用场景
7.1 数据库连接池
// 简化的数据库连接池共享内存结构
typedef struct {
pthread_mutex_t lock;
int max_connections;
int used_connections;
connection_t connections[MAX_CONNECTIONS];
} connection_pool_t;
7.2 实时数据流处理
// 环形缓冲区用于实时数据流
typedef struct {
int head;
int tail;
int size;
int capacity;
struct timespec timestamp;
sensor_data_t buffer[0]; // 柔性数组
} ring_buffer_t;
7.3 游戏服务器状态同步
// 游戏状态共享
typedef struct {
player_t players[MAX_PLAYERS];
game_state_t state;
physics_data_t physics;
int frame_number;
char map_data[MAP_SIZE];
} shared_game_state_t;
八、编译与运行
# 编译(需要链接实时库)
gcc -o program program.c -lrt -lpthread
# 设置共享内存限制
sysctl -w kernel.shmmax=1073741824 # 设置最大共享内存为1GB
sysctl -w kernel.shmall=2097152 # 设置系统范围内共享内存页数
# 永久设置(Linux)
echo "kernel.shmmax=1073741824" >> /etc/sysctl.conf
echo "kernel.shmall=2097152" >> /etc/sysctl.conf
sysctl -p
总结
POSIX 共享内存是最高效的 IPC 机制,适用于:
- 高性能计算:需要快速数据交换的场景
- 实时系统:低延迟要求的应用
- 大数据处理:避免数据复制开销
- 进程池通信:工作进程间共享状态
关键注意事项:
- 必须实现同步:使用信号量、互斥锁等机制
- 注意内存对齐:避免性能下降
- 妥善清理资源:防止内存泄漏
- 考虑可移植性:不同系统的行为可能略有差异
- 安全考虑:共享内存对所有有权限的进程可见
通过合理使用 POSIX 共享内存,可以构建出高效、可扩展的进程间通信架构。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)