`mmap` 系统调用详解

mmap 是 Unix/Linux 系统中一个强大且多用途的系统调用,用于将文件或设备映射到进程的地址空间,实现内存映射I/O。

1. 函数的概念与用途

mmap(内存映射)函数允许程序将文件或其他对象直接映射到其地址空间,这样文件内容就可以通过内存指针直接访问,而不需要使用传统的 read/write 系统调用。

主要用途:

  • 文件I/O优化:提供对文件的高效随机访问,避免频繁的 read/write 系统调用
  • 进程间通信:通过映射同一文件实现共享内存
  • 内存分配:用于实现高效的内存分配器(如 malloc
  • 程序加载:用于加载可执行文件和共享库
  • 零拷贝I/O:在某些情况下可以实现零拷贝网络传输

2. 函数的声明与出处

mmap 函数定义在 <sys/mman.h> 头文件中,是 POSIX 标准的一部分。

#include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

3. 返回值的含义与取值范围

  • 成功时:返回映射区域的起始地址
  • 失败时:返回 MAP_FAILED(通常是 (void *)-1),并设置 errno 来指示错误原因

常见错误码 (errno):

  • EACCES:文件描述符不支持所请求的访问类型
  • EAGAIN:文件已被锁定,或太多内存被锁定
  • EBADF:文件描述符无效
  • EINVAL:参数无效(如长度为零、不支持的保护或标志)
  • ENFILE:已达到系统对打开文件总数的限制
  • ENODEV:文件所在文件系统不支持内存映射
  • ENOMEM:没有可用内存,或进程超出内存映射限制
  • EPERM:操作被拒绝(如尝试写入只写区域)

4. 参数的含义与取值范围

  1. void *addr

    • 含义:建议的映射起始地址(通常设为 NULL,由内核自动选择)
    • 取值范围:任何有效的地址或 NULL
  2. size_t length

    • 含义:要映射的字节数
    • 取值范围:大于 0 的值
  3. int prot

    • 含义:内存保护标志,指定访问权限
    • 常见取值(使用按位或组合):
      • PROT_NONE:页面不可访问
      • PROT_READ:页面可读
      • PROT_WRITE:页面可写
      • PROT_EXEC:页面可执行
  4. int flags

    • 含义:映射类型和选项
    • 常见取值(使用按位或组合):
      • MAP_SHARED:共享映射,修改对其他进程可见
      • MAP_PRIVATE:私有映射,修改不会写回文件
      • MAP_ANONYMOUS:不映射文件,创建匿名内存
      • MAP_FIXED:必须使用指定地址
      • MAP_LOCKED:锁定页面在内存中
  5. int fd

    • 含义:要映射的文件描述符
    • 取值范围:有效的文件描述符(对于匿名映射,设为 -1)
  6. off_t offset

    • 含义:文件中的偏移量,必须是系统页面大小的倍数
    • 取值范围:非负值,通常是页面大小的倍数

5. 函数使用案例

案例1:文件映射与读取

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main() {const char *filename = "example.txt";const int file_size = 1024;// 创建并写入示例文件int fd = open(filename, O_RDWR | O_CREAT, 0644);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}// 调整文件大小if (ftruncate(fd, file_size) == -1) {perror("ftruncate");close(fd);exit(EXIT_FAILURE);}// 映射文件到内存char *mapped = mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (mapped == MAP_FAILED) {perror("mmap");close(fd);exit(EXIT_FAILURE);}// 通过内存访问文件内容const char *message = "Hello, mmap!";strncpy(mapped, message, strlen(message));printf("File content via mmap: %s\n", mapped);// 取消映射并关闭文件if (munmap(mapped, file_size) == -1) {perror("munmap");}close(fd);return 0;
}

案例2:进程间共享内存

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>#define SHM_SIZE 1024int main() {// 创建共享内存区域(匿名映射)char *shared_mem = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);if (shared_mem == MAP_FAILED) {perror("mmap");exit(EXIT_FAILURE);}pid_t pid = fork();if (pid == -1) {perror("fork");exit(EXIT_FAILURE);}if (pid == 0) {  // 子进程printf("Child process writing to shared memory\n");strcpy(shared_mem, "Message from child process");exit(EXIT_SUCCESS);} else {         // 父进程wait(NULL);  // 等待子进程结束printf("Parent process reading from shared memory: %s\n", shared_mem);// 清理if (munmap(shared_mem, SHM_SIZE) == -1) {perror("munmap");}}return 0;
}

