Linux共享内存编程详解:shm_open与mmap函数实战指南

在Linux进程间通信(IPC)中,共享内存是效率最高的方式之一。它通过让多个进程直接访问同一块物理内存区域,避免了数据拷贝的开销,适用于高吞吐量的数据交换场景。本文将详细拆解共享内存编程的核心函数(shm_open、mmap等),结合函数原型、参数解析和实战示例,帮助大家快速掌握共享内存的使用方法。

一、共享内存核心函数详解

共享内存的实现依赖一组系统调用,从创建共享内存对象、设置大小,到内存映射和释放,每个步骤都有对应的核心函数。

1. 共享内存对象的创建与删除:shm_open() & shm_unlink()

共享内存对象本质是/dev/shm目录下的临时文件,通过shm_open创建/打开,shm_unlink删除。

头文件

#include <sys/mman.h>

shm_open():创建/打开共享内存对象

int shm_open(const char *name, int oflag, mode_t mode);

  • 参数说明

    • name:共享内存对象名称,无需指定/dev/shm路径(系统自动映射)。

    • oflag:打开模式,支持多选项拼接(如O_CREAT | O_RDWR):

      • O_CREAT:对象不存在时创建。

      • O_RDONLY/O_RDWR:只读/读写模式。

      • O_EXCL:与O_CREAT配合,避免覆盖已存在对象。

      • O_TRUNC:截断对象至0长度(仅O_RDWR模式有效)。

    • mode:创建新对象时的权限位(类似文件权限),常用0644(所有者读写、其他只读)。

  • 返回值:成功返回文件描述符,失败返回-1

shm_unlink():删除共享内存对象
int shm_unlink(const char *name);
  • 参数说明name为要删除的共享内存对象名称。

  • 返回值:成功返回0,失败返回-1

  • 注意:此函数仅删除对象的目录项,已映射的内存需通过munmap释放。


2. 共享内存大小设置:truncate() & ftruncate()

创建共享内存对象后,需通过这两个函数设置其大小(默认大小为0,无法直接使用)。

头文件
#include <unistd.h> 
#include <sys/types.h>
函数原型与差异

函数

作用

核心差异

truncate(const char *path, off_t length)

按路径缩放文件大小

需指定文件路径,仅支持文件系统中的实际文件

ftruncate(int fd, off_t length)

按文件描述符缩放大小

接收文件描述符,支持shm_open创建的共享内存对象

  • 参数说明

    • path/fd:目标文件路径或文件描述符。

    • length:目标大小(字节数)。

  • 返回值:成功返回0,失败返回-1

  • 特性:文件缩小则截断部分数据丢失,放大则扩展部分填充\0,文件偏移量不变。


3. 内存映射与释放:mmap() & munmap()

通过mmap将共享内存对象映射到进程的虚拟地址空间,进程可直接读写该内存;munmap用于取消映射。

头文件
#include <sys/mman.h>
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:允许写入。

      • PROT_EXEC:允许执行。

      • PROT_NONE:不可访问。

    • flags:映射选项(核心选项二选一):

      • MAP_SHARED:共享映射,修改会同步到共享内存(进程间通信必选)。

      • MAP_PRIVATE:私有映射,修改仅对当前进程有效。

      • 其他常用选项:MAP_ANONYMOUS(匿名映射,不关联文件)、MAP_FIXED(强制指定映射地址)。

    • fd:被映射的文件/共享内存描述符(匿名映射传-1)。

    • offset:映射起始偏移量(共享内存映射设为0)。

  • 返回值:成功返回映射起始地址(常用char*接收),失败返回MAP_FAILED(即(void*)-1)。

munmap():取消内存映射
int munmap(void *addr, size_t length);
  • 参数说明

    • addrmmap返回的映射起始地址。

    • length:映射内存的字节长度(需与mmaplength一致)。

  • 返回值:成功返回0,失败返回-1

  • 注意:取消映射后,原地址不可再访问,否则会触发段错误。

二、共享内存实战示例:父子进程通信

下面通过一个完整示例,实现父进程写入数据、子进程读取数据的共享内存通信流程。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>

#define SHM_NAME "/my_shared_mem"  // 共享内存对象名称
#define SHM_SIZE 1024              // 共享内存大小(1KB)

int main() {
    int shm_fd;
    char *shm_addr;

    // 1. 创建共享内存对象(读写模式,权限0644)
    shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0644);
    if (shm_fd == -1) {
        perror("shm_open failed");
        exit(EXIT_FAILURE);
    }

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

    // 3. 映射共享内存到虚拟地址空间
    shm_addr = (char *)mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (shm_addr == MAP_FAILED) {
        perror("mmap failed");
        shm_unlink(SHM_NAME);
        exit(EXIT_FAILURE);
    }

    // 关闭共享内存文件描述符(映射后可关闭,不影响映射关系)
    close(shm_fd);

    // 4. 创建子进程
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork failed");
        munmap(shm_addr, SHM_SIZE);
        shm_unlink(SHM_NAME);
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {  // 子进程:读取共享内存数据
        printf("子进程:等待父进程写入数据...\n");
        sleep(2);  // 等待父进程写入

        // 读取共享内存内容
        printf("子进程:从共享内存读取到:%s\n", shm_addr);

        // 子进程取消映射
        if (munmap(shm_addr, SHM_SIZE) == -1) {
            perror("munmap failed (child)");
            exit(EXIT_FAILURE);
        }
        exit(EXIT_SUCCESS);
    } else {  // 父进程:写入数据到共享内存
        const char *msg = "Hello, 共享内存!这是父进程的消息";
        strncpy(shm_addr, msg, SHM_SIZE - 1);  // 留一个字节存'\0'
        shm_addr[SHM_SIZE - 1] = '\0';  // 确保字符串结束符
        printf("父进程:已写入数据到共享内存\n");

        // 等待子进程结束
        wait(NULL);

        // 父进程取消映射
        if (munmap(shm_addr, SHM_SIZE) == -1) {
            perror("munmap failed (parent)");
            exit(EXIT_FAILURE);
        }

        // 删除共享内存对象
        if (shm_unlink(SHM_NAME) == -1) {
            perror("shm_unlink failed");
            exit(EXIT_FAILURE);
        }
        printf("父进程:共享内存已释放\n");
    }

    return 0;
}

编译与运行

        1.编译命令(需链接rt库,部分系统要求):

        2.运行结果:

三、注意事项与常见问题

  1. 权限问题shm_openmode参数需合理设置(如0644),否则其他进程可能无法访问。

  2. 对象名称规范:共享内存对象名称需以/开头(如/my_shm),否则shm_open会失败。

  3. 内存释放顺序:先通过munmap取消映射,再通过shm_unlink删除对象,避免内存泄漏。

  4. 数据同步:共享内存无默认同步机制,多进程并发读写时需配合信号量、互斥锁等同步手段,避免数据竞争。

  5. 大小匹配mmaplength需与ftruncate设置的共享内存大小一致,否则可能出现访问越界。

四、总结

共享内存是Linux IPC中效率最高的方式,核心依赖shm_open(创建对象)、ftruncate(设置大小)、mmap(内存映射)三个关键步骤。通过本文的函数解析和实战示例,相信大家已掌握共享内存的基本使用方法。

实际开发中,共享内存常与同步机制结合使用,适用于大数据量、低延迟的进程间通信场景(如实时数据传输、多进程协作计算等)。如果在使用过程中遇到问题,可通过man 2 函数名(如man 2 mmap)查看系统手册获取更详细的说明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值