进程间通信

1.shm 共享内存

共享内存(Shared Memory,简称 SHM)是进程间通信(IPC)的一种机制,它允许多个进程访问同一块物理内存区域,从而实现高效的数据共享。不同于管道或消息队列,共享内存不涉及内核的复制开销,而是直接映射到用户空间地址,因此性能最高,但需要进程自行处理同步和互斥问题。

本文将详细解释 System V 风格的共享内存(SHM),这是 Unix/Linux 系统中最常见的实现方式。POSIX 也有共享内存 API(如 shm_open 和 mmap),但 System V SHM 更传统且广泛使用。我们将从概念、API、示例、优缺点等方面展开。

1. 共享内存的基本概念
  • 原理:操作系统为共享内存分配一块连续的物理内存页,并将其映射到多个进程的虚拟地址空间中。进程通过系统调用获取这段内存的句柄(ID),然后附加(attach)到自己的地址空间进行读写。
  • 关键元素
    • 键值(Key):一个整数(通常用 ftok 生成),用于标识共享内存段。多个进程使用相同键值才能访问同一段内存。
    • 大小(Size):共享内存段的字节大小,必须是页面大小的整数倍(通常 4KB)。
    • 权限(Permissions):类似文件权限,包括读(R)、写(W)、创建(C)等标志。
    • ID:成功创建后返回的唯一标识符(int shmid),用于后续操作。
  • 生命周期:共享内存段在创建后存在于内核,直到显式删除或系统重启。进程附加后,可以随时分离,但段本身不会自动销毁。

共享内存适用于高性能场景,如多进程数据库、实时系统,但需配合信号量(semaphore)或锁机制避免竞态条件。

2. 核心 API 函数

System V SHM 主要由四个函数组成:shmget(创建/获取)、shmat(附加)、shmdt(分离)、shmctl(控制)。这些函数定义在 <sys/shm.h> 头文件中。

2.1 shmget - 创建或获取共享内存段
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);

2.2 shmat - 附加共享内存到进程地址空间
#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);

  • 参数
    • key:键值。常用 IPC_PRIVATE(0)表示私有段,或用 ftok 生成(如 ftok("/tmp", 'a'))。
    • size:段大小(字节)。若 key 已存在,则忽略此参数。
    • shmflg:标志位,包括:
      • IPC_CREAT:如果键不存在,则创建新段。
      • IPC_EXCL:与 IPC_CREAT 结合使用,若已存在则失败。
      • 权限位:如 0666(读写权限)。
  • 返回值:成功返回 shmid(≥0),失败返回 -1(errno 如 ENOSPC 表示空间不足)。
  • 示例:创建 1024 字节段:int shmid = shmget(IPC_PRIVATE, 1024, IPC_CREAT | 0666);
  • 参数
    • shmid:由 shmget 返回的 ID。
    • shmaddr:附加地址。NULL 表示让内核自动选择。
    • shmflg:标志,如 SHM_RDONLY(只读附加)。
  • 返回值:成功返回附加后的指针(void*),失败返回 (void*)-1。
  • 注意:附加后,指针指向共享内存,可像普通数组一样读写。多个进程附加同一段时,修改会立即可见。
2.3 shmdt - 从进程地址空间分离共享内存
#include <sys/shm.h>

int shmdt(const void *shmaddr);
  • 参数:shmaddr:由 shmat 返回的指针。
  • 返回值:成功返回 0,失败返回 -1。
  • 注意:分离不删除段本身,仅断开当前进程的映射。所有进程分离后,段仍存在直到 shmctl 删除。
2.4 shmctl - 控制共享内存段
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 参数
    • shmid:共享内存 ID。
    • cmd:命令,如:
      • IPC_STAT:获取段信息到 buf。
      • IPC_SET:设置段权限/所有者。
      • IPC_RMID:标记段删除(最后分离时真正销毁)。
    • buf:struct shmid_ds 结构体,包含段统计信息(如大小、附加进程数)。
  • 返回值:成功 0,失败 -1。
  • 示例:删除段:shmctl(shmid, IPC_RMID, NULL);
