80T资源合集下载
链接:https://pan.quark.cn/s/5643428d4f9f
在Linux系统编程中,进程间通信(IPC)是一个核心话题。虽然管道(Pipe/FIFO)非常常用,但它存在数据“读后即焚”且只能顺序读取的局限性。今天我们来深入探讨一种更高效、更灵活的通信方式——存储映射I/O(Memory-mapped I/O),即 。
mmap
本文将重点介绍如何利用 在两个无血缘关系的进程之间,通过共享文件映射区传输结构体数据。
mmap
一、 核心概念与优势
通过将一个磁盘文件映射到内存中,使得进程可以像访问内存一样直接操作文件。
mmap
相比于管道(FIFO),mmap 的主要优势在于:
数据持久性:映射区的数据对应物理文件,除非被覆盖,否则会一直保留(管道数据读完就消失)。重复读取:多个读进程可以同时、重复地读取同一份数据。高效:直接操作内存,减少了内核空间与用户空间之间的数据拷贝(零拷贝)。跨越血缘:只要能打开同一个文件,任意进程间均可通信。
二、 场景设计:学生信息同步
为了演示通信过程,我们设计一个简单的场景:
数据结构:定义一个 结构体,包含 ID、姓名和年龄。写进程(Server):创建一个文件并建立映射区,每隔2秒修改结构体中的 ID 值并写入映射区。读进程(Client):打开同一个文件建立映射区,持续读取并打印结构体内容。
student
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:非常重要。新创建的文件大小为0,直接映射会导致总线错误(Bus Error)。必须扩展文件大小至结构体大小。
ftruncate:必须使用
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. 结果分析
从结果可以看出,虽然两个进程没有父子关系,但通过映射同一个文件 ,读进程能够实时获取写进程更新的数据。且读进程的读取频率高于写进程(0.5s vs 2s),可以看到读进程会多次读取到相同的 ID,证明了 mmap 的数据是非破坏性读取(不同于 FIFO 的读走即消)。
test_map
五、 开发避坑指南 (Summary)
在实际开发中,使用 mmap 需要注意以下几点:
MAP_SHARED 是必须的:
如果要实现进程间通信,必须指定 。如果你使用了
MAP_SHARED,对内存的修改会触发“写时复制”(Copy-on-Write),修改仅在当前进程内存中有效,不会写回物理文件,其他进程也就无法看到变化。
MAP_PRIVATE
文件大小不能为0:
创建新文件后,文件长度为0。直接映射会失败或导致崩溃。必须调用
open 或
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






![[C++探索之旅] 第一部分第十一课:小练习,猜单词 - 鹿快](https://img.lukuai.com/blogimg/20251015/da217e2245754101b3d2ef80869e9de2.jpg)










暂无评论内容