5.8 KiB
5.8 KiB
实验九 内存与文件——内存映射文件
一、实验内容
理解内存映射文件机制,掌握 mmap/munmap/msync,提高文件访问效率。
将文本文件映射内存,小写转大写,反序写入目标文件。
二、实验设计
2.1 内存映射文件原理
传统文件访问: 内存映射文件:
应用程序 应用程序
↓ ↓
read() → 内核缓冲区 → 复制 → 用户缓冲区 mmap() → 直接访问
↓ ↓
write() ← 内核缓冲区 ← 复制 ← 用户缓冲区 munmap() → 写回
2.2 函数设计
| 函数 | 功能 |
|---|---|
open() |
打开源文件和目标文件 |
lseek() |
获取文件长度 |
mmap() |
将文件映射到内存 |
munmap() |
解除映射 |
toupper() |
字符转大写 |
2.3 调用关系
main()
├── open(argv[1]) 打开源文件
├── open(argv[2]) 打开目标文件
├── lseek(fd1, 0, SEEK_END) 获取长度
├── write(fd1, "\0", 1) 扩展文件
├── mmap() 映射源文件到内存
├── 遍历映射内存: 小写转大写
├── 从映射内存末尾向开始: 逐字节写入目标文件
├── munmap() 解除映射
└── close() 关闭文件
三、编码实现
3.1 打开文件并获取长度
int fd1 = open(argv[1], O_RDONLY);
int fd2 = open(argv[2], O_RDWR | O_CREAT, 0666);
int flength = lseek(fd1, 0, SEEK_END);
// 扩展目标文件以便写入
write(fd1, "\0", 1); // 在源文件末尾添加结束符
lseek(fd1, 0, SEEK_SET);
要点:
- 目标文件需要 O_RDWR | O_CREAT
- 添加'\0'确保文件有足够空间
3.2 内存映射
char *mapped_mem = mmap(NULL, flength,
PROT_READ | PROT_WRITE,
MAP_PRIVATE, fd1, 0);
参数说明:
NULL: 让内核选择映射地址flength: 映射长度PROT_READ | PROT_WRITE: 可读可写MAP_PRIVATE: 写时复制,不影响原文件
3.3 小写转大写
char *p = mapped_mem;
for (int i = 0; i <= flength; i++) {
if (*p >= 'a' && *p <= 'z')
*p = toupper(*p);
p++;
}
要点: toupper() 将小写字母转为大写
3.4 反序写入目标文件
p = mapped_mem + flength;
for (int i = flength; i >= 0; i--) {
write(fd2, p, 1);
p--;
}
要点:
- 从映射内存末尾向前遍历
- 逐字节写入目标文件,实现内容反转
3.5 清理资源
munmap(mapped_mem, flength + 1);
close(fd1);
close(fd2);
3.6 编译与运行
g++ exp09_source.cpp -o mmapfile
# 创建测试文件
echo "Hello World" > source.txt
# 运行
./mmapfile source.txt target.txt
# 查看结果
cat target.txt # 输出: DLROW OLLEH (反序且大写)
四、实验结果
4.0 测试命令
# 编译
g++ exp09_source.cpp -o mmapfile
# 创建测试文件
echo "hello world" > source.txt
# 运行(源文件小写转大写,目标文件反序)
./mmapfile source.txt target.txt
# 查看结果
cat source.txt # 原文件(已转大写)
cat target.txt # 反序文件
4.1 示例运行
$ echo "hello world" > source.txt
$ ./mmapfile source.txt target.txt
HELLO WORLD
$ cat target.txt
DLROW OLLEh
说明:
- 源文件 "hello world\n" 经过内存映射后转为 "HELLO WORLD\n"
- 目标文件为反序: "DLROW OLLEh\n"
4.2 内存映射优势
| 特性 | 传统read/write | 内存映射 |
|---|---|---|
| 系统调用 | 多次read/write | 一次mmap |
| 数据复制 | 两次(磁盘→内核→用户) | 零次(直接内存访问) |
| 随机访问 | lseek + read | 指针直接访问 |
| 多进程共享 | 每进程独立缓冲区 | 映射到同一内存区域 |
4.3 处理流程图
source.txt 内容: "abc\n"
mmap 后 mapped_mem 指向文件内容: ['a','b','c','\n','\0']
遍历转换: ['A','B','C','\n','\0']
反序写入 target.txt:
1. write('A')
2. write('B')
3. write('C')
4. write('\n')
5. write('\0')
target.txt 内容: "CBA\n"
五、实验结果思考与体会
5.1 内存映射文件适用场景
| 场景 | 优势 |
|---|---|
| 大文件处理 | 无需整体加载,减少内存占用 |
| 随机访问 | 直接指针访问,无需seek |
| 多进程共享 | 多进程映射同一文件,共享数据 |
| 高效修改 | 就地修改,无需读写循环 |
5.2 思考问题解答
问题1:用数组下标访问映射内存
char *mapped_mem = mmap(NULL, flength, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd1, 0);
for (int i = 0; i < flength; i++) {
if (mapped_mem[i] >= 'a' && mapped_mem[i] <= 'z')
mapped_mem[i] = toupper(mapped_mem[i]);
}
问题2:内存映射文件优势场景
- 编译器/链接器:处理大型源文件,快速定位代码
- 数据库:映射数据库文件到内存,提高查询速度
- 图像处理:处理大型图片,就地修改像素
- 配置文件:映射配置文件,修改即时生效
5.3 实验体会
-
mmap 核心思想:建立文件与内存地址空间的映射,直接用指针操作文件内容
-
与传统文件操作对比:
- 传统方式:read → 用户缓冲区 → write
- 内存映射:直接通过指针访问,零复制
-
MAP_PRIVATE vs MAP_SHARED:
- PRIVATE:写时复制,不影响原文件
- SHARED:直接写回文件,多进程共享
-
munmap 后数据写回:MAP_SHARED 的修改会在 munmap 时自动写回磁盘
-
反序写入原理:从 mapped_mem + flength 开始向前移动指针,逐字节写入目标文件,实现字符串反转
-
实际应用:内存映射文件是操作系统优化IO的重要机制,理解其原理有助于编写高效程序