共享内存详解:原理、系统调用与高性能进程间通信

共享内存详解:原理、系统调用与高性能进程间通信

在进程间通信(IPC)的众多方式中,无论是管道、消息队列还是共享内存,它们的本质思想完全一致:让不同的进程看到同一份资源。共享内存直接把同一块物理内存暴露给多个进程,因而成为速度最快的 IPC 机制。本文将围绕共享内存的原理、系统调用、命令行管理及特性展开,帮助你彻底掌握这一核心技术。

一、核心原理:让不同进程看到同一份资源

共享内存允许多个进程将各自的虚拟地址空间映射到同一块物理内存区域。这意味着进程 A 写入的数据,进程 B 无需经过内核中转就能立即看到,因为两者访问的是完全相同的物理页框。

这个过程被称为 挂载(attach),就像把一块公共内存"挂"到了各自进程的地址空间中。通过这种设计,数据拷贝次数大幅减少,通信效率得到质的提升。

类比理解:在文件系统中,inode 是文件的唯一标识,但我们用文件描述符(fd)来操作文件。共享内存的 key 就如同 inode,而 shmget 返回的 shmid 就像 fd,是进程内操作共享内存的句柄。

二、使用步骤与关键系统调用

使用共享内存的标准流程可以分为以下几步:

  1. 创建或打开 共享内存 ------ shmget
  2. 关联(挂接) 到进程地址空间 ------ shmat
  3. 直接读写共享内存
  4. 解除关联(去挂接) ------ shmdt
  5. 删除共享内存 ------ shmctl 或命令行工具

下面逐一详解这些系统调用。

2.1 创建/获取共享内存:shmget

参数说明:

  • key :共享内存的唯一标识。可由 ftok() 生成,或使用 IPC_PRIVATE 创建私有共享内存。
  • size :申请的大小,单位为字节。内核以页(通常 4KB)为单位分配,因此 大小会自动向上取整为页的整数倍。例如申请 4097 字节,实际获得 8KB。
  • shmflg :标志位与权限组合。
    • IPC_CREAT:不存在则创建,存在则直接打开。
    • IPC_EXCL:与 IPC_CREAT 联用,若已存在则返回错误,保证创建全新的内存段。
    • 权限位:如 0666 表示所有用户可读写。权限必须显式指定,否则可能为 0 导致无法访问。

返回值: 成功返回 共享内存标识符 shmid(非负整数),失败返回 -1。

核心类比:key 是全局名称(类似 inode),shmid 是进程操作句柄(类似文件描述符 fd)。不同进程用同一个 key 可获取到同一个 shmid,从而操作同一块共享内存。

2.2 挂接共享内存:shmat

创建共享内存后,进程还无法直接访问,必须通过 shmat 将其映射到自己的虚拟地址空间。

  • shmid :由 shmget 返回的标识符。
  • shmaddr :建议传入 NULL,让内核自动选择一块合适的地址。
  • shmflg :通常设为 0(读写),若需只读则指定 SHM_RDONLY

返回值: 成功返回映射后的内存地址(void*),直接强转为所需类型即可使用;失败返回 (void*)-1

2.3 直接使用共享内存

挂接成功后,共享内存的访问极其简单:无需任何系统调用,直接解引用指针读写

c 复制代码
char *addr = (char*)shmat(shmid, NULL, 0);
sprintf(addr, "Hello from Process A");
// 进程 B 挂接同一 shmid 后,能直接读取到 "Hello from Process A"

这种直接内存访问的方式,正是共享内存速度极快的根本原因。

2.4 解除关联:shmdt

进程不再需要访问共享内存时,应将其从自身地址空间中剥离。注意:这不会删除共享内存本身

  • shmaddrshmat 返回的地址。
  • 返回值:成功返回 0,失败返回 -1。

2.5 控制与删除:shmctl 及命令行工具

共享内存是内核持久化资源,即使所有进程都解除关联,仍会存在,必须显式删除。

  • 删除共享内存shmctl(shmid, IPC_RMID, NULL)。标记删除,待所有挂接进程 shmdt 后由系统回收。
  • 获取信息shmctl(shmid, IPC_STAT, buf) 可查看大小、挂接进程数等。

命令行管理共享内存:

  • 查看当前系统中的共享内存段:
bash 复制代码
ipcs -m

输出中 nattch 表示当前挂接数量,bytes 表示大小。

  • 删除指定共享内存:
bash 复制代码
ipcrm -m <shmid>        # 按 shmid 删除
ipcrm -M <key>          # 按 key 删除

三、完整示例:server 与 client 通信

以下代码展示了共享内存的标准使用流程。server 负责创建共享内存、写入数据并在等待 client 读取后删除;client 则获取同一块共享内存并读取内容。

server.c(创建并写入数据)

c 复制代码
#include <stdio.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>
#include <unistd.h>

