㈠ linux鐨刴map鏂囦欢鍐呭瓨鏄犲皠鏈哄埗鏄浠涔堬紵
鍏变韩鍐呭瓨鍏佽镐袱涓鎴栧氫釜杩涚▼鍏变韩涓缁欏畾鐨勫瓨鍌ㄥ尯锛屽洜涓烘暟鎹涓嶉渶瑕佹潵鍥炲嶅埗锛屾墍浠ユ槸鏈蹇鐨勪竴绉嶈繘绋嬮棿閫氫俊鏈哄埗銆傚叡浜鍐呭瓨鍙浠ラ氳繃mmap()鏄犲皠鏅閫氭枃浠讹紙鐗规畩鎯呭喌涓嬭繕鍙浠ラ噰鐢ㄥ尶鍚嶆槧灏勶級鏈哄埗瀹炵幇锛屼篃鍙浠ラ氳繃绯荤粺V鍏变韩鍐呭瓨鏈哄埗瀹炵幇銆傚簲鐢ㄦ帴鍙e拰鍘熺悊寰堢畝鍗曪紝鍐呴儴鏈哄埗澶嶆潅銆備负浜嗗疄鐜版洿瀹夊叏閫氫俊锛屽線寰杩樹笌淇″彿鐏绛夊悓姝ユ満鍒跺叡鍚屼娇鐢ㄣ俶map鐨勬満鍒跺傦細灏辨槸鍦ㄧ佺洏涓婂缓绔嬩竴涓鏂囦欢锛屾瘡涓杩涚▼瀛樺偍鍣ㄩ噷闈锛屽崟鐙寮杈熶竴涓绌洪棿鏉ヨ繘琛屾槧灏勩傚傛灉澶氳繘绋嬬殑璇濓紝閭d箞涓嶄細瀵瑰疄闄呯殑鐗╃悊瀛樺偍鍣锛堜富瀛橈級娑堣楀お澶с俿hm鐨勬満鍒讹細姣忎釜杩涚▼鐨勫叡浜鍐呭瓨閮界洿鎺ユ槧灏勫埌瀹為檯鐗╃悊瀛樺偍鍣ㄩ噷闈銆
1銆乵map淇濆瓨鍒板疄闄呯‖鐩橈紝瀹為檯瀛樺偍骞舵病鏈夊弽鏄犲埌涓诲瓨涓娿備紭鐐癸細鍌ㄥ瓨閲忓彲浠ュ緢澶э紙澶氫簬涓诲瓨锛夛紱缂虹偣锛氳繘绋嬮棿璇诲彇鍜屽啓鍏ラ熷害瑕佹瘮涓诲瓨鐨勮佹參銆
2銆乻hm淇濆瓨鍒扮墿鐞嗗瓨鍌ㄥ櫒锛堜富瀛橈級锛屽疄闄呯殑鍌ㄥ瓨閲忕洿鎺ュ弽鏄犲埌涓诲瓨涓娿備紭鐐癸紝杩涚▼闂磋块棶閫熷害锛堣诲啓锛夋瘮纾佺洏瑕佸揩锛涚己鐐癸紝鍌ㄥ瓨閲忎笉鑳介潪甯稿ぇ锛堝氫簬涓诲瓨锛変娇鐢ㄤ笂鐪嬶細濡傛灉鍒嗛厤鐨勫瓨鍌ㄩ噺涓嶅ぇ锛岄偅涔堜娇鐢╯hm锛涘傛灉瀛樺偍閲忓ぇ锛岄偅涔堜娇鐢╩map銆
㈡ 【深入浅出Linux】关于mmap的解析
看这篇文章之前需要知道一个概念
虚拟内存系统通过将虚拟内存分割为称作虚拟页(Virtual Page,VP)大小固定的块,一般情况下,每个虚拟页的大小默认是4096字节。同样的,物理内存也被分割为物理页(Physical Page,PP),也为4096字节。
在LINUX中我们可以使用mmap用来在进程虚拟内存地址空间中分配地址空间,创建和物理内存的映射关系。
映射关系可以分为两种
1、文件映射
磁盘文件映射进程的虚拟地址空间,使用文件内容初始化物理内存。
2、匿名映射
初始化全为0的内存空间。
而对于映射关系是否共享又分为
1、私有映射(MAP_PRIVATE)
多进程间数据共享,修改不反应到磁盘实际文件,是一个-on-write(写时复制)的映射方式。
2、共享映射(MAP_SHARED)
多进程间数据共享,修改反应到磁盘实际文件中。
因此总结起来有4种组合
1、私有文件映射
多个进程使用同样的物理内存页进行初始化,但是各个进程对内存文件的修改不会共享,也不会反应到物理文件中
2、私有匿名映射
mmap会创建一个新的映射,各个进程不共享,这种使用主要用于分配内存(malloc分配大内存会调用mmap)。
例如开辟新进程时,会为每个进程分配虚拟的地址空间,这些虚拟地址映射的物理内存空间各个进程间读的时候共享,写的时候会-on-write。
3、共享文件映射
多个进程通过虚拟内存技术共享同样的物理内存空间,对内存文件 的修改会反应到实际物理文件中,他也是进程间通信(IPC)的一种机制。
4、共享匿名映射
这种机制在进行fork的时候不会采用写时复制,父子进程完全共享同样的物理内存页,这也就实现了父子进程通信(IPC).
这里值得注意的是,mmap只是在虚拟内存分配了地址空间,只有在第一次访问虚拟内存的时候才分配物理内存。
在mmap之后,并没有在将文件内容加载到物理页上,只上在虚拟内存中分配了地址空间。当进程在访问这段地址时,通过查找页表,发现虚拟内存对应的页没有在物理内存中缓存,则产生"缺页",由内核的缺页异常处理程序处理,将文件对应内容,以页为单位(4096)加载到物理内存,注意是只加载缺页,但也会受操作系统一些调度策略影响,加载的比所需的多。
1.write
因为物理内存是有限的,mmap在写入数据超过物理内存时,操作系统会进行页置换,根据淘汰算法,将需要淘汰的页置换成所需的新页,所以mmap对应的内存是可以被淘汰的(若内存页是"脏"的,则操作系统会先将数据回写磁盘再淘汰)。这样,就算mmap的数据远大于物理内存,操作系统也能很好地处理,不会产生功能上的问题。
2.read
从图中可以看出,mmap要比普通的read系统调用少了一次的过程。因为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为什么主要采用分页机制来实现虚拟存储管理
1 分页机制
在虚拟内存中,页表是个映射表的概念, 即从进程能理解的线性地址(linear address)映射到存储器上的物理地址(phisical address).
很显然,这个页表是需要常驻内存的东西, 以应对频繁的查询映射需要(实际上,现代支持VM的处理器都有一个叫TLB的硬件级页表缓存部件,本文不讨论)。
1.1 为什么使用多级页表来完成映射
但是为什么要使用多级页表来完成映射呢?
用来将虚拟地址映射到物理地址的数据结构称为页表, 实现两个地址空间的关联最容易的方式是使用数组, 对虚拟地址空间中的每一页, 都分配一个数组项. 该数组指向与之关联的页帧, 但这会引发一个问题, 例如, IA-32体系结构使用4KB大小的页, 在虚拟地址空间为4GB的前提下, 则需要包含100万项的页表. 这个问题在64位体系结构下, 情况会更加糟糕. 而每个进程都需要自身的页表, 这回导致系统中大量的所有内存都用来保存页表.
设想一个典型的32位的X86系统,它的虚拟内存用户空间(user space)大小为3G, 并且典型的一个页表项(page table entry, pte)大小为4 bytes,每一个页(page)大小为4k bytes。那么这3G空间一共有(3G/4k=)786432个页面,每个页面需要一个pte来保存映射信息,这样一共需要786432个pte!
如何存储这些信息呢?一个直观的做法是用数组来存储,这样每个页能存储(4k/4=)1K个,这样一共需要(786432/1k=)768个连续的物理页面(phsical page)。而且,这只是一个进程,如果要存放所有N个进程,这个数目还要乘上N! 这是个巨大的数目,哪怕内存能提供这样数量的空间,要找到连续768个连续的物理页面在系统运行一段时间后碎片化的情况下,也是不现实的。
为减少页表的大小并容许忽略不需要的区域, 计算机体系结构的涉及会将虚拟地址分成多个部分. 同时虚拟地址空间的大部分们区域都没有使用, 因而页没有关联到页帧, 那么就可以使用功能相同但内存用量少的多的模型: 多级页表
但是新的问题来了, 到底采用几级页表合适呢?
1.2 32位系统中2级页表
从80386开始, intel处理器的分页单元是4KB的页, 32位的地址空间被分为3部分
单元
描述
页目录表Directory 最高10位
页中间表Table 中间10位
页内偏移 最低12位
即页表被划分为页目录表Directory和页中间表Tabl两个部分
此种情况下, 线性地址的转换分为两步完成.
第一步, 基于两级转换表(页目录表和页中间表), 最终查找到地址所在的页帧
第二步, 基于偏移, 在所在的页帧中查找到对应偏移的物理地址
使用这种二级页表可以有效的减少每个进程页表所需的RAM的数量. 如果使用简单的一级页表, 那将需要高达220个页表, 假设每项4B, 则共需要占用220?4B=4MB的RAM来表示每个进程的页表. 当然我们并不需要映射所有的线性地址空间(32位机器上线性地址空间为4GB), 内核通常只为进程实际使用的那些虚拟内存区请求页表来减少内存使用量.
1.3 64位系统中的分页
正常来说, 对于32位的系统两级页表已经足够了, 但是对于64位系统的计算机, 这远远不够.
首先假设一个大小为4KB的标准页. 因为1KB覆盖210个地址的范围, 4KB覆盖212个地址, 所以offset字段需要12位.
这样线性地址空间就剩下64-12=52位分配给页中间表Table和页目录表Directory. 如果我们现在决定仅仅使用64位中的48位来寻址(这个限制其实已经足够了, 2^48=256TB, 即可达到256TB的寻址空间). 剩下的48-12=36位被分配给Table和Directory字段. 即使我们现在决定位两个字段各预留18位, 那么每个进程的页目录和页表都包含218个项, 即超过256000个项.
基于这个原因, 所有64位处理器的硬件分页系统都使用了额外的分页级别. 使用的级别取决于处理器的类型
平台名称
页大小
寻址所使用的位数
分页级别数
线性地址分级
alpha 8KB 43 3 10 + 10 + 10 + 13
ia64 4KB 39 3 9 + 9 + 9 + 12
ppc64 4KB 41 3 10 + 10 + 9 + 12
sh64 4KB 41 3 10 + 10 + 9 + 12
x86_64 4KB 48 4 9 + 9 + 9 + 9 + 12
㈣ /dev/mapper/rhel-root鏄浠涔
/dev/mapper/rhel-root
绯荤粺鍒╃敤Device mapper鏈哄埗寤虹珛浜嗕竴涓鍗风粍锛坴olume group锛孷G锛夈
Device mapper 鏄 Linux 2.6 鍐呮牳涓鎻愪緵鐨勪竴绉嶄粠閫昏緫璁惧囧埌鐗╃悊璁惧囩殑鏄犲皠妗嗘灦鏈哄埗锛屽湪璇ユ満鍒朵笅锛岀敤鎴疯兘澶熷緢鏂逛究鐨勬牴鎹鑷宸辩殑闇瑕佸畾鍒跺疄鐜板瓨鍌ㄨ祫婧愮殑绠$悊绛栫暐锛屽綋鍓嶆瘮杈冩祦琛岀殑 Linux 涓嬬殑閫昏緫鍗风$悊鍣ㄥ LVM2锛圠inux Volume Manager 2 version)銆丒VMS(Enterprise Volume Management System)銆乨mraid(Device Mapper Raid Tool)绛夐兘鏄鍩轰簬璇ユ満鍒跺疄鐜扮殑銆傜悊瑙hユ満鍒舵槸杩涗竴姝ュ垎鏋愩佺悊瑙h繖浜涘嵎绠$悊鍣ㄧ殑瀹炵幇鍙婅捐$殑鍩虹銆傞氳繃鏈鏂囦篃鑳藉熻繘涓姝ョ悊瑙 Linux 绯荤粺鍧椾竴绾 IO鐨勮捐″拰瀹炵幇銆傘奓inux灏辫ヨ繖涔堝︺
Device Mapper 鏄 Linux2.6 鍐呮牳涓鏀鎸侀昏緫鍗风$悊鐨勯氱敤璁惧囨槧灏勬満鍒讹紝浠栦负瀹炵幇鐢ㄤ簬瀛樺偍璧勬簮绠$悊鐨勫潡璁惧囬┍鍔ㄦ彁渚涗簡涓涓楂樺害妯″潡鍖栫殑鍐呮牳鏋舵瀯銆