3. 完整示例:父子进程共享内存通信

以下是一个 C 语言示例:父进程创建共享内存,写入数据;子进程附加、读取、修改后分离。编译运行:gcc example.c -o example && ./example。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/wait.h>

int main() {
    int shmid;
    char *shm_ptr;
    pid_t pid;

    // 创建共享内存:1024 字节
    shmid = shmget(IPC_PRIVATE, 1024, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(1);
    }

    // 父进程附加并写入数据
    shm_ptr = shmat(shmid, NULL, 0);
    if (shm_ptr == (char*)-1) {
        perror("shmat");
        exit(1);
    }
    sprintf(shm_ptr, "Hello from parent!");

    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(1);
    }

    if (pid == 0) {  // 子进程
        // 子进程附加(自动获取同一段)
        char *child_ptr = shmat(shmid, NULL, 0);
        if (child_ptr == (char*)-1) {
            perror("shmat child");
            exit(1);
        }
        printf("Child reads: %s\n", child_ptr);
        sprintf(child_ptr, "Modified by child!");
        shmdt(child_ptr);  // 子进程分离
        exit(0);
    } else {  // 父进程
        wait(NULL);  // 等待子进程
        printf("Parent reads after child: %s\n", shm_ptr);
        shmdt(shm_ptr);  // 父进程分离
        shmctl(shmid, IPC_RMID, NULL);  // 删除段
    }

    return 0;
}

运行输出

text

Child reads: Hello from parent!
Parent reads after child: Modified by child!
  • 解释:父进程创建段并写入,子进程读取并修改,父进程最后读取修改结果。使用 IPC_PRIVATE 确保私有段。
. 高级主题
  • 键值生成:用 ftok(<sys/ipc.h>)基于路径和 proj_id 生成唯一键:key_t key = ftok("/tmp/shmfile", 65);。路径需存在。
  • 权限与安全:权限位类似 open 系统调用。段所有者默认为创建进程 UID。跨用户访问需设置 IPC_SET。
  • 限制与查询:用 shmctl 的 IPC_STAT 查看段信息。系统限制(如最大段数)在 /proc/sys/kernel/shm* 可调。
  • 同步问题:SHM 无内置锁,需结合信号量(semget)或互斥锁(pthread_mutex)。示例:用信号量保护读写。
  • 错误处理:常见 errno:
    • EINVAL:无效参数(如 size=0)。
    • EACCES:权限不足。
    • ENOENT:键不存在且无 IPC_CREAT。
    • Unix/Linux 文件权限的数字表示详解

      在 Unix/Linux 系统(如 Linux、macOS)中,文件和目录的权限常用八进制数字表示(通常是 3 位数字,如 644)。这个数字表示**所有者(owner)、组(group)和其他人(others)各自的权限组合。每个组的权限由读(r)、写(w)、执行(x)**三项相加计算:

    • 读(r) = 4
    • 写(w) = 2
    • 执行(x) = 1
    • 无权限 = 0
常见文件读写权限的数字表示

以下是文件(非目录)常见的读写权限组合。注意:文件默认无执行权限,除非是脚本/可执行文件。

权限描述(符号表示)数字表示解释(所有者/组/其他)典型用途
rw-r--r--644rw- / r-- / r--普通文件:所有者读写,其他只读(默认文件权限)
rw-rw-r--664rw- / rw- / r--组内协作文件:所有者和组读写,其他只读
rw-rw-rw-666rw- / rw- / rw-所有人读写(共享内存 SHM 常用)
rw-------600rw- / --- / ---私有文件:仅所有者读写
r--r--r--444r-- / r-- / r--只读文件:所有人只读,无写

mmap 进程间通信详解

