Mmap的实现原理和应用

文章围绕普通read/write和mmap后映射区域读写的区别、选择mmap而非普通读写的原因展开分析。介绍了进程运行空间、系统调用中write和read、mmap的原理,强调page cache管理。通过代码运行结果对比,显示mmap系统调用耗时远少于普通read。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

很多文章分析了mmap的实现原理。从代码的逻辑来分析,总是觉没有把mmap后读写映射区域和普通的read/write联系起来。不得不产生疑问:

1,普通的read/write和mmap后的映射区域的读写到底有什么区别。

2, 为什么有时候会选择mmap而放弃普通的read/write。

3,如果文章中的内容有不对是或者是不妥的地方,欢迎大家指正。

 

围绕着这两个问题分析一下,其实在考虑这些问题的同时不免和其他的很多系统机制产生交互。虽然是讲解mmap,但是很多知识还是为了阐明问题做必要的铺垫。这些知识也正是linux的繁琐所在。一个应用往往和系统中的多种机制交互。这篇文章中尽量减少对源代码的引用和分析。把这样的工作留到以后的细节分析中。但是很多分析的理论依据还是来自于源代码。可见源代码的重要地位。


基础知识:

1, 进程每次切换后,都会在tlb base寄存器中重新load属于每一个进程自己的地址转换基地址。在cpu当前运行的进程中都会有current宏来表示当前的进程的信息。应为这个代码实现涉及到硬件架构的问题,为了避免差异的存在在文章中用到硬件知识的时候还是指明是x86的架构,毕竟x86的资料和分析的研究人员也比较多。其实arm还有其他类似的RISC的芯片,只要有mmu支持的,都会有类似的基地址寄存器。

2, 在系统运行进程之前都会为每一个进程分配属于它自己的运行空间。并且这个空间的有效性依赖于tlb base中的内容。32位的系统中访问的空间大小为4G。在这个空间中进程是“自由”的。所谓“自由”不是说对于4G的任何一个地址或者一段空间都可以访问。如果要访问,还是要遵循地址有效性,就是tlb base中所指向的任何页表转换后的物理地址。其中的有效性有越界,权限等等检查。

3, 任何一个用户进程的运行在系统分配的空间中。这个空间可以有
vma:struct vm_area_struct来表示。所有的运行空间可以有这个结构体描述。用户进程可以分为text data 段。这些段的具体在4G中的位置有不同的vma来描述。Vma的管理又有其他机制保证,这些机制涉及到了算法和物理内存管理等。请看一下两个图片:

图 一:


图 二:


系统调用中的write和read:

 这里没有指定确切的文件系统类型作为分析的对象。找到系统调用号,然后确定具体的文件系统所带的file operation。在特定的file operation中有属于每一种文件系统自己的操作函数集合。其中就有read和write。

图 三:

在真正的把用户数据读写到磁盘或者是存储设备前,内核还会在page cache中管理这些数据。这些page的存在有效的管理了用户数据和读写的效率。用户数据不是直接来自于应用层,读(read)或者是写入(write)磁盘和存储介质,而是被一层一层的应用所划分,在每一层次中都会有不同的功能对应。最后发生交互时,在最恰当的时机触发磁盘的操作。通过IO驱动写入磁盘和存储介质。这里主要强调page cache的管理。应为page的管理设计到了缓存,这些缓存以page的单位管理。在没有IO操作之前,暂时存放在系统空间中,而并未直接写入磁盘或者存贮介质。

系统调用中的mmap:

当创建一个或者切换一个进程的同时,会把属于这个当前进程的系统信息载入。这些系统信息中包含了当前进程的运行空间。当用户程序调用mmap后。函数会在当前进程的空间中找到适合的vma来描述自己将要映射的区域。这个区域的作用就是将mmap函数中文件描述符所指向的具体文件中内容映射过来。

原理是:mmap的执行,仅仅是在内核中建立了文件与虚拟内存空间的对应关系。用户访问这些虚拟内存空间时,页面表里面是没有这些空间的表项的。当用户程序试图访问这些映射的空间时,于是产生缺页异常。内核捕捉这些异常,逐渐将文件载入。所谓的载入过程,具体的操作就是read和write在管理pagecache。Vma的结构体中有很文件操作集。vma操作集中会有自己关于page cache的操作集合。这样,虽然是两种不同的系统调用,由于操作和调用触发的路径不同。但是最后还是落实到了page cache的管理。实现了文件内容的操作。

