# 实验九 内存与文件——内存映射文件 ## 一、实验内容 理解内存映射文件机制,掌握 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 打开文件并获取长度 ```cpp 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 内存映射 ```cpp char *mapped_mem = mmap(NULL, flength, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd1, 0); ``` **参数说明:** - `NULL`: 让内核选择映射地址 - `flength`: 映射长度 - `PROT_READ | PROT_WRITE`: 可读可写 - `MAP_PRIVATE`: 写时复制,不影响原文件 ### 3.3 小写转大写 ```cpp char *p = mapped_mem; for (int i = 0; i <= flength; i++) { if (*p >= 'a' && *p <= 'z') *p = toupper(*p); p++; } ``` **要点:** `toupper()` 将小写字母转为大写 ### 3.4 反序写入目标文件 ```cpp p = mapped_mem + flength; for (int i = flength; i >= 0; i--) { write(fd2, p, 1); p--; } ``` **要点:** - 从映射内存末尾向前遍历 - 逐字节写入目标文件,实现内容反转 ### 3.5 清理资源 ```cpp munmap(mapped_mem, flength + 1); close(fd1); close(fd2); ``` ### 3.6 编译与运行 ```bash g++ exp09_source.cpp -o mmapfile # 创建测试文件 echo "Hello World" > source.txt # 运行 ./mmapfile source.txt target.txt # 查看结果 cat target.txt # 输出: DLROW OLLEH (反序且大写) ``` ## 四、实验结果 ### 4.0 测试命令 ```bash # 编译 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:用数组下标访问映射内存** ```cpp 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:内存映射文件优势场景** 1. **编译器/链接器**:处理大型源文件,快速定位代码 2. **数据库**:映射数据库文件到内存,提高查询速度 3. **图像处理**:处理大型图片,就地修改像素 4. **配置文件**:映射配置文件,修改即时生效 ### 5.3 实验体会 1. **mmap 核心思想**:建立文件与内存地址空间的映射,直接用指针操作文件内容 2. **与传统文件操作对比**: - 传统方式:read → 用户缓冲区 → write - 内存映射:直接通过指针访问,零复制 3. **MAP_PRIVATE vs MAP_SHARED**: - PRIVATE:写时复制,不影响原文件 - SHARED:直接写回文件,多进程共享 4. **munmap 后数据写回**:MAP_SHARED 的修改会在 munmap 时自动写回磁盘 5. **反序写入原理**:从 mapped_mem + flength 开始向前移动指针,逐字节写入目标文件,实现字符串反转 6. **实际应用**:内存映射文件是操作系统优化IO的重要机制,理解其原理有助于编写高效程序