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 机制,适用于:

  1. 高性能计算:需要快速数据交换的场景
  2. 实时系统:低延迟要求的应用
  3. 大数据处理:避免数据复制开销
  4. 进程池通信:工作进程间共享状态

关键注意事项:

  1. 必须实现同步:使用信号量、互斥锁等机制
  2. 注意内存对齐:避免性能下降
  3. 妥善清理资源:防止内存泄漏
  4. 考虑可移植性:不同系统的行为可能略有差异
  5. 安全考虑:共享内存对所有有权限的进程可见

通过合理使用 POSIX 共享内存,可以构建出高效、可扩展的进程间通信架构。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