mmap(memory map)是 POSIX 标准的一种系统调用,用于将文件或其他对象映射到进程的虚拟地址空间中,从而实现高效的进程间通信(IPC)。它常用于共享内存场景,与 System V SHM 类似,但更灵活、可基于文件持久化数据。mmap 不限于文件,还支持匿名映射(anonymous mapping),适用于纯内存共享。

相比 SHM,mmap 更现代、跨平台(Linux/Unix/macOS),且易于与文件 I/O 结合。但它也需手动处理同步(如使用 msync 同步到磁盘)。本文聚焦进程间通信用法,从概念、API、示例、优缺点等方面详解。

1. mmap 的基本概念
  • 原理:mmap 将文件(或匿名内存)映射到进程的地址空间,形成共享视图。多个进程映射同一文件时,修改会立即可见(若使用 MAP_SHARED 标志)。内核通过页表管理物理页的共享,避免数据复制。
  • 关键元素
    • 文件描述符(fd):通过 open 获取文件句柄。若匿名映射,fd = -1。
    • 映射大小(length):映射的字节数,通常为文件大小(用 fstat 获取)。
    • 标志(flags)
      • MAP_SHARED:修改反映到所有进程和底层文件(IPC 核心)。
      • MAP_PRIVATE:私有复制(写时复制,COW),不共享。
      • MAP_ANONYMOUS:匿名映射,无底层文件,纯内存共享。
    • 偏移(offset):文件偏移,从何处开始映射(通常 0)。
    • 保护(prot):如 PROT_READ | PROT_WRITE(读写)。
  • 生命周期:映射在 munmap 或进程退出时解除。文件需手动关闭,但映射独立。
  • 适用场景:多进程共享大文件(如数据库页)、实时数据交换。需配合锁(如 flock 或信号量)避免竞态。

匿名 mmap 是 SHM 的现代替代:无需键值,直接 fork 后子进程继承映射。

2. 核心 API 函数

mmap 系列定义在 <sys/mman.h> 头文件中。主要函数:open(可选)、mmap(映射)、munmap(解除)、msync(同步)、close(关闭文件)。

2.1 open - 打开文件(文件映射时用)

c

#include <fcntl.h>
#include <sys/stat.h>

int open(const char *pathname, int flags, mode_t mode);
  • 参数:pathname 文件路径;flags 如 O_RDWR | O_CREAT;mode 如 0666。
  • 返回值:文件描述符(fd ≥0),失败 -1。
  • 注意:匿名映射无需此步。
2.2 mmap - 映射内存

c

#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。
    • flags:如 MAP_SHARED | MAP_ANONYMOUS。
    • fd:文件 fd(匿名时 -1)。
    • offset:偏移(页对齐)。
  • 返回值:成功返回映射指针(void*),失败 (void*)-1(errno 如 ENOMEM)。
  • 示例:匿名 1024 字节映射:void *ptr = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
2.3 munmap - 解除映射

c

#include <sys/mman.h>

int munmap(void *addr, size_t length);
  • 参数:addr 和 length 与 mmap 对应。
  • 返回值:成功 0,失败 -1。
  • 注意:进程退出自动 munmap,但显式调用更安全。
2.4 msync - 同步映射到文件(可选)

c

#include <sys/mman.h>

int msync(void *addr, size_t length, int flags);
  • 参数:flags 如 MS_SYNC(阻塞同步)或 MS_ASYNC(异步)。
  • 用途:确保修改写入磁盘,防止数据丢失。
3. 完整示例:父子进程使用 mmap 通信

以下 C 示例:父进程创建匿名 mmap 区域,写入数据;fork 子进程,子进程读取、修改;父进程最后读取。编译:gcc example.c -o example && ./example。

c

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

