linux-理解mmap函数

https://blog.csdn.net/iter_zc/article/details/44308729

稍后读:https://www.cnblogs.com/huxiao-tee/p/4660352.html

https://blog.csdn.net/luckywang1103/article/details/50619251

https://blog.csdn.net/u014686399/article/details/71247437

Linux进程地址空间管理 https://blog.csdn.net/chenying126/article/details/78703570

0.用户空间、内核空间

我们知道现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操心系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核,保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间每个进程可以通过系统调用或者中断进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间
需要注意的细节问题:
(1) 内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。
(2) Linux使用两级保护机制:0级供内核使用,3级供用户程序使用。
内核态与用户态:
(1)当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。
(2)当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。

1.一个Linux进程的虚拟内存

https://www.cnblogs.com/wuchanming/p/4339770.html

如图展示了一个Linux进程的虚拟内存。
虚拟的意思是进程以为自己有这么一大块内存,实际上物理内存可能还没有分配给它,等到缺页异常是系统才会分配,通过这种以时间换空间的方式提高了内存利用效率。从虚拟内存到物理内存的映射过程需要一个专门的硬件单元MMU来完成。
系统调用的代码和数据就在内核虚拟内存中,
因为在保护模式下,用户态进程无法访问到这里,必须要通过系统调用的方式陷入到内核态才行。

2.Linux是如何组织虚拟内存的


内核为系统中的每个进程维护一个单独的任务结构task_struct,其中元素包含了内核运行该进程所需要的所有信息(PID、指向用户栈的指针、可执行目标文件的名字、虚拟内存状态、pc指针等)
task_struct中的mm_struct描述了虚拟内存的当前状态,其中mmap字段指向一个vm_area_struct(区域结构)的链表。顺序搜索区域结构的链表花销会很大,实际上Linux在链表中构建了一个树,并在这棵树中进行查找。
进程对某一虚拟内存区域的任何操作需要用要的信息,都可以从vm_area_struct中获得。mmap函数就是要创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。

3.缺页处理


当MMU在试图翻译某个虚拟地址A时,触发了一个缺页。缺页异常处理程序会做如下检查:
- 1)虚拟地址A是否合法?即是否在链表mm_struct所描述的区域内。
- 2)试图进行的内存访问是否合法?即检查指令的权限是否与vm_prot字段所描述的页读写许可权限相匹配。
- 3)正常缺页。系统会负责把该虚拟内存区域对应的文件加载到内存中。

4.内存映射mmap函数

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。如下图所示:

          

由上图可以看出,进程的虚拟地址空间,由多个虚拟内存区域构成。虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。上图中所示的text数据段(代码段)、初始数据段、BSS数据段、堆、栈和内存映射,都是一个独立的虚拟内存区域。而为内存映射服务的地址空间处在堆栈之间的空余部分。

Linux通过将一个虚拟内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射。
Linux进程可以使用mmap函数来创建新的虚拟内存区域,并将对象映射到这些区域中。
mmap函数定义在libc中:

 #include <sys/mman.h>

     void *
     mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

具体内容可以通过命令man 2 mmap查看。
mmap参数的可视化解释:

mmap原理

在调用mmap实现这样的映射关系后,它只是在进程的虚拟空间中分配了一段空间,真实的物理地址还不会分配的,当进程第一次访问这段空间(当作内存一样),CPU陷入OS内核执行异常处理,然后异常处理会在这个时间分配物理内存,并用文件的内容填充这片内存,然后才返回进程的上下文,这时进程才会感知到这片内存里有数据。
之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。

mmap 的回写时机:
* 内存不足
* 进程退出
* 调用 msync 或者 munmap
* 不设置 MAP_NOSYNC 情况下 30s-60s(仅限FreeBSD)

mmap的4种类型

https://blog.csdn.net/iter_zc/article/details/44308729

mmap分为有后备文件的映射和匿名文件的映射,这两种映射又有私有映射和共享映射之分,所以mmap可以创建4种类型的映射

1. 后备文件的共享映射,多个进程的vm_area_struct指向同一个物理内存区域,一个进程对文件内容的修改,会被其他进程可见。对文件内容的修改会被写回到后备文件。

2. 后备文件的私有映射,多个进程的vm_area_struct指向同一个物理内存区域,采用写时拷贝的方式,当一个进程对文件内容做修改,不会被其他进程看到。另外对文件内的修改也不会被写回到后备文件。当内存不够需要进行页回收时,私有映射的页被交换到交换区。一般用在加载共享代码库

3. 匿名文件的共享映射,内核创建一个初始都是0的物理内存区域,然后多个进程的vm_area_struct指向这个共享的物理内存区域,对该区域内容的修改对所有进程可见。匿名文件在页回收时被交换到交换区

4. 匿名文件的私有映射,内核创建一个初始都是0的物理内存区域,对该区域内容的修改只对创建者进程可见。匿名文件在页回收时被交换到交换区。malloc()底层是用了匿名文件的私有映射来分配大块内存。

程序的加载