案例3:高效大数据处理

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>#define FILE_SIZE (1024 * 1024 * 100)  // 100MBint main() {const char *filename = "large_file.bin";int fd = open(filename, O_RDWR | O_CREAT, 0644);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}// 调整文件大小if (ftruncate(fd, FILE_SIZE) == -1) {perror("ftruncate");close(fd);exit(EXIT_FAILURE);}// 映射文件char *mapped = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (mapped == MAP_FAILED) {perror("mmap");close(fd);exit(EXIT_FAILURE);}// 使用内存映射进行高效处理clock_t start = clock();for (size_t i = 0; i < FILE_SIZE; i++) {mapped[i] = (char)(i % 256);  // 填充数据}clock_t end = clock();double elapsed = (double)(end - start) / CLOCKS_PER_SEC;printf("Processed %d MB in %.2f seconds (%.2f MB/s)\n", FILE_SIZE / (1024 * 1024), elapsed, (FILE_SIZE / (1024.0 * 1024.0)) / elapsed);// 清理if (munmap(mapped, FILE_SIZE) == -1) {perror("munmap");}close(fd);remove(filename);  // 删除临时文件return 0;
}

6. 编译方式与注意事项

编译命令:

gcc -o mmap_example1 mmap_example1.c
gcc -o mmap_example2 mmap_example2.c
gcc -o mmap_example3 mmap_example3.c

注意事项:

  1. 权限检查:确保对映射的文件有适当的访问权限
  2. 对齐要求offset 参数必须是系统页面大小的倍数(可用 sysconf(_SC_PAGE_SIZE) 获取)
  3. 资源清理:始终使用 munmap() 取消映射,并使用 close() 关闭文件描述符
  4. 错误处理:始终检查返回值并处理可能的错误
  5. 同步:对于共享映射,修改可能不会立即写回文件,可使用 msync() 强制同步
  6. 内存保护:注意设置适当的保护标志,避免安全漏洞
  7. 可移植性:不同系统可能有不同的页面大小和限制

7. 执行结果说明

案例1输出:

File content via mmap: Hello, mmap!

这个示例创建了一个文件,通过内存映射写入内容,然后读取并显示内容。

案例2输出:

Child process writing to shared memory
Parent process reading from shared memory: Message from child process

这个示例演示了父子进程如何通过匿名映射共享内存区域。

案例3输出:

Processed 100 MB in 0.15 seconds (666.67 MB/s)

这个示例展示了使用内存映射处理大文件的高效性,通过直接内存访问避免了频繁的系统调用。

8. 图文总结

以下是 mmap 系统调用的工作流程:

文件映射
匿名映射
应用程序调用 mmap()
内核验证参数并保留地址空间区域
映射类型?
建立文件页表项
但不立即加载所有内容
分配物理内存页并清零
返回映射区域的指针
应用程序通过指针访问内存
访问的页面已加载?
触发缺页异常
内核加载所需数据到内存
恢复应用程序执行
直接访问内存
继续执行

关键点总结:

  1. mmap 提供了一种高效的文件访问方式,避免了频繁的 read/write 系统调用
  2. 支持文件映射和匿名映射两种模式,分别用于文件I/O和进程间通信
  3. 采用惰性加载机制,只有在实际访问时才会将数据加载到内存
  4. 对于共享映射,修改可能会自动写回文件(取决于标志)
  5. 必须正确使用 munmap() 释放映射区域,避免资源泄漏
  6. 适当设置保护标志和映射选项对性能和安全性至关重要

mmap 是一个强大的系统调用,正确使用可以显著提高I/O性能,但需要仔细考虑内存管理和同步问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/96896.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/96896.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度剖析Spring AI源码(二):Model抽象层 - “驯服”天下AI的“紧箍咒”

