当前位置: 首页 > news >正文

`munmap`系统调用及示例

这次我们介绍 munmap 函数,它是 mmap 的“搭档”,用于释放之前通过 mmap 创建的内存映射区域。


1. 函数介绍

munmap 是一个 Linux 系统调用,其主要功能是解除(或取消映射)之前通过 mmap 系统调用在进程地址空间中创建的内存映射区域。调用 munmap 会通知内核,进程不再需要访问由 mmap 建立的从 addr 开始、长度为 length 的那块虚拟内存区域。

你可以把它想象成从墙上撕下之前贴上的书页(由 mmap 贴上)。撕下来后,这块“墙上的区域”(进程的地址空间)就空出来了,可以被系统重新分配给其他用途,同时与之关联的文件(如果有的话)也不再通过这块内存访问。


2. 函数原型

#include <sys/mman.h> // 必需int munmap(void *addr, size_t length);

3. 功能

  • 解除映射: 告诉内核解除从地址 addr 开始、长度为 length 字节的内存区域的映射关系。
  • 释放资源: 内核会释放与该映射区域相关的内核数据结构,并可能将该区域的虚拟地址空间标记为未使用。
  • 数据同步: 对于 MAP_SHARED 映射,内核可能会将映射区域中被修改的页面刷新回底层文件(虽然不保证立即完成,msync 可以强制同步)。
  • 内存回收: 进程不再能通过 addr 指针访问这块内存。如果这块内存是通过 mmap 映射文件得到的,那么对该区域的访问将导致段错误(Segmentation fault)。

4. 参数

  • void *addr: 这是之前调用 mmap 成功返回的地址。它标识了要解除映射的内存区域的起始地址。
    • 重要: addr 必须是 mmap 返回的确切地址。如果传递一个 mmap 返回的地址加上某个偏移量,行为是未定义的,通常会导致 munmap 失败 (EINVAL)。
  • size_t length: 这是要解除映射的内存区域的长度(以字节为单位)。
    • 重要: 这个 length 应该与当初调用 mmap 时指定的 length 相匹配,或者至少覆盖你想解除映射的部分。如果 length 为 0,munmap 调用无效。

5. 返回值

  • 成功时: 返回 0。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EINVAL addr 不是有效的映射地址或 length 为 0 且 addr 有效,ENOMEM 指定的地址范围不完全在已映射区域内等)。

重要提示: 必须检查 munmap 的返回值! 虽然很多示例代码忽略了,但 munmap 是可能失败的。忽略错误可能导致资源泄漏(虽然操作系统在进程退出时会清理所有映射,但在长时间运行的程序中,不清理可能导致问题)。


6. 相似函数,或关联函数

  • mmap: 与 munmap 相对应,用于创建内存映射。
  • msync: 在调用 munmap 之前,如果需要确保 MAP_SHARED 映射的修改已写入文件,可以先调用 msync
  • mprotect: 修改映射区域的保护属性,但这不涉及解除映射。
  • free: 用于释放通过 malloc 分配的堆内存,与 munmap 释放 mmap 的内存相对应(尽管 mmap 有时也被 malloc 内部用于分配大块内存)。

7. 示例代码

示例 1:基本的 mmapmunmap 使用

这个例子结合了 mmapmunmap,展示了它们的标准使用流程。

