Linux系统编程:使用 mmap 实现无血缘关系进程间通信

80T资源合集下载
链接:https://pan.quark.cn/s/5643428d4f9f

在Linux系统编程中,进程间通信(IPC)是一个核心话题。虽然管道(Pipe/FIFO)非常常用,但它存在数据“读后即焚”且只能顺序读取的局限性。今天我们来深入探讨一种更高效、更灵活的通信方式——存储映射I/O(Memory-mapped I/O),即
mmap

本文将重点介绍如何利用
mmap
在两个无血缘关系的进程之间,通过共享文件映射区传输结构体数据。

一、 核心概念与优势


mmap
通过将一个磁盘文件映射到内存中,使得进程可以像访问内存一样直接操作文件。

相比于管道(FIFO),mmap 的主要优势在于:

数据持久性:映射区的数据对应物理文件,除非被覆盖,否则会一直保留(管道数据读完就消失)。重复读取:多个读进程可以同时、重复地读取同一份数据。高效:直接操作内存,减少了内核空间与用户空间之间的数据拷贝(零拷贝)。跨越血缘:只要能打开同一个文件,任意进程间均可通信。

二、 场景设计:学生信息同步

为了演示通信过程,我们设计一个简单的场景:

数据结构:定义一个
student
结构体,包含 ID、姓名和年龄。写进程(Server):创建一个文件并建立映射区,每隔2秒修改结构体中的 ID 值并写入映射区。读进程(Client):打开同一个文件建立映射区,持续读取并打印结构体内容。

1. 公共头文件与结构体

首先,我们需要统一通信双方的数据协议。


// student.h
#ifndef STUDENT_H
#define STUDENT_H

typedef struct {
    int id;
    char name[256];
    int age;
} student;

#endif

三、 代码实现

1. 写进程 (mmap_w.c)

写进程负责创建文件、设置文件大小,并初始化数据。

关键点解析:


open
:使用
O_RDWR|O_CREAT|O_TRUNC
,确保文件存在且被清空。
ftruncate
非常重要。新创建的文件大小为0,直接映射会导致总线错误(Bus Error)。必须扩展文件大小至结构体大小。
mmap
:必须使用
MAP_SHARED
,否则数据修改只在当前进程可见,无法同步到物理文件或其他进程。


/* mmap_w.c - 写进程 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "student.h"

int main() {
    // 1. 打开文件,如果不存在则创建,若存在则截断为0
    int fd = open("test_map", O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open error");
        exit(1);
    }

    // 2. 扩展文件大小(物理文件大小必须足够容纳结构体)
    ftruncate(fd, sizeof(student));

    // 3. 创建映射区
    // 参数:NULL(系统自动分配地址), 长度, 读写权限, 共享模式, 文件描述符, 偏移量
    student *p = mmap(NULL, sizeof(student), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED) {
        perror("mmap error");
        exit(1);
    }

    // 4. 映射建立成功后,文件描述符可以关闭(可选)
    close(fd);

    // 5. 初始化数据
    student stu = {1, "xiaoming", 16};

    // 6. 循环修改共享内存中的数据
    while (1) {
        memcpy(p, &stu, sizeof(stu)); // 将数据写入映射区
        printf("Writer: id=%d, name=%s, age=%d
", stu.id, stu.name, stu.age);
        
        stu.id++; // 模拟数据更新
        sleep(2); // 写频率控制
    }

    // 7. 释放映射区(实际运行中while(1)无法到达此处,仅作完整性演示)
    munmap(p, sizeof(student));

    return 0;
}

2. 读进程 (mmap_r.c)

读进程不需要创建文件,只需要打开现有的文件进行映射。

关键点解析:


open
:可以使用
O_RDONLY
,但在某些系统中为了兼容性或如果需要写回,可能需要
O_RDWR
。本例中我们只读。
mmap
:权限需与
open
对应。如果
open

O_RDONLY
,这里只能是
PROT_READ

unlink
:在进程结束时删除文件(可选),但在多进程通信中通常保留文件以便其他进程连接。


/* mmap_r.c - 读进程 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "student.h"

int main() {
    // 1. 打开同一个文件
    int fd = open("test_map", O_RDONLY);
    if (fd == -1) {
        perror("open error");
        exit(1);
    }

    // 2. 创建映射区 (注意:这里只需要读权限)
    student *p = mmap(NULL, sizeof(student), PROT_READ, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED) {
        perror("mmap error");
        exit(1);
    }

    // 3. 关闭文件描述符
    close(fd);

    // 4. 循环读取数据
    while (1) {
        printf("Reader: id=%d, name=%s, age=%d
", p->id, p->name, p->age);
        // 稍微休眠一下,降低CPU占用,同时观察数据的变化
        usleep(500000); // 0.5秒
    }

    munmap(p, sizeof(student));

    return 0;
}

四、 编译与运行结果

1. 编译代码

确保目录下有
student.h
,
mmap_w.c
,
mmap_r.c


gcc mmap_w.c -o mmap_w
gcc mmap_r.c -o mmap_r

2. 运行演示

你需要打开两个终端窗口。

终端 1 (运行写进程):


./mmap_w

输出结果:


Writer: id=1, name=xiaoming, age=16
Writer: id=2, name=xiaoming, age=16
Writer: id=3, name=xiaoming, age=16
...

终端 2 (运行读进程):
注意:你可以在写进程运行后的任意时间启动读进程,甚至可以启动多个读进程。


./mmap_r

输出结果:


Reader: id=1, name=xiaoming, age=16
Reader: id=1, name=xiaoming, age=16
Reader: id=1, name=xiaoming, age=16
Reader: id=1, name=xiaoming, age=16
Reader: id=2, name=xiaoming, age=16  <-- 数据随写进程更新
Reader: id=2, name=xiaoming, age=16
...

3. 结果分析

从结果可以看出,虽然两个进程没有父子关系,但通过映射同一个文件
test_map
,读进程能够实时获取写进程更新的数据。且读进程的读取频率高于写进程(0.5s vs 2s),可以看到读进程会多次读取到相同的 ID,证明了 mmap 的数据是非破坏性读取(不同于 FIFO 的读走即消)。

五、 开发避坑指南 (Summary)

在实际开发中,使用 mmap 需要注意以下几点:

MAP_SHARED 是必须的

如果要实现进程间通信,必须指定
MAP_SHARED
。如果你使用了
MAP_PRIVATE
,对内存的修改会触发“写时复制”(Copy-on-Write),修改仅在当前进程内存中有效,不会写回物理文件,其他进程也就无法看到变化。

文件大小不能为0


open
创建新文件后,文件长度为0。直接映射会失败或导致崩溃。必须调用
ftruncate(fd, size)

lseek
配合
write
来扩展文件物理大小。

权限匹配


mmap
的权限(
PROT_READ
等)不能超过
open
时的权限。写进程如果需要
PROT_READ | PROT_WRITE
,则
open
必须是
O_RDWR

头文件

不要忘记包含
<sys/mman.h>

<fcntl.h>
,否则会报
PROT_READ
未定义或
MAP_FAILED
错误。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
成陌的头像 - 鹿快
评论 抢沙发

请登录后发表评论

    暂无评论内容