linux mmap(linux内存映射mmap原理分析)

【深入浅出Linux】关于mmap的解析

看这篇文章之前需要知道一个概念

虚拟内存系统通过将虚拟内存分割为称作虚拟页(Virtual Page,VP)大小固定的块,一般情况下,每个虚拟页的大小默认是4096字节。同样的,物理内存也被分割为物理页(Physical Page,PP),也为4096字节。

在LINUX中我们可以使用mmap用来在进程虚拟内存地址空间中分配地址空间,创建和物理内存的映射关系。

映射关系可以分为两种

1、文件映射

磁盘文件映射进程的虚拟地址空间,使用文件内容初始化物理内存。

2、匿名映射

初始化全为0的内存空间。

而对于映射关系是否共享又分为

1、私有映射(MAP_PRIVATE)

多进程间数据共享,修改不反应到磁盘实际文件,是一个copy-on-write(写时复制)的映射方式。

2、共享映射(MAP_SHARED)

多进程间数据共享,修改反应到磁盘实际文件中。

因此总结起来有4种组合

1、私有文件映射

多个进程使用同样的物理内存页进行初始化,但是各个进程对内存文件的修改不会共享,也不会反应到物理文件中

2、私有匿名映射

mmap会创建一个新的映射,各个进程不共享,这种使用主要用于分配内存(malloc分配大内存会调用mmap)。

例如开辟新进程时,会为每个进程分配虚拟的地址空间,这些虚拟地址映射的物理内存空间各个进程间读的时候共享,写的时候会copy-on-write。

3、共享文件映射

多个进程通过虚拟内存技术共享同样的物理内存空间,对内存文件的修改会反应到实际物理文件中,他也是进程间通信(IPC)的一种机制。

4、共享匿名映射

这种机制在进行fork的时候不会采用写时复制,父子进程完全共享同样的物理内存页,这也就实现了父子进程通信(IPC).

这里值得注意的是,mmap只是在虚拟内存分配了地址空间,只有在第一次访问虚拟内存的时候才分配物理内存。

在mmap之后,并没有在将文件内容加载到物理页上,只上在虚拟内存中分配了地址空间。当进程在访问这段地址时,通过查找页表,发现虚拟内存对应的页没有在物理内存中缓存,则产生"缺页",由内核的缺页异常处理程序处理,将文件对应内容,以页为单位(4096)加载到物理内存,注意是只加载缺页,但也会受操作系统一些调度策略影响,加载的比所需的多。

1.write

因为物理内存是有限的,mmap在写入数据超过物理内存时,操作系统会进行页置换,根据淘汰算法,将需要淘汰的页置换成所需的新页,所以mmap对应的内存是可以被淘汰的(若内存页是"脏"的,则操作系统会先将数据回写磁盘再淘汰)。这样,就算mmap的数据远大于物理内存,操作系统也能很好地处理,不会产生功能上的问题。

2.read

从图中可以看出,mmap要比普通的read系统调用少了一次copy的过程。因为read调用,进程是无法直接访问kernel space的,所以在read系统调用返回前,内核需要将数据从内核复制到进程指定的buffer。但mmap之后,进程可以直接访问mmap的数据(page cache)。

测试结果来源于:深入剖析mmap-从三个关键问题说起

1.读性能分析

场景:对2G的文件进行顺序写入

可以看到mmap在100byte写入时已经基本达到最大写入性能,而write调用需要在4096(也就是一个page size)时,才能达到最大写入性能。

从测试结果可以看出,在写小数据时,mmap会比write调用快,但在写大数据时,反而没那么快。

2.写性能分析

场景:对2G的文件进行顺序读取(为了避免磁盘对测试的影响,2G文件都缓存在pagecache中)

由上可以看出,在read上面,mmap的性能还是非常好的。

优点如下:

1、对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。

2、实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。

3、提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。同时,如果进程A和进程B都映射了区域C,当A第一次读取C时通过缺页从磁盘复制文件页到内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据。

4、可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说,但凡是需要用磁盘空间代替内存的时候,mmap都可以发挥其功效。

缺点如下:

1.文件如果很小,是小于4096字节的,比如10字节,由于内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。虽然被映射的文件只有10字节,但是对应到进程虚拟地址区域的大小需要满足整页大小,因此mmap函数执行后,实际映射到虚拟内存区域的是4096个字节,11~4096的字节部分用零填充。因此如果连续mmap小文件,会浪费内存空间。

3.如果更新文件的操作很多,会触发大量的脏页回写及由此引发的随机IO上。所以在随机写很多的情况下,mmap方式在效率上不一定会比带缓冲区的一般写快。

linux内存管理——mmap函数详解

Linux内存管理中的mmap函数是一种重要的系统调用,它在客户-服务程序中通过共享内存显著优化了文件复制操作。原本的四次数据复制被简化为两次,提高了性能。mmap函数的主要用途包括:

1.将文件映射到内存,常用于频繁读写的场景,通过内存操作代替IO,提升性能。

2.匿名内存映射,为进程提供共享内存空间,便于进程间的协作。