深度剖析Spring AI源码&#xff08;二&#xff09;&#xff1a;Model抽象层 - “驯服”天下AI的“紧箍咒”上一章我们鸟瞰了Spring AI的宏伟蓝图&#xff0c;今天&#xff0c;我们要深入这座大厦的基石——Model抽象层。如果说Spring AI是连接Java与AI世界的桥梁&#xff0c;那…

永磁同步电机无速度算法--高频脉振正弦波注入到两相静止坐标系

一、原理介绍采用一种改进的永磁同步电机低速无位置传感器控制策略。与传统的旋转高频信号注入法和脉振高频信号注入法不同&#xff0c;该策略选择向静止坐标轴系注入高频脉振信号&#xff0c;转子位置估计信息可以通过载波电流响应提取。并使用一种类似于简化型扩展卡尔曼滤波…

嵌入式学习日志————ADC模数转换器之实验

1.配置ADC的步骤①开启RCC时钟&#xff0c;包括ADC和GPIO的时钟②配置GPIO&#xff0c;把相应端口配置成模拟输入模式③配置多路开关&#xff0c;把左边的通道接入右边的规则组列表里④配置ADC转换器⑤调用ADC_Cmd函数&#xff0c;开启ADC2.库函数配置ADCCLK分频器void RCC_ADC…

Java设计模式之《状态模式》

目录 1、状态模式 1.1、介绍 1.2、设计背景 1.3、适用场景 2、实现 2.1、if-else实现 2.2、状态模式实现 2.3、最终版 1、关于if-else的优化 2、状态模式下的优化 3、ArrayList 配置“状态流” 3、总结 前言 关于Java的设计模式分类如下&#xff1a; 对于状态模式…

three.js+WebGL踩坑经验合集(9.2):polygonOffsetFactor工作原理大揭秘

本篇延续上篇内容&#xff1a; three.jsWebGL踩坑经验合集(9.1):polygonOffsetUnits工作原理大揭秘-CSDN博客 跟polygonOffsetUnits相比&#xff0c;polygonOffsetFactor的系数m要复杂得多&#xff0c;因为它跟平面的视角相关&#xff0c;而不像r那样&#xff0c;在一个固定的…

C++高级特性与设计模式答案

目录 C++高级特性与设计模式:从资源管理到架构设计 一、C++高级特性:超越基础语法的利器 1. 什么是RAII(资源获取即初始化)?它有什么作用? 实现原理 核心作用 2. 什么是Pimpl惯用法?它有什么优势? 实现方式 核心优势 3. 什么是CRTP(奇异递归模板模式)?它的应用场景是…

论文阅读:arxiv 2025 Can You Trick the Grader? Adversarial Persuasion of LLM Judges

总目录 大模型安全相关研究&#xff1a;https://blog.csdn.net/WhiffeYF/article/details/142132328 Can You Trick the Grader? Adversarial Persuasion of LLM Judges https://arxiv.org/pdf/2508.07805 https://www.doubao.com/chat/17534937260220418 文章目录论文翻译…

6pen Art

本文转载自&#xff1a;6pen Art - Hello123工具导航 ** 一、&#x1f3a8; 6pen 是什么&#xff1f; 6pen 是一款由国内团队开发的 AI 绘画工具&#xff0c;让你只需用文字描述想法&#xff0c;就能瞬间生成惊艳的视觉画作。不管是写实风景还是抽象概念&#xff0c;它都能理…

Let‘s Encrypt证书在 Android5.x 的设备上报错

报错信息&#xff1a; com.android.volley.NoConnectionError: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.at com.android.volley.toolbox.NetworkUtility.shouldRetryException(N…

C语言数组名与sizeof的深层关联

要理解 “数组名本质代表整个数组的类型和内存块” 与 “sizeof(arr) 输出总字节数” 的关联&#xff0c;核心是抓住 sizeof 运算符的设计逻辑 和 数组类型的本质属性—— 这两者是直接挂钩的&#xff0c;我们一步步拆解&#xff1a;第一步&#xff1a;先明确 sizeof 的核心作用…