Ps:

文件的page cache管理也是很好的内容。涉及到了address space的操作。其中很多的内容和文件操作相关。

效率对比:

    这里应用了网上一篇文章。发现较好的分析,着这里引用一下。

Mmap:

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

#include <sys/mman.h>

void main()

{

int fd = open("test.file", 0);

struct stat statbuf;

char *start;

char buf[2] = {0};

int ret = 0;

fstat(fd, &statbuf);

start = mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

do {

*buf = start[ret++];

}while(ret < statbuf.st_size);

}  

Read:

#include <stdio.h>

#include <stdlib.h>

void main()

{

FILE *pf = fopen("test.file", "r");

char buf[2] = {0};

int ret = 0;

do {

ret = fread(buf, 1, 1, pf);

}while(ret);

}   


运行结果:

[xiangy@compiling-server test_read]$ time ./fread

real    0m0.901s

user    0m0.892s

sys     0m0.010s

[xiangy@compiling-server test_read]$ time ./mmap

real    0m0.112s

user    0m0.106s

sys     0m0.006s

[xiangy@compiling-server test_read]$ time ./read

real    0m15.549s

user    0m3.933s

sys     0m11.566s

[xiangy@compiling-server test_read]$ ll test.file

-rw-r--r-- 1 xiangy svx8004 23955531 Sep 24 17:17 test.file   


可以看出使用mmap后发现,系统调用所消耗的时间远远比普通的read少很多。
---------------------  
作者:小陆zi  
来源:优快云  
原文:https://blog.youkuaiyun.com/edwardlulinux/article/details/8604400  
版权声明:本文为博主原创文章,转载请附上博文链接!