#include <sys/mman.h> // mmap, munmap
#include <sys/stat.h> // fstat
#include <fcntl.h>    // open, O_RDONLY
#include <unistd.h>   // close, fstat
#include <stdio.h>    // perror, printf, fprintf
#include <stdlib.h>   // exitint main(int argc, char *argv[]) {int fd;struct stat sb;char *mapped_memory;size_t file_length;if (argc != 2) {fprintf(stderr, "Usage: %s <filename>\n", argv[0]);exit(EXIT_FAILURE);}// 1. 打开文件fd = open(argv[1], O_RDONLY);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}// 2. 获取文件大小if (fstat(fd, &sb) == -1) {perror("fstat");close(fd);exit(EXIT_FAILURE);}file_length = sb.st_size;if (file_length == 0) {printf("File is empty.\n");close(fd);exit(EXIT_SUCCESS);}// 3. 创建内存映射mapped_memory = mmap(NULL, file_length, PROT_READ, MAP_PRIVATE, fd, 0);if (mapped_memory == MAP_FAILED) {perror("mmap");close(fd);exit(EXIT_FAILURE);}printf("File '%s' mapped successfully. Address: %p, Length: %zu bytes\n",argv[1], (void*)mapped_memory, file_length);// 4. 使用映射的内存 (这里只是简单打印第一个字符)printf("First character of the file: '%c'\n", mapped_memory[0]);// ... 这里可以进行更多对 mapped_memory 的读/写操作 ...// 5. 关键步骤:解除内存映射 - 释放资源if (munmap(mapped_memory, file_length) == -1) {// munmap 失败!这是一个需要处理的错误perror("CRITICAL ERROR: munmap failed");// 即使 munmap 失败,也应该尝试关闭文件close(fd);exit(EXIT_FAILURE); // 或根据应用逻辑决定如何处理}printf("Memory unmapped successfully.\n");// 6. 关闭文件描述符 (可以在 munmap 之前或之后)if (close(fd) == -1) {perror("close");exit(EXIT_FAILURE);}printf("File closed.\n");printf("Program finished successfully.\n");return 0;
}

代码解释:

  1. 打开文件并获取其大小。
  2. 调用 mmap 将整个文件映射到内存。
  3. 检查 mmap 是否成功。
  4. 使用映射的内存(示例中只打印了第一个字符)。
  5. 关键: 调用 munmap(mapped_memory, file_length) 来解除映射。这是释放 mmap 资源的关键步骤。
  6. 最重要: 检查 munmap 的返回值。如果返回 -1,打印错误信息并退出(或按应用逻辑处理)。
  7. 最后关闭文件描述符。
示例 2:处理 MAP_SHARED 映射的清理

这个例子强调在解除 MAP_SHARED 映射前使用 msync 确保数据写入。

#include <sys/mman.h> // mmap, munmap, msync
#include <sys/stat.h> // fstat
#include <fcntl.h>    // open
#include <unistd.h>   // close, fstat
#include <stdio.h>    // perror, printf, fprintf
#include <stdlib.h>   // exit
#include <string.h>   // memsetint main(int argc, char *argv[]) {int fd;struct stat sb;char *mapped_memory;size_t file_length = 4096; // 至少一页大小const char *data_to_write = "Data written by process using MAP_SHARED and msync.";if (argc != 2) {fprintf(stderr, "Usage: %s <filename>\n", argv[0]);exit(EXIT_FAILURE);}// 1. 打开或创建文件 (为了确保文件足够大,这里先创建/截断)fd = open(argv[1], O_RDWR | O_CREAT, 0644);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}// 确保文件至少有一页大小if (ftruncate(fd, file_length) == -1) {perror("ftruncate");close(fd);exit(EXIT_FAILURE);}// 2. 获取文件大小 (确认)if (fstat(fd, &sb) == -1) {perror("fstat");close(fd);exit(EXIT_FAILURE);}file_length = sb.st_size;printf("File size is %zu bytes.\n", file_length);// 3. 创建共享内存映射mapped_memory = mmap(NULL, file_length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (mapped_memory == MAP_FAILED) {perror("mmap");close(fd);exit(EXIT_FAILURE);}printf("File '%s' mapped for shared read/write. Address: %p\n", argv[1], (void*)mapped_memory);// 4. 修改映射的内存// 清零前几字节memset(mapped_memory, 0, 100);// 写入我们的数据size_t data_len = strlen(data_to_write);if (data_len < file_length) {for (size_t i = 0; i < data_len; ++i) {mapped_memory[i] = data_to_write[i];}// 或 memcpy(mapped_memory, data_to_write, data_len);}printf("Modified memory via mmap.\n");// 5. (重要) 强制同步修改到文件// 对于 MAP_SHARED 映射,这是一个好习惯,尤其是在 munmap 之前if (msync(mapped_memory, data_len, MS_SYNC) == -1) {perror("msync");// 即使 msync 失败,也尝试清理} else {printf("Changes synced to file using msync.\n");}// 6. 解除内存映射if (munmap(mapped_memory, file_length) == -1) {perror("munmap");close(fd);exit(EXIT_FAILURE);}printf("Memory unmapped.\n");// 7. 关闭文件描述符if (close(fd) == -1) {perror("close");exit(EXIT_FAILURE);}printf("File closed.\n");printf("Process finished. Check the file content.\n");return 0;
}