int main() {
    size_t size = 1024;
    void *shm_ptr;

    // 创建匿名共享映射:1024 字节
    shm_ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (shm_ptr == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }

    // 父进程写入数据
    strcpy((char *)shm_ptr, "Hello from parent!");

    // 创建子进程
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(1);
    }

    if (pid == 0) {  // 子进程
        // 子进程直接访问同一映射(继承自父进程)
        printf("Child reads: %s\n", (char *)shm_ptr);
        strcpy((char *)shm_ptr, "Modified by child!");
        munmap(shm_ptr, size);  // 子进程解除
        exit(0);
    } else {  // 父进程
        wait(NULL);  // 等待子进程
        printf("Parent reads after child: %s\n", (char *)shm_ptr);
        munmap(shm_ptr, size);  // 父进程解除
    }

    return 0;
}

运行输出

text

Child reads: Hello from parent!
Parent reads after child: Modified by child!
  • 解释:fork 后,子进程继承父进程的 mmap 视图,实现零复制共享。匿名标志避免文件 I/O。
4. 文件映射示例(持久化)

若需持久化,使用文件:

c

int fd = open("shared.dat", O_RDWR | O_CREAT | O_TRUNC, 0666);
ftruncate(fd, 1024);  // 设置文件大小
void *ptr = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// ... 使用后
msync(ptr, 1024, MS_SYNC);
munmap(ptr, 1024);
close(fd);

多个进程打开同一文件,即可共享。

5. 高级主题
  • 匿名 vs 文件映射:匿名(MAP_ANONYMOUS)适合临时共享;文件映射支持持久化和大文件(> 虚拟地址限)。
  • 同步:用 pthread_mutex(初始化在共享内存)或文件锁(fcntl)。示例:mutex 在 mmap 首部。
  • 页对齐:length/offset 需页对齐(getpagesize() 获取,通常 4KB)。
  • 错误处理:常见 errno:EACCES(权限)、ENOMEM(内存不足)、EINVAL(无效参数)。
  • 限制:虚拟地址空间限(ulimit -v);大映射需考虑 TLB 缓存。
6. 优缺点
方面优点缺点
性能零复制、懒加载(按需页入),高效需手动同步,易页错误(SIGSEGV)
使用POSIX 标准,易与文件结合地址不固定(ASLR),调试难
适用跨进程/线程、大文件共享不支持跨机器,文件版需磁盘 I/O
兼容Linux/Unix/macOS/Windows(模拟)老系统可能不支持匿名共享
7. 与 SHM 的比较
方面mmapSystem V SHM
创建mmap(匿名或文件)shmget(键值)
持久化支持文件映射纯内存,无持久
API 复杂简单,集成文件 I/O需额外 shmat/shmdt
移植性高(POSIX)Unix 特定
同步手动(msync/锁)手动(信号量)
推荐现代应用、文件共享传统高性能 IPC

mmap 是 SHM 的升级版,尤其适合文件-backed 场景。如果需要线程内通信(pthread)、POSIX SHM(shm_open)示例,或特定同步代码,欢迎补充细节!参考:man mmap。

mmap 创建的文件路径及与 SHM 的区别详解

针对你的问题,我将分步解释 mmap 创建的“文件”路径(其实是底层文件描述符),以及为什么 System V SHM 不是基于文件的通信机制。最后用表格总结二者区别。基于 POSIX/Linux 标准,这些概念在用户空间实现中通用。

