linux c共享内存POSIX API的用法

Linux 下的 C 语言共享内存(Shared Memory)是进程间通信(IPC)中最快的一种方式。它允许两个或多个进程访问同一块物理内存空间,因为数据不需要在客户机和服务器之间复制,所以速度非常快。

在 Linux 中,主要有两套共享内存的 API 标准:

  • System V 共享内存 (老牌,经典,使用 shmget, shmat 等)
  • POSIX 共享内存 (新标准,更符合文件操作习惯,使用 shm_open, mmap 等)

下面我将详细介绍 POSIX 共享内存的用法,并提供一个包含**同步机制(互斥锁)**的完整代码示例。

核心 API 简介

使用 POSIX 共享内存主要涉及以下几个步骤和函数:

  1. shm_open() : 创建或打开一个共享内存对象。
    • 类似于 open(),但它不操作普通文件,而是操作内存对象。
    • 返回一个文件描述符(file descriptor)。
  2. ftruncate() : 设置共享内存对象的大小。
    • 新创建的共享内存对象大小为 0,必须调用此函数设置大小。
  3. mmap() : 将共享内存对象映射到进程的虚拟地址空间。
    • 这是读写数据的关键步骤,映射后就可以像操作普通指针一样操作这块内存。
  4. munmap() : 解除映射。
    • 进程结束或不再需要时调用。
  5. shm_unlink() : 删除共享内存对象。
    • 类似于 unlink(),将对象从系统中移除。如果不调用,即使进程结束,内存对象也会一直存在于 /dev/shm 中(直到重启)。

进阶示例:带互斥锁同步的共享内存

为什么需要同步?

共享内存本身是最快的 IPC 方式,但它不提供 任何同步机制。如果一个进程正在写,另一个进程同时在读,就会读到脏数据。因此,在生产环境中,我们通常会在共享内存中放入一个互斥锁(Mutex)

场景设计
  • Protocol.h: 定义共享的数据结构。
  • Writer.c: 初始化共享内存和锁,写入数据。
  • Reader.c: 打开共享内存,加锁读取数据。
1. 公共头文件 (protocol.h)

这里定义了共享内存的名称、大小以及数据结构。

c 复制代码
#ifndef PROTOCOL_H
#define PROTOCOL_H

#include <pthread.h>

// 共享内存对象的名称,必须以 "/" 开头
#define SHM_NAME "/demo_posix_shm"

// 定义共享内存中存放的数据结构
typedef struct {
    // 进程间互斥锁,必须设置为 PTHREAD_PROCESS_SHARED
    pthread_mutex_t mutex;
    
    // 一个标志位,用于简单的逻辑控制(例如:数据是否已更新)
    int data_version;
    
    // 实际的数据载荷
    char buffer[256];
} SharedData;

#endif
2. 写入端 (writer.c)

负责创建共享内存、初始化互斥锁,并写入数据。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>      // For O_* constants
#include <sys/mman.h>   // For mmap, shm_open
#include <unistd.h>     // For ftruncate, close
#include <pthread.h>    // For mutex
#include <errno.h>
#include "protocol.h"

int main() {
    // 1. 创建共享内存对象
    // O_CREAT: 不存在则创建
    // O_RDWR: 读写模式
    // O_TRUNC: 如果已存在,将其截断(清空)
    int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR | O_TRUNC, 0666);
    if (shm_fd == -1) {
        perror("shm_open failed");
        exit(EXIT_FAILURE);
    }

    // 2. 设置共享内存大小
    if (ftruncate(shm_fd, sizeof(SharedData)) == -1) {
        perror("ftruncate failed");
        exit(EXIT_FAILURE);
    }

    // 3. 内存映射
    SharedData *shared_mem = (SharedData *)mmap(NULL, sizeof(SharedData), 
                                                PROT_READ | PROT_WRITE, 
                                                MAP_SHARED, shm_fd, 0);
    if (shared_mem == MAP_FAILED) {
        perror("mmap failed");
        exit(EXIT_FAILURE);
    }

    // 4. 初始化互斥锁 (非常关键的一步)
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    // 设置互斥锁属性为进程间共享 (PTHREAD_PROCESS_SHARED)
    // 默认的互斥锁只能在同一个进程的线程间使用
    if (pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED) != 0) {
        perror("pthread_mutexattr_setpshared failed");
        exit(EXIT_FAILURE);
    }
    pthread_mutex_init(&shared_mem->mutex, &attr);
    
    // 初始化数据
    shared_mem->data_version = 0;
    memset(shared_mem->buffer, 0, sizeof(shared_mem->buffer));

    printf("Writer: Shared memory initialized. Start writing...\n");

    // 5. 模拟多次写入
    for (int i = 1; i <= 5; i++) {
        sleep(1); // 模拟耗时操作

        // --- 进入临界区 ---
        pthread_mutex_lock(&shared_mem->mutex);
        
        shared_mem->data_version = i;
        snprintf(shared_mem->buffer, sizeof(shared_mem->buffer), "Message ID #%d", i);
        printf("Writer: Wrote '%s'\n", shared_mem->buffer);
        
        pthread_mutex_unlock(&shared_mem->mutex);
        // --- 退出临界区 ---
    }

    printf("Writer: Done. Waiting for reader to finish (press Enter to cleanup)...\n");
    getchar();

    // 6. 清理资源
    // 销毁锁
    pthread_mutex_destroy(&shared_mem->mutex);
    pthread_mutexattr_destroy(&attr);

    // 解除映射
    munmap(shared_mem, sizeof(SharedData));
    // 关闭文件描述符
    close(shm_fd);
    // 删除共享内存对象
    shm_unlink(SHM_NAME);

    printf("Writer: Cleanup complete.\n");
    return 0;
}
3. 读取端 (reader.c)