最近对javashop做了压力测试:百万级并发下完全不是问题

最近对 javashop 做了压力测试&#xff1a;百万级并发下完全不是问题 在电商行业竞争白热化的今天&#xff0c;系统性能直接决定了用户体验和企业商业成功。本文基于《Javashop 压测报告》&#xff0c;从技术架构、核心指标、业务价值三大维度深度解析其性能优势&#xff0c;并…

Java大厂面试实战:从Spring Boot到微服务架构的全链路技术解析

Java大厂面试实战&#xff1a;从Spring Boot到微服务架构的全链路技术解析 面试场景&#xff1a;某互联网大厂Java后端开发岗 面试官&#xff08;严肃&#xff09;&#xff1a;谢飞机&#xff0c;我们今天来聊点硬核的。先说说你对Java生态的理解。 谢飞机&#xff08;挠头&…

在分布式环境下正确使用MyBatis二级缓存

在分布式环境下使用 MyBatis 二级缓存&#xff0c;核心挑战是解决多节点缓存一致性问题。单机环境中&#xff0c;二级缓存是内存级别的本地缓存&#xff0c;而分布式环境下多节点独立部署&#xff0c;本地缓存无法跨节点共享&#xff0c;易导致 “缓存孤岛” 和数据不一致。本文…

血缘元数据采集开放标准:OpenLineage Integrations Apache Spark Quickstart with Jupyter

OpenLineage 是一个用于元数据和血缘采集的开放标准&#xff0c;专为在作业运行时动态采集数据而设计。它通过统一的命名策略定义了由作业&#xff08;Job&#xff09;、运行实例&#xff08;Run&#xff09;和数据集&#xff08;Dataset&#xff09; 组成的通用模型&#xff0…

人工智能之数学基础:离散随机变量和连续随机变量

本文重点 随机变量是概率论与统计学中的核心概念,用于将随机现象的抽象结果转化为可量化的数值。根据取值特性的不同,随机变量可分为离散型和连续型两大类。 在前面的课程中我们学习了随机变量,随机变量可以理解为一个函数,通过这个函数我们就可以将随机试验中的结果数值…

SQL语句(查询)

单表查询 常量查询 让我们来看一个具体的 SQL 代码和结果示例&#xff0c;假设有一张名为 orders 的数据表&#xff0c;它存储了订单信息&#xff0c;包括订单编号&#xff08;order_id&#xff09;、商品单价&#xff08;unit_price&#xff09;、购买数量&#xff08;quantit…

Java 大视界 -- Java 大数据机器学习模型在金融市场波动预测与资产配置动态调整中的应用

Java 大视界 -- Java 大数据机器学习模型在金融市场波动预测与资产配置动态调整中的应用引言&#xff1a;正文&#xff1a;一、Java 构建的金融数据处理架构1.1 多源数据实时融合与清洗1.2 跨市场数据关联&#xff08;风险传导分析&#xff09;二、Java 驱动的市场波动预测模型…

基于muduo库的图床云共享存储项目(一)

基于muduo库的图床云共享存储项目&#xff08;一&#xff09;项目简介整体架构项目依赖基础组件muduo库Channel类Poller / EpollPoller 类EventLoopAcceptor类FastDfsJSON的使用项目简介 当前所实现的项目是一个基于muduo库的图床云共享存储项目&#xff0c;他的主要的功能就是…

数字化转型三阶段:从信息化、数字化到数智化的战略进化

企业的数字化转型包括信息化、数字化、数智化三个阶段&#xff0c;并非一个阶段结束才能进入到下一个阶段。01信息化→业务数据化信息化是将企业在生产经营过程中产生的业务信息进行记录、储存和管理&#xff0c;通过电子终端呈现&#xff0c;便于信息的传播与沟通。信息化是对…

SpringBoot如何获取系统Controller名称和方法名称

这种代码里面的Controller和里面的方法怎么获取代码&#xff1a;/*** 获取所有Controller名称*/ApiDescription("获取所有Controller名称")PostMapping("/getControllerNames")public Result getControllerNames() {return dataDesensitizationRulesServic…