Linux执行一个ELF格式的程序,这个程序在磁盘上,为了执行这个程序,需要把程序加载到内存中,这时采用的就是mmap,mmap让虚拟空间和文件的内容组成的空间(文件空间)对应。因为ELF格式是区分代码、数据段的,这里的就不是简单的整个文件的映射了,需要将文件的分段区域映射到内存的不同位置。OS加载ELF文件的过程非常复杂这里就不展开了,具体内容可以看《程序员的自我修养》。
当CPU真的在这个地址上发起读写执行等操作时,因为文件的内容在磁盘上是不能被CPU访问的,所以OS会进入异常,系统的缺页处理程序会调用文件系统把一页或者多页的文件内容加载到物理内存中。

5.为什么mmap()可以节约IO读写时间

常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制,这是由OS控制的。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。

而使用mmap操作文件中,由于不需要经过内核空间的数据缓存,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。
mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。

6、Linux 文件读写过程:

https://blog.csdn.net/yangguosb/article/details/78355037

这里写图片描述

读文件流程

  1. 进程调用库函数向内核发起读文件请求;
  2. 内核通过检查进程的文件描述符定位到虚拟文件系统的已打开文件列表表项;
  3. 调用该文件可用的系统调用函数read();
  4. read()函数通过文件表项链接到目录项模块,根据传入的文件路径,在目录项模块中检索,找到该文件的inode;
  5. 在inode中,通过文件内容偏移量计算出要读取的页;
  6. 通过inode找到文件对应的address_space;
  7. 在address_space中访问该文件的页缓存树,查找对应的页缓存结点:
    (1)如果页缓存命中,那么直接返回文件内容;
    (2)如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应的页填充该缓存页;重新进行第6步查找页缓存;

写文件流程

前5步和读文件一致,在address_space中查询对应页的页缓存是否存在:

  1. 如果页缓存命中,直接把文件内容修改更新在页缓存的页中。写文件就结束了。这时候文件修改位于页缓存,并没有写回到磁盘文件中去;
  2. 如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应的页填充该缓存页。此时缓存页命中,进行第6步。
  3. 一个页缓存中的页如果被修改,那么会被标记成脏页。脏页需要写回到磁盘中的文件块。有两种方式可以把脏页写回磁盘:
    (1)手动调用sync()或者fsync()系统调用把脏页写回
    (2)pdflush进程会定时把脏页写回到磁盘
    同时注意,脏页不能被置换出内存,如果脏页正在被写回,那么会被设置写回标记,这时候该页就被上锁,其他写请求被阻塞直到锁释放。
 
 
G
M
T
 
 
Detect languageAfrikaansAlbanianAmharicArabicArmenianAzerbaijaniBasqueBelarusianBengaliBosnianBulgarianCatalanCebuanoChichewaChinese (Simplified)Chinese (Traditional)CorsicanCroatianCzechDanishDutchEnglishEsperantoEstonianFilipinoFinnishFrenchFrisianGalicianGeorgianGermanGreekGujaratiHaitian CreoleHausaHawaiianHebrewHindiHmongHungarianIcelandicIgboIndonesianIrishItalianJapaneseJavaneseKannadaKazakhKhmerKoreanKurdishKyrgyzLaoLatinLatvianLithuanianLuxembourgishMacedonianMalagasyMalayMalayalamMalteseMaoriMarathiMongolianMyanmar (Burmese)NepaliNorwegianPashtoPersianPolishPortuguesePunjabiRomanianRussianSamoanScots GaelicSerbianSesothoShonaSindhiSinhalaSlovakSlovenianSomaliSpanishSundaneseSwahiliSwedishTajikTamilTeluguThaiTurkishUkrainianUrduUzbekVietnameseWelshXhosaYiddishYorubaZulu
 
AfrikaansAlbanianAmharicArabicArmenianAzerbaijaniBasqueBelarusianBengaliBosnianBulgarianCatalanCebuanoChichewaChinese (Simplified)Chinese (Traditional)CorsicanCroatianCzechDanishDutchEnglishEsperantoEstonianFilipinoFinnishFrenchFrisianGalicianGeorgianGermanGreekGujaratiHaitian CreoleHausaHawaiianHebrewHindiHmongHungarianIcelandicIgboIndonesianIrishItalianJapaneseJavaneseKannadaKazakhKhmerKoreanKurdishKyrgyzLaoLatinLatvianLithuanianLuxembourgishMacedonianMalagasyMalayMalayalamMalteseMaoriMarathiMongolianMyanmar (Burmese)NepaliNorwegianPashtoPersianPolishPortuguesePunjabiRomanianRussianSamoanScots GaelicSerbianSesothoShonaSindhiSinhalaSlovakSlovenianSomaliSpanishSundaneseSwahiliSwedishTajikTamilTeluguThaiTurkishUkrainianUrduUzbekVietnameseWelshXhosaYiddishYorubaZulu
 
 
 
 
 
 
 
 
 
Text-to-speech function is limited to 200 characters
 
 
Options : History : Feedback : Donate Close

猜你喜欢

转载自blog.csdn.net/yanbao4070/article/details/81085774