负责打开共享内存,并在锁的保护下读取数据。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <pthread.h>
#include "protocol.h"

int main() {
    // 1. 打开共享内存对象
    // 注意:这里不需要 O_CREAT,因为假设 Writer 已经创建了
    int shm_fd = shm_open(SHM_NAME, O_RDWR, 0666);
    if (shm_fd == -1) {
        perror("Reader: shm_open failed (is writer running?)");
        exit(EXIT_FAILURE);
    }

    // 2. 内存映射
    SharedData *shared_mem = (SharedData *)mmap(NULL, sizeof(SharedData), 
                                                PROT_READ | PROT_WRITE, 
                                                MAP_SHARED, shm_fd, 0);
    if (shared_mem == MAP_FAILED) {
        perror("mmap failed");
        exit(EXIT_FAILURE);
    }

    printf("Reader: Attached to shared memory. Start reading...\n");

    int last_read_version = 0;

    // 3. 循环读取
    for (int i = 0; i < 10; i++) {
        sleep(1); 

        // --- 进入临界区 ---
        pthread_mutex_lock(&shared_mem->mutex);

        if (shared_mem->data_version > last_read_version) {
            printf("Reader: Detected new data (v%d): %s\n", 
                   shared_mem->data_version, shared_mem->buffer);
            last_read_version = shared_mem->data_version;
        } else {
            printf("Reader: No new data yet...\n");
        }

        pthread_mutex_unlock(&shared_mem->mutex);
        // --- 退出临界区 ---
    }

    // 4. 解除映射
    munmap(shared_mem, sizeof(SharedData));
    close(shm_fd);
    
    // 注意:Reader 通常不负责 shm_unlink,除非它是最后一个退出的进程
    printf("Reader: Detached and exiting.\n");

    return 0;
}

编译与运行

POSIX 共享内存和 pthread 库通常需要链接 rt (Realtime) 和 pthread 库。

编译命令:

bash 复制代码
gcc writer.c -o writer -lrt -lpthread
gcc reader.c -o reader -lrt -lpthread

运行步骤:

  1. 打开一个终端,运行 Writer:

    bash 复制代码
    ./writer

    Writer 会输出正在写入的消息。

  2. 打开另一个终端,运行 Reader:

    bash 复制代码
    ./reader

    Reader 会检测到 Writer 写入的新数据并打印出来。

  3. 观察系统状态

    在程序运行时,你可以查看 /dev/shm 目录,你会看到一个名为 demo_posix_shm 的文件:

    bash 复制代码
    ls -l /dev/shm/

关键点总结

  1. 链接库 :编译时必须加上 -lrt (对于 shm_open) 和 -lpthread (对于互斥锁)。
  2. MAP_SHARED :在 mmap 时必须使用 MAP_SHARED,否则修改对其他进程不可见。
  3. 互斥锁属性 :普通的 pthread_mutex 只能用于线程同步。用于进程间同步时,必须调用 pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED)
  4. 生命周期 :POSIX 共享内存具有内核持久性 。如果程序崩溃且没有调用 shm_unlink,该内存对象会一直存在,直到系统重启或手动删除(rm /dev/shm/demo_posix_shm)。
相关推荐
林鸿风采14 小时前
在Alpine Linux上部署docker,并配置开机自启
linux·docker·eureka·alpine
l1t15 小时前
在arm64 Linux系统上编译tdoku-lib的问题和解决
linux·运维·服务器·c语言·cmake
secondyoung15 小时前
Git使用:rebase用法
c语言·经验分享·git·vscode
kklovecode15 小时前
C语言之头文件,宏和条件编译
c语言·开发语言·算法
txinyu的博客16 小时前
Linux 内存管理
linux·运维·开发语言·c++
珠穆峰16 小时前
linux清理缓存命令“echo 3 > /proc/sys/vm/drop_caches”
java·linux·缓存
Xの哲學16 小时前
Linux自旋锁深度解析: 从设计思想到实战应用
linux·服务器·网络·数据结构·算法
晚风吹长发16 小时前
深入理解Linux中用户缓冲区,文件系统及inode
linux·运维·算法·链接·缓冲区·inode
LuckyLay16 小时前
Ubuntu配置多版本Java,自由切换
java·linux·ubuntu
SongYuLong的博客16 小时前
openwrt 启动脚本
linux·运维·服务器·物联网