<think>好的,我现在需要帮助用户了解mmap原理以及它如何实现零拷贝。首先,我得回忆一下mmap的基本概念。mmap是memorymapping的缩写,也就是内存映射。它的主要作用是将文件或其他对象映射到进程的地址空间,这样应用程序就可以像访问内存一样访问文件,而不需要通过read/write系统调用来进行数据拷贝。接下来,我需要详细解释mmap的工作原理。用户可能需要知道mmap系统调用的具体参数步骤。比如,mmap函数原型是什么样的,参数有什么作用。例如,void*mmap(void*addr, size_t length, intprot,int flags, intfd,off_toffset);这里addr通常是让内核自动选择映射地址,length是映射长度,prot是保护权限,flags控制映射类型,fd是文件描述符,offset是文件偏移。这部分需要结合引用中的内容,尤其是引用[3]提到的函数原型。然后是关于零拷贝的实现机制。传统文件读写需要用户空间内核空间之间的多次数据拷贝,比如通过read将数据从磁盘拷贝到内核缓冲区,再从内核缓冲区拷贝到用户空间;write时又需要从用户空间拷贝到内核缓冲区,再写到磁盘或网络。而mmap通过将文件直接映射到用户空间,避免了用户空间内核空间之间的拷贝,只有一次从磁盘到内核缓冲区的DMA拷贝,以及从内核缓冲区到用户空间的映射,不需要CPU参与拷贝,从而减少拷贝次数,提高效率。这里可以参考引用[1]提到的DMA拷贝零拷贝的概念,以及引用[4]中关于mmap如何实现零拷贝的解释。还需要讨论mmap是否真正实现了零拷贝。根据引用[4]中的问题,华为面试提到是否真正实现了零拷贝。这里可能需要说明,mmap减少了用户空间内核空间之间的拷贝,但仍然需要内核缓冲区到用户空间的映射,而sendfile则更进一步,完全避免了用户空间的参与,所以mmap可能不是狭义上的零拷贝,但属于广义上的优化。需要区分不同零拷贝技术的定义,引用[1]提到狭义零拷贝是sendfile的情况,而mmap可能属于减少拷贝次数的情况。另外,要注意mmap的优缺点。比如优点包括减少拷贝次数、提高大文件处理效率、方便共享内存等。缺点可能有内存同步问题,如引用[5]提到的需要使用msync来同步数据,防止断电导致数据丢失。此外,大文件映射可能导致虚拟内存压力,需要处理缺页中断,可能影响性能。 最后,生成相关问题需要考虑用户可能的后续疑问,比如mmap与其他零拷贝技术的对比,具体应用场景,或者如何在实际代码中使用mmap。结合之前的引用,可以生成相关问题,比如mmapsendfile的区别,或者使用mmap时的注意事项。需要确保回答结构清晰,分点解释原理实现机制,同时引用相关文献来支持观点。例如,在解释mmap减少拷贝次数时,引用[1][4]的内容。在讨论同步问题时引用[5]。同时,语言要符合中文要求,避免使用Markdown,数学公式用$...$,独立公式用$$...$$,但用户问题中没有涉及数学公式,所以可能不需要使用。不过要检查是否有需要公式的地方,比如DMA拷贝的流程,但可能用文字描述更合适。 总结回答结构:先介绍mmap的基本原理,分步骤说明映射过程;然后对比传统IOmmap的拷贝次数,突出零拷贝的实现;接着讨论优缺点注意事项;最后相关问题部分围绕对比、应用、安全等展开。</think>### Mmap原理及零拷贝实现机制 #### 一、Mmap基本原理 **内存映射(Memory Mapping,mmap)** 是一种将文件直接映射到进程虚拟地址空间的技术,使得应用程序可以像操作内存一样读写文件。其核心步骤包括: 1. **建立映射关系** 调用 `mmap()` 函数时,操作系统为文件分配一段虚拟内存区域,并将其与物理文件关联。此时文件内容并未完全加载到内存,而是按需通过**缺页中断**动态加载[^3]。 2. **访问文件数据** 进程通过指针直接读写映射区域,操作系统自动将修改同步到文件(若指定 `MAP_SHARED` 标志)。 函数原型如下: ```c void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); ``` 参数说明: - `addr`:建议映射地址(通常设为 `NULL`,由内核自动分配) - `length`:映射长度 - `prot`:内存保护权限(如 `PROT_READ`) - `flags`:控制映射类型(如 `MAP_SHARED`) - `fd`:文件描述符 - `offset`:文件起始偏移量 #### 二、零拷贝实现机制 **传统文件读写流程**: 1. 磁盘数据 → 内核缓冲区(DMA拷贝) 2. 内核缓冲区 → 用户缓冲区(CPU拷贝) 3. 用户缓冲区 → 内核网络缓冲区(CPU拷贝) 4. 网络缓冲区 → 网卡(DMA拷贝) **总拷贝次数**:4次,涉及2次CPU拷贝[^1][^4]。 **Mmap优化后的流程**: 1. 磁盘数据 → 内核缓冲区(DMA拷贝) 2. **内核缓冲区映射到用户空间**(无需CPU拷贝) 3. 内核缓冲区 → 网卡(DMA拷贝) **总拷贝次数**:3次,仅1次CPU拷贝(数据准备阶段)[^4]。 通过共享内核缓冲区与用户空间,避免了用户态与内核态之间的数据复制,从而减少CPU开销。 #### 三、Mmap的优缺点 **优势**: 1. **减少拷贝次数**:相比传统IO,CPU拷贝次数从2次降为0次(仅DMA拷贝)[^1][^4]。 2. **高效处理大文件**:按需加载,避免一次性占用过多内存。 3. **共享内存**:多进程可通过映射同一文件实现通信。 **局限性**: 1. **内存同步风险**:默认异步写入,需手动调用 `msync()` 确保数据持久化(如防止断电丢失)[^5]。 2. **虚拟内存压力**:大文件映射可能导致频繁缺页中断。 3. **非狭义零拷贝**:严格意义的零拷贝需完全消除CPU拷贝(如 `sendfile` 技术)[^1][^4]。 #### 四、应用场景 - **大文件处理**:如日志分析、数据库文件操作。 - **进程间通信**:通过共享映射区域传递数据。 - **高性能IO**:结合网络框架(如Netty、Kafka)提升吞吐量[^2][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值