3.无关联进程间的Posix共享内存,如SystemV的shmget/shmat功能的实现。

函数的参数详解如下:

- addr:映射的内存起始地址,可设为NULL让系统自动选择。

- length:映射到内存的文件部分大小。

- prot:映射区域的访问权限,可组合PROT_EXEC、PROT_READ、PROT_WRITE或PROT_NONE。

- flags:控制映射特性,如MAP_SHARED(共享)或MAP_PRIVATE(私有,写时复制)等。

- fd:映射文件描述符,匿名映射时设为-1。

- offset:文件映射的偏移量,通常为0。

示例代码展示了共享映射和父子进程通信的应用,如修改共享内存文件内容,以及利用mmap进行进程间的通信。同时,需要注意的是,Linux的页式管理机制决定了进程对映射内存的访问限制,避免内存访问溢出。

mmap函数在内存管理和进程间通信中发挥着关键作用,通过合理使用,可以提高程序性能并增强进程间的协作。更多详细信息请参考《UNIX网络编程卷2》。

Linux内存管理之mmap详解

Linux内存管理之mmap详解

一. mmap系统调用

mmap是Linux内核提供的一种功能,用于将文件或其他对象映射到进程的内存中。文件会映射到多个页上,如果文件大小不是所有页大小的总和,那么最后一个页的未使用空间会被清零。munmap则执行相反的操作,移除特定地址区域的对象映射。

使用mmap映射文件到进程后,可以直接操作虚拟地址区域进行文件的读写等操作,无需再调用read,write等系统调用。但需注意,直接对该段内存进行写操作时不会写入超过当前文件大小的内容。

共享内存通信的一个显著优点是效率高,进程可以直接读写内存,而无需数据拷贝。与管道和消息队列等通信方式相比,共享内存只需要两次数据拷贝:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,通信时,进程并不总是读写少量数据后就解除映射,而是保持共享区域,直到通信结束,数据内容一直保存在共享内存中,并在解除映射时写回文件。因此,采用共享内存通信效率非常高。

基于文件的映射,在mmap和munmap执行过程中,被映射文件的st_atime可能被更新。使用PROT_WRITE和MAP_SHARED标志建立的文件映射,其st_ctime和st_mtime在对映射区写入后,但在msync()通过MS_SYNC和MS_ASYNC调用前会被更新。

使用方法:

#include

void*mmap(void*start, size_t length, int prot, int flags,

int fd, off_t offset);

int munmap(void*start, size_t length);

返回说明:

成功时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED(其值为(void*)-1),munmap返回-1。错误代码包括EACCES(访问错误)、EAGAIN(文件已被锁定或内存已锁定)、EBADF(无效文件描述符)、EINVAL(参数无效)、ENFILE(文件描述符限制)、ENODEV(文件系统不支持内存映射)、ENOMEM(内存不足)、EPERM(权限不足)、ETXTBSY(以只读方式打开文件,同时指定MAP_DENYWRITE标志)、SIGSEGV(尝试向只读区写入)、SIGBUS(尝试访问不属于进程的内存区)。

参数:

start:映射区的开始地址。

length:映射区的长度。

prot:期望的内存保护标志,不能与文件的打开模式冲突。可通过或运算组合为以下值:PROT_EXEC(可执行)、PROT_READ(可读)、PROT_WRITE(可写)、PROT_NONE(不可访问)。

flags:指定映射对象的类型、映射选项和是否可以共享。值可组合为以下位:MAP_FIXED(使用指定的映射起始地址,如果与现有映射空间重叠,重叠部分将被丢弃。起始地址必须落在页边界上)、MAP_SHARED(与其他所有映射此对象的进程共享映射空间,写入映射区相当于输出到文件,直到msync()或munmap()调用,文件实际上未更新)、MAP_PRIVATE(建立一个写入时复制的私有映射,内存区域的写入不会影响原文件)。

fd:有效的文件描述符。若使用MAP_ANONYMOUS,为了兼容问题,其值应为-1。

offset:被映射对象内容的起点。

二.系统调用munmap()

#include

int munmap( void* addr, size_t len)。该调用解除进程地址空间中的映射关系,addr是mmap()返回的地址,len是映射区的大小。映射关系解除后,对原映射地址的访问将导致段错误。

三.系统调用msync()

#include

int msync( void* addr, size_t len, int flags)。通常,映射空间的更改不会直接写回磁盘文件中,直到munmap()调用后才执行此操作。通过调用msync()可以实现磁盘上文件内容与共享内存区内容的一致性。

四. mmap进行内存映射的原理

mmap系统调用的主要目的是将设备或文件映射到用户进程的虚拟地址空间,实现用户进程对文件的直接读写。此过程分为三步:在用户虚拟地址空间中寻找空闲的满足要求的连续虚拟地址空间(由内核的mmap系统调用完成)、建立虚拟地址空间与文件或设备物理地址之间的映射(通过修改进程页表实现)、在实际访问新映射页面时的操作(由缺页中断完成)。详细描述包括页表管理、虚拟地址与物理地址的映射、缺页异常处理等。

阅读剩余
THE END