代码解释:

  1. 以读写模式打开或创建文件,并使用 ftruncate 确保文件至少有一页大小。
  2. 调用 mmap 创建 MAP_SHARED 映射。
  3. 修改映射的内存。
  4. 关键: 在 munmap 之前调用 msync(mapped_memory, data_len, MS_SYNC)。这确保了对共享映射区域的修改被强制写回到文件中。MS_SYNC 会等待写入操作完成。
  5. 调用 munmap 解除映射。
  6. 检查 munmapmsync 的返回值。
  7. 关闭文件描述符。
示例 3:错误处理和多次映射/解除映射

这个例子展示了在一个程序中进行多次 mmap/munmap 操作,并强调了正确的错误处理。

#include <sys/mman.h> // mmap, munmap
#include <sys/stat.h> // fstat
#include <fcntl.h>    // open
#include <unistd.h>   // close, fstat
#include <stdio.h>    // perror, printf, fprintf
#include <stdlib.h>   // exit// 函数:安全地映射文件的一部分
int safe_mmap_part(const char *filename, size_t offset, size_t length, char **mapped_ptr) {int fd = -1;struct stat sb;char *mapped = MAP_FAILED;fd = open(filename, O_RDONLY);if (fd == -1) {perror("open in safe_mmap_part");goto error;}if (fstat(fd, &sb) == -1) {perror("fstat in safe_mmap_part");goto error;}// 检查偏移和长度是否有效if (offset >= (size_t)sb.st_size || length == 0 || offset + length > (size_t)sb.st_size) {fprintf(stderr, "Invalid offset or length for file '%s'\n", filename);goto error;}// 确保 offset 是页对齐的 (虽然 mmap 通常能处理,但最好自己保证)long pagesize = sysconf(_SC_PAGESIZE);if (offset % pagesize != 0) {fprintf(stderr, "Offset %zu is not page-aligned (%ld)\n", offset, pagesize);goto error;}mapped = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, offset);if (mapped == MAP_FAILED) {perror("mmap in safe_mmap_part");goto error;}// 成功,设置输出参数*mapped_ptr = mapped;close(fd); // mmap 成功后可以关闭 fdreturn 0; // Successerror:if (mapped != MAP_FAILED) {munmap(mapped, length); // 尝试清理,尽管可能失败}if (fd != -1) {close(fd);}*mapped_ptr = MAP_FAILED;return -1; // Failure
}int main(int argc, char *argv[]) {char *map1 = MAP_FAILED, *map2 = MAP_FAILED;int result1, result2;if (argc != 2) {fprintf(stderr, "Usage: %s <filename>\n", argv[0]);exit(EXIT_FAILURE);}// 映射文件的前 1KBresult1 = safe_mmap_part(argv[1], 0, 1024, &map1);if (result1 == 0 && map1 != MAP_FAILED) {printf("Successfully mapped first 1KB of '%s' at %p\n", argv[1], (void*)map1);printf("First char of part 1: '%c'\n", map1[0]);} else {printf("Failed to map first part.\n");}// 尝试映射文件的 1KB-2KB 部分 (偏移 1024)result2 = safe_mmap_part(argv[1], 1024, 1024, &map2);if (result2 == 0 && map2 != MAP_FAILED) {printf("Successfully mapped 1KB starting at offset 1024 of '%s' at %p\n", argv[1], (void*)map2);// 打印这部分的第一个字符printf("First char of part 2: '%c'\n", map2[0]);} else {printf("Failed to map second part.\n");}// --- 清理阶段 ---// 解除第一个映射if (map1 != MAP_FAILED) {if (munmap(map1, 1024) == -1) {perror("munmap part 1");// 根据应用决定是否 exit} else {printf("Unmapped first part successfully.\n");}map1 = MAP_FAILED; // 防止重复解除映射}// 解除第二个映射if (map2 != MAP_FAILED) {if (munmap(map2, 1024) == -1) {perror("munmap part 2");} else {printf("Unmapped second part successfully.\n");}map2 = MAP_FAILED;}printf("All mappings cleaned up.\n");return 0;
}