#define SHM_SIZE 1024           // 共享内存大小(会向上取整为页的整数倍)
#define PATHNAME "."            // ftok 用于生成 key 的路径
#define PROJ_ID 0x66            // ftok 用于生成 key 的项目 ID

int main() {
    // 1. 生成唯一 key
    key_t key = ftok(PATHNAME, PROJ_ID);
    if (key == -1) {
        perror("ftok");
        return 1;
    }

    // 2. 创建共享内存(不存在则创建,权限 0666)
    int shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1) {
        perror("shmget");
        return 1;
    }
    printf("Server: shmid = %d\n", shmid);

    // 3. 挂接共享内存
    char *addr = (char*)shmat(shmid, NULL, 0);
    if (addr == (void*)-1) {
        perror("shmat");
        return 1;
    }

    // 4. 写入数据(无需系统调用)
    const char *msg = "Hello from server!";
    strcpy(addr, msg);
    printf("Server: written [%s]\n", msg);

    // 5. 等待 client 读取(仅演示,实际应使用信号量等同步机制)
    printf("Server: waiting 10s for client to read...\n");
    sleep(10);

    // 6. 解除关联
    shmdt(addr);

    // 7. 删除共享内存
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl IPC_RMID");
        return 1;
    }
    printf("Server: shared memory removed.\n");

    return 0;
}

client.c(获取并读取数据)

c 复制代码
#include <stdio.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>

#define SHM_SIZE 1024
#define PATHNAME "."
#define PROJ_ID 0x66

int main() {
    // 1. 生成与 server 相同的 key
    key_t key = ftok(PATHNAME, PROJ_ID);
    if (key == -1) {
        perror("ftok");
        return 1;
    }

    // 2. 获取已创建的共享内存(不加 IPC_CREAT)
    int shmid = shmget(key, SHM_SIZE, 0666);
    if (shmid == -1) {
        perror("shmget");
        return 1;
    }
    printf("Client: shmid = %d\n", shmid);

    // 3. 挂接共享内存
    char *addr = (char*)shmat(shmid, NULL, 0);
    if (addr == (void*)-1) {
        perror("shmat");
        return 1;
    }

    // 4. 直接读取数据(无需系统调用)
    printf("Client: read [%s]\n", addr);

    // 5. 解除关联
    shmdt(addr);

    return 0;
}

四、共享内存的突出特点

4.1 优点:零拷贝与直接访问

  • 无需系统调用 :数据读写直接在用户态完成,不会陷入内核,效率极高。相比之下,管道每次读写都需要 read/write 系统调用。
  • 拷贝次数少,速度快:管道等需要经历"用户空间→内核缓冲区→用户空间"的两次拷贝;共享内存由进程 A 直接写入物理内存,进程 B 直接读取,几乎达到零拷贝。

4.2 缺点:缺乏同步与互斥机制

共享内存仅仅提供了一片"裸"内存区域,内核不会对访问做任何保护 。管道自带同步能力(读写端关闭时会有相应信号或返回 EOF,缓冲区满/空会阻塞),而共享内存则完全依赖开发者自己处理竞争问题。多个进程同时写入会造成数据混乱,因此必须配合 信号量、互斥锁或条件变量 等同步机制使用。

五、总结与思考

共享内存通过把同一物理内存映射到不同进程的虚拟地址空间,真正实现了"让不同进程看到同一份资源"。它具备极高的通信效率,但也将同步控制的责任完全交给了程序员。

掌握 shmgetshmatshmdtshmctl 这几个关键系统调用,理解 key 与 shmid 的关系、size 的对齐规则以及标志位与权限的组合,再配合 ipcs/ipcrm 命令进行管理,你就能够在项目中灵活运用共享内存。实际开发中,请务必为共享内存加上适当的同步措施,才能构建稳定可靠的高性能 IPC 程序。

相关推荐
2601_965798471 小时前
Build a Consulting Site in 2026: Nifty Theme Practical Review
java·linux·数据库
枳实-叶1 小时前
Linux 环境下段错误出现的原因及调试方法
linux·运维·服务器
Dawn-bit2 小时前
Linux文本处理三剑客之sed详解
linux·运维·服务器·云计算·运维开发
Lzh编程小栈2 小时前
【STM32底层精讲】RCC时钟系统超全详解(时钟树+源码+避坑指南)
c语言·stm32·单片机·嵌入式硬件·面试
dok122 小时前
Johannes 《Linux内核模块与设备驱动开发:编写Linux驱动程序》 (9)manual_cdev
linux·运维·驱动开发
June`2 小时前
常量内存和只读缓存
c++·人工智能·算法·cuda
Cx330❀2 小时前
【Linux网络】深入 HTTP 协议(五):从 Cookie/Session 原理到 C++ 源码实战
linux·运维·服务器·开发语言·网络·c++·http
鸿芯微控科技2 小时前
MFC气体转换系数怎么用?标定气体、换算方向与Python批量计算
c++·python·mfc
小小、码农4 小时前
Linux进程概念
linux·服务器·网络