1. mmap 创建的文件在哪个路径下?
  • 文件映射(File-backed mmap)
    • mmap 本身不“创建”文件,而是将现有文件映射到内存。如果你想通过 mmap 实现共享,首先需用 open() 创建或打开一个文件作为底层存储。
    • 路径:文件路径完全由 open() 的 pathname 参数决定,通常是用户指定的任意路径。例如:
      • /tmp/shared.dat:临时文件,常用于测试。
      • /var/run/myapp/shm_file:持久化目录。
      • 当前工作目录:如 ./data.bin。
    • 示例代码:

      c

      int fd = open("/tmp/mmap_shared.dat", O_RDWR | O_CREAT | O_TRUNC, 0666);  // 路径:/tmp/mmap_shared.dat
      ftruncate(fd, 1024);  // 设置文件大小
      void *ptr = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
      • 这里,文件实际创建在 /tmp/ 目录下(需有写权限)。进程退出后,文件仍存在于磁盘,除非手动 unlink(fd) 删除。
    • 注意:文件大小需预设(ftruncate),否则映射失败。修改映射内存会同步到文件(用 msync 强制)。
  • 匿名映射(Anonymous mmap)
    • 使用 MAP_ANONYMOUS 标志时,无底层文件(fd = -1),纯内存区域,由内核分配虚拟页。
    • 路径:无路径!它不依赖文件系统,直接在进程地址空间中创建共享内存。fork 后的子进程继承视图,实现 IPC。
    • 示例:

      c

      void *ptr = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);  // 无文件路径
    • 适用:临时共享,无需持久化。
  • 查询路径:用 ls /tmp/ 或 lsof -p <PID> 查看打开的文件描述符。匿名无文件可见。
2. SHM 不也是基于文件通信的吗?
  • 不是!System V SHM(shmget 等)是纯内核内存管理,不涉及文件系统:
    • SHM 段是内核 IPC 资源,通过**键值(key)**标识(如 ftok 生成),存储在内核的共享内存表中(用 ipcs -m 查看)。
    • 无物理文件:数据仅在 RAM 中,进程附加(shmat)后直接访问。进程退出或 shmctl(IPC_RMID) 后,段被释放(可能延迟直到最后附加进程分离)。
    • “基于文件”的误解:有时用 ftok("/path/to/file", proj_id) 生成键值,但文件仅用于生成唯一 ID(路径需存在,但文件内容无关)。实际 SHM 不读写该文件。
    • 示例:key_t key = ftok("/tmp/keyfile", 65); int shmid = shmget(key, 1024, IPC_CREAT | 0666); —— /tmp/keyfile 只用于键生成,无通信作用。
3. mmap 与 SHM 的区别

二者都用于进程间共享内存,但 mmap 更灵活(文件/匿名),SHM 更传统(纯 IPC)。以下表格对比:

方面mmap (文件映射)mmap (匿名)System V SHM
底层存储基于磁盘文件(持久化)纯内存(无文件)纯内核内存(无文件)
路径/标识open() 的 pathname(如 /tmp/xx)无路径(fd=-1)键值(key,如 ftok 生成)
创建方式open + ftruncate + mmapmmap(MAP_ANONYMOUS)shmget(key, size, flags)
持久化是(文件存盘,msync 同步)否(进程退出丢失)否(内核段,系统重启丢失)
IPC 实现多个进程 open 同一文件 + MAP_SHAREDfork 继承,或多进程 mmap 同一匿名多进程用同一 key + shmat
大小限制文件系统限(大文件 OK)虚拟内存限系统 SHMMAX/SHMALL(/proc/sys/kernel/shm*)
清理unlink(fd) 删除文件;munmapmunmap(自动)shmctl(IPC_RMID);shmdt
优点持久、易备份、跨重启简单、高效、无文件开销传统、键值命名、易多进程访问
缺点磁盘 I/O 慢、需文件权限无持久、地址不固定无持久、需手动删段(易泄漏)
适用数据库页、配置文件共享临时多进程数据交换高性能 IPC(如消息队列后端)
  • 总结区别:mmap 是“内存映射”通用工具,可模拟文件 I/O 或纯 SHM;SHM 是专为 IPC 设计的内核抽象,无文件介入。匿名 mmap 几乎等价于 SHM,但更 POSIX 兼容(无键值)。在现代代码中,优先 mmap(尤其是匿名),除非遗留系统需 SHM。

如果需要代码示例(如 mmap 文件版 vs SHM)、特定平台(如 ARM)细节,或调试工具(如 ipcs 输出),随时补充!参考:man mmap、man shm_overview。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值