代码解释:

  1. 定义了一个 safe_mmap_part 函数,它封装了打开文件、检查大小、调用 mmap 以及错误处理的逻辑。它返回 0 表示成功,-1 表示失败,并通过指针参数 mapped_ptr 返回映射地址。
  2. main 函数中,调用 safe_mmap_part 两次,分别映射文件的不同部分。
  3. 清理阶段: 遍历所有可能有效的映射指针(通过检查是否不等于 MAP_FAILED)并调用 munmap
  4. 关键: 对每一次 munmap 调用都检查了返回值。如果失败,会打印错误信息。
  5. 解除映射后,将指针设置回 MAP_FAILED,这是一种防止重复解除映射的好习惯。

总结来说,munmap 是管理 mmap 资源的关键函数。养成始终检查 munmap 返回值的习惯,并在适当的时候(尤其是 MAP_SHARED 映射)结合 msync 使用,对于编写健壮和高效的 Linux 程序至关重要。

http://www.dtcms.com/a/299432.html

相关文章:

  • 柔性智造:华控智能的垂直整合定制方案
  • 微服务springcloud http客户端feign
  • 伟淼科技李志伟:破解二代接班传承困局,系统性方案破除三代魔咒
  • Redis缓存策略以及bigkey的学习(九)
  • C语言——学习笔记
  • 数据结构(4)单链表算法题(上)
  • Linux DNS 服务器正反向解析
  • 深入分析计算机网络传输层和应用层面试题
  • 从压缩到加水印,如何实现一站式图片处理
  • 编程语言Java——核心技术篇(四)集合类详解
  • 从0开始学linux韦东山教程Linux驱动入门实验班(5)
  • C语言中:形参与实参的那些事
  • 分类预测 | MATLAB实现CPO-SVM冠豪猪算法优化支持向量机分类预测
  • 分类预测 | MATLAB实现DBO-SVM蜣螂算法优化支持向量机分类预测
  • pyskl-Windows系统使用自己的数据集训练(一)
  • 《C++ list 完全指南:从基础到高效使用》
  • 【洛谷】单向链表、队列安排、约瑟夫问题(list相关算法题)
  • 扣子(Coze)宣布开源两大核心项目——Coze Studio(扣子开发平台)和Coze Loop(扣子罗盘),附安装步骤
  • ubuntu下docker安装thingsboard物联网平台详细记录(附每张图)
  • 如何在 Ubuntu 24.04 或 22.04 中创建自定义 Bash 命令
  • 商汤InternLM发布最先进的开源多模态推理模型——Intern-S1
  • 【机器学习深度学习】LLamaFactory微调效果与vllm部署效果不一致如何解决
  • 开源智能体框架(Agent Zero)
  • VLAN的划分(基于华为eNSP)
  • Android 蓝牙学习
  • 使用Netty搭建一个网络聊天室
  • ​P1103 书本整理 - 洛谷​
  • 方正小标宋简3.0,可编辑
  • 暑期算法训练.9
  • ArcGIS 2024软件下载及安装教程|ArcGIS软件安装附下载地址|详细安装说明