3.存储器管理
存储器管理
内存管理的必要性
内存的基础知识在计算机执行时,处理器处理的几乎每一条指令都涉及到对存储器的访问,因此要求存储器的访问速度能跟上处理机的运行速度。此外还要求存储器有非常大的容量
因为主存储器的速度是跟不上处理器的,所以为了缓和这一矛盾,引入寄存器和高速缓存
寄存器:有和处理器一样的速度,所以寄存器的访问速度最快,但是价格昂贵,主要用于存放处理器运行时的数据
高速缓存:介于寄存器和储存器之间的存储器,主要用于备份主存中较常用的数据,一减少主存储器的访问次数,可以大幅提高程序执行速度
磁盘缓存:磁盘的I/O速度也远低于对主存的访问速度,为了缓和矛盾,而设置了磁盘缓存,主要用于暂时存放频繁使用的一部分磁盘数据与信息,减少访问磁盘的次数,磁盘缓存与高速缓存不同,并不实际存在,而是将主存中部分存储空间暂时存放从磁盘中读出的信息,主存当然也可以看做是硬盘的高速缓存
由于任何程序和数据都必须占用内存空间才能执行和处理,因此有效地存储管理也是多道程序设计技术的关键支撑
内存管理的概念
- 内存分配
要为每道程序分配内存空间,提高存储器的利用率,尽量减少碎片,允许正在运行的程序增加内存空间,
- 内存保护
确保每道用户程序都仅在自己的内存空间中运行,绝不允许用户程序访问操作系统的程序和数据
- 地址映射
每道程序经过编译和链接后形成的地址都是从0开始的,但是不可能将它们真的从0开始的物理地址装入内存,为了保证程序运行,存储器必须提供地址映射功能,即将地址空间中的逻辑地址转换为内存空间中的物理地址,在硬件支持下完成
- 内存扩充
借助虚拟存储功能,在逻辑上扩充内存容量
覆盖与交换技术
覆盖技术
覆盖技术用来解决程序大小超过物理内存总和的问题
覆盖技术的思想:将程序分为多个段,常用的段常驻内存,不常用的段在需要时调入内存
在内存中分一个固定区和若干个覆盖区,需要常驻内存的段放在固定区中,调入后就不再调出,不常用的段放在覆盖区,需要用到时调入内存,用不到时调出内存
必须由程序员声明覆盖结构,操作系统自动完成覆盖
缺点:对用户不透明,增加了用户编程负担,现在已经成为历史
交换技术
设计思想:内存空间紧张时,系统将内存中某些进程暂时换出外存,把外存中某些已具备运行条件的进程换入内存(中级调度:进程在内存与磁盘间动态调度)
通常把磁盘分为文件区和对换区两部分,文件区主要用于存放文件,主要追求存储空间的利用率,因此采用的是离散分配方式
对换区空间只占磁盘空间的小部分,因此对换区空间的管理主要追求换入换出速度,因此对换区采用连续分配方式
连续分配管理方式
连续分配:为用户分配的必须是一个连续的内存空间
单一连续分配
内存被分为系统区和用户区,系统区通常位于内存的低地址部分,用户存放操作系统的相关数据,用户区用于存放用户进程相关数据
内存中只能有一道用户程序,用户程序独占整个用户区空间
优点:
- 实现简单,无外部碎片,可以采用覆盖技术扩充内存
- 不一定需要采用内存保护(早期的PC操作系统MS-DOS)
缺点:
- 只能用于单用户,单任务的操作系统中;
- 有内部碎片;
- 存储利用率极低
内部碎片:分配给某进程的内存区域,如果有些部分没有用上
外部(内存)碎片:是指内存中某些空闲分区由于太小而难以利用
固定分区分配
将整个用户空间分配为若干个固定大小的分区,在每个分区中只装入一道作业,这样就形成了最早的,最简单的一种可执行多道程序的内存管理方式
分区大小相等:缺乏灵活性,但是很适合用于用一台计算机控制多个相同对象的场合
分区大小不同:增加了灵活性,可以满足不同大小的进程需求
操作系统需要建立一个分区说明表,来实现各个分区的分配与回收,每个表项对应一个分区,通常按分区大小排列,每个表项包括对应分区的大小,起始地址,状态
地址转换:对于固定分区的方式采用静态地址重定位的方式
内存保护:可由加载程序进行地址越界检查,即界地址保护
优点:
- 实现简单,无外部碎片
缺点
- 会产生内部碎片,内存利用率低,当用户程序太大,不得不采取覆盖技术
动态分区分配
在进程装入内存的时候,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要,因此系统分区的大小数目是可变的
系统可以采用空闲分区表或者空闲分区链来记录内存的使用情况
- 空闲分区表:每个空闲分区对应一个表项,表项中包含分区号,分区大小,分区起始地址等信息
- 空闲分区链:每个分区的起始部分和末尾部分分别设置前向指针和后向指针,起始部分处还可记录分区大小等信息
分配算法:当一个新作业装入内存时,须按照一定的动态分区分配算法,从空闲分区表中选出一个分区分配给该作业。由于分配算法对系统性能有很大影响,因此很重要
分配内存:如果请求的分区比空闲分区小,只需要在空闲分区表中修改分区大小和起始位置,如果请求的分区和空闲分区一样,那么可以直接删除空闲分区的那个表项
回收内存:有3种情况
如果回收区的前后有一个相邻的空闲分区,则将其合并
如果回收区的前后各有一个相邻的空闲分区,则全部合并
如果回收区的前后都没有相邻的空闲分区,则新增一个表项
地址转换:采用动态地址重定位
内存保护:设置两个专用控制寄存器,分别存放分区的起始地址和进程占有的连续存储空间的长度,在执行指令或访问数据时,由寄存器对比知道绝对地址以达到保护的目的
动态分区分配没有内部碎片,但是有外部碎片
首次适应算法(按序)
算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区
实现方式:空闲分区以地址递增的次序排列,每次分配内存时顺序查找空闲分区表,找到大小能满足要求的第一个空闲分区
最佳适应算法(最小)
算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域,因此,为了保证当大进程到来时有连续的大片空间,可以尽可能多地留下大片的空闲区,优先使用更小的空闲区
实现方式:将空闲分区按容量递增次序链接,每次分配内存时顺序查找空闲分区链,找到大小能满足要求的第一个空闲分区
缺点:每次都选最小的分区进行分配,会留下越来越多的,很小的,难以利用的内存块,这种方法会产生很多的外部碎片
最坏适应算法(最大)
算法思想:为了解决最佳适应算法的问题,可以在每次分配时优先利用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用
实现方式:空闲分区按容量递减次序链接,每次分配内存时顺序查找空闲分区链,找到大小能满足要求的第一个空闲分区
缺点:每次都选最大的分区分配,但是会导致较大的连续空箱区被迅速用完,之后如果有大进程到达,就没有内存分区可用了
邻近适应算法(最近)
算法思想:首次适应算法每次都从表头开始查找,每次都要经过细小的分区,增加了查找的开销,如果每次都从上次查找结束的位置开始查找,就能解决上述问题
实现方式:空闲分区以地址递增的顺序排列,每次分配内存时从上次查找结束的位置开始查找空闲分区表,找到大小能满足要求的第一个空闲分区
算法总结
综合来看,首次适应算法效果最好
离散分配管理方式
连续分配方式会形成很多碎片,虽然可以通过紧凑(compact)方法将许多碎片拼接成可用的大块空间,但是要付出很大开销,如果允许将一个进程直接分散地装入到许多不相邻接的分区中,便可充分利用内存空间,而无须紧凑,这就是离散分配方式,而离散分配方式又可以分为三种
- 分页存储管理方式
- 分段存储管理方式
- 段页式存储管理方式
分页存储管理方式
基本概念
页框(物理):将内存空间分为一个个大小相等的分区,每个分区就是一个页框或物理块,每个页框有一个编号,即页框号,页框号从0开始
页面(逻辑):将进程的逻辑地址空间也分为与页框大小相等的一个个部分,每个部分称为页或页面,每个页面也有一个编号,称为页号,页号也是从0开始
操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中,也就是说,进程的页面与内存的页框有一一对应的关系
各个页面不必连续存放,可以放到不相邻的各个页框中
页表(对应):为了能知道进程的每个页面在内存中存放的位置,操作系统要为每个进程建立一张页表
- 一个进程就对应一张页表
- 进程的每个页面对应一个页表项
- 页表记录进程页面和实际存放的内存块之间的映射关系
- 页表实际上也是存储在页面内,所以页面大小能决定一页能存放多少个页表
在页表中页号是隐含的
页表项:在页表中,一个页号与其对应的物理块号称之为一个页表项
例题:假设某物理内存大小为4GB,页面大小为4KB,则每个页表项至少应该为多少字节?
解:内存块大小=页面大小=4KB=$2^{12}$B
4GB的内存会被分为 $2^{32}/2^{12}=2^{20}$个内存块
内存块号至少需要20bit来表示来表示,即3B,页表项由页号和块号组成,页号是隐含的,所以每个页表项至少需要3B来表示
内存的块数等于物理内存大小除以页面大小,可以算出需要多少个二进制数来表达具体的内存块数,再转换为字节数
页表记录的只是内存块号,而不是内存块的起始地址,J号内存块的起始地址等于J×内存块大小
地址结构
虽然进程的各个页面在物理上是离散存放的,但是页面内部是连续存放的
即一个进程只有一个页表以及多个页面,在每个页面里面,地址都是从0开始,连续存放的,而页内偏移量就是指,该逻辑地址在页面内部,距离页面起始的偏差值
如果要访问逻辑结构A,则需要找到A的页号P,然后找到P号页面在内存中的起始地址,确定逻辑地址A的页内偏移量W
例:在某计算机系统中,页面大小是50B,某进程的逻辑地址空间大小时200B,则逻辑地址110的页号和页内偏移量是多少?
页号=110/2=2
页内偏移量=110%50=10
而在计算机内部,地址是用二进制表示的,如果页面大小刚好是2的整数幂,则计算机硬件可以很快速的把逻辑地址拆分为页号和页内偏移量
如果计算机用32个bit来表示逻辑地址,每个页面大小为$2^kB$,用二进制数表示逻辑地址,则末尾$K$位即为页内偏移量,其余部分就是页号
分页存储管理的逻辑地址结构如下
地址结构包含两个部分:前一部分为页号,后一部分为页内偏移量,在图中,地址长度为32
位,其中0~11
位为页内偏移量,或称页内地址,12到31位是页号
如果有K位表示页内偏移量,则说明该系统中一个页面的大小是$2^k$个内存单元
如果有M位表示页号,则说明在系统中,一个进程最多允许有$2^M$个页面
基本地址变化机构
基本地址变换结构可以借助进程的页表将逻辑地址转换为物理地址
通常会在系统中设置一个页表寄存器,存放页表在内存中的起始位置$F$和页表长度$M$,进程未执行时,页表的起始位置和页表长度放在进程控制块(PCB)中,当进程被调度时,操作系统内核会把它们放在页表寄存器中
设页面大小为$L$,逻辑地址$A$到物理地址$E$的变换过程如下
- 计算页号$P$和页内偏移量$W$
- 比较页号$P$和页表长度$M$,检查$P$是否小于$M$
- 页表中页号$P$对应的页表项地址=页表起始地址$F+$页号$P×$页表项长度,取出该页表项内容$B$,即内存块号
- 计算得到物理地址$E=$内存块号$B×$页面大小$L+$页内偏移量$W$,用得到的物理地址$E$去访存
具有快表的地址变换结构
快表,又称联想寄存器(TLB,translation lookaside buffer),是一种访问速度比内存快很多的高速缓存,用来存放最近访问的页表项的副本,可以加速地址变换,与此对应,内页中的页表常称为慢表
变换过程:
- CPU给出逻辑地址,由某个硬件算得页号,页内偏移量,将页号与快表中的所有页号进行比较
- 如果找到匹配的页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元,因此,如果快表命中,则访问某个逻辑地址仅需一次访存即可
- 如果没有找到匹配的页号,则需要访问内存中的页表,计算得到物理地址后,访问该物理地址对应的内存单元,因此,若快表未命中,则访问某个逻辑地址需要两次访存(在找到页表项后,应同时将其存入快表,以便后面可能的再次访问)
两级页表
单级页表的不便:
- 如果要用页号来查询页表项,要在所有的页表项都连续存放的基础上才能用这种方法找到页表项,这违背了离散存储的特性
- 很多时候,进程在一段时间内只需要访问某几个页面就可以正常运行了,因此没有必要让整个页表都常驻内存
解决进程必须在内存中连续存储的问题时,运用了分页的思想,那么解决页表必须连续存放时,同样可以把必须存放的页表再分页
可以将长长的页表进行分组,使每个内存块刚好可以放入一个分组,为离散分配的页表再建立一张页表,称为页目录表,或称外层页表
其中一级页号可以在页目录表中指示页表的存放内存块号,然后二级页号可以指示页面的具体存放位置
若采取多级页表机制,则各级页表大小不能超过一个页面
N级页表访问一个逻辑地址需要N+1次访存
分段存储管理方式
进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名,每段从0开始
内存分配规则:以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻,由于是按逻辑功能模块划分,用户编程更方便,刻度性更高
段号的位数决定了每个进程最多可以分为几个段
段内地址位数决定了每个段的最大长度是多少
程序分为多个段,各段离散地装入内存,为了保证程序的运行,就必须从物理内存中找到各个逻辑段的存放位置,为此需要每个进程建立一张段映射表,简称段表
每个段对应一个段表项,其中记录了该段内存中的起始位置和段的长度
各个段表项的长度是相同的,因此,段号可以是隐含的,不占存储空间
分段,分页管理的对比
页是信息的物理单位,分页的主要目的是为了实现离散分配,提高内存利用率,分页仅仅是系统管理上的需要,对用户不可见
段是信息的逻辑单位,分段的主要目的是更好地满足用户需求,一个段通常包含着一组属于一个逻辑模块的信息。分段对用户是可见的
- 分页的用户进程地址空间是一维的,程序员只需给出一个记忆符即可表示一个地址
- 分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址
- 分段比分页更容易实现信息的共享和保护
分段,分页的优缺点分析
优点 | 缺点 | |
---|---|---|
分页管理 | 内存空间利用率高,不会产生外部碎片,只会有少量页内碎片 | 不方便按照逻辑模块实现信息的共享和保护 |
分段管理 | 方便按照逻辑模块实现信息的共享和保护 | 如果段长过大,为其分配很大的连续空间会很不方便,另外,段式管理会产生外部碎片 |
段页式管理方式
段页式系统的基本原理还是分段和分页原理的结合,即先将用户程序分为若干段,再把每个段分为大小相同的内存块,并为每个段赋予一个段名
段号的位数决定了每个进程最多可以分为几个段
页号位数决定了每个段最大有多少页
页内偏移量决定了页面大小,内存块大小
每个段对应一个段表项,每个段表项由段号,页表长度,页表存放块号组成,每个段表项长度相等,段号是隐含的
变换过程
需要三次访存,第一次是访问内存中的段表,第二次是访问内存中的页表,最后才是访问目标内存单元
不同分配方式的总结
虚拟存储器
概念
传统存储管理方式存在一些问题,比如作业必须一次全部装入内存后才能开始运行,一旦作业被装入内存,就会一直驻留在内存中,直到作业结束,这会浪费很多内存资源
根据局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行,在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序
若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存
在操作系统的作用下,在用户开始似乎有一个比实际内存大得多的内存,这就是虚拟内存
虚拟内存技术,允许一个作业分多次调入内存,实现需要建立在离散分配的内存管理方式上
请求分页管理方式
请求分页存储管理和基本分页存储管理的主要区别是:
在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序,操作系统需要提供请求调页功能
若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存,操作系统需要提供页面置换的功能
请求分页存储管理的页表
状态位:表示是否已调入内存
访问字段:可记录最近被访问过几次,或记录上次被访问的时间,供置换算法参考
修改位:表示页面调入内存后是否被修改过
外存位置:页面在外存中的存放位置
每当要访问的页面不在内存时,便产生一个缺页中断,然后由操作系统的缺页中断处理程序处理中断,此时缺页的进程阻塞,放入阻塞队列,调页完成后再将其唤醒,放入就绪队列
如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入该块,并修改页表中相应的页表项
如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存,未修改过的页面不用写回外存
缺页中断属于内中断,一条指令在实行期间,可能产生多次缺页中断
页面置换算法
页面的换入换出需要磁盘I/O,会有较大的开销,因此好的页面置换算法应该追求更少的缺页率
最佳置换算法(OPT,Optimal)
每次选择淘汰的页面将是以后永不使用,或者最长时间内不再被访问的页面,这样可以保证最低的缺页率
最佳置换算法可以保证最低的缺页率,但实际上,只有在进程执行的过程中才能知道接下来会访问到的是哪个页面,操作系统无法提前预判页面访问序列,因此,最佳置换算法是无法实现的
先进先出算法(First In First Out)
每次选择淘汰的页面是最早进入内存的页面
Belady异常:当为进程分配的物理块数增大时,缺页次数不减反增的异常现象
只有FIFO算法会产生Belady异常,FIFO算法虽然实现简单,但是该算法与进程实际运行时的规律不适应,因此算法性能差
最近最久未使用置换算法(LRU,Least Recently Used)
每次淘汰的页面是最近最久未使用的页面
实现方法:赋予每个页面对应的页表项中,用访问字段记录该页面自上次被访问以来所经历的时间t,当要淘汰一个页面时,选择t值最大的
该算法实现需要专门的硬件,虽然算法性能好,但是实现困难,开销大
时钟置换算法(CLOCK)
又称最近未用算法(NRU,Not Recently Used)
实现方法:为每个页面设置一个访问位,再将内存中的页面都通过指针链接成一个循环队列。当某页被访问时,其访问位置为1。当要淘汰一个页面时,只需检查页的访问位。如果是0,就将其换出,如果是1,就将它置为0,继续检查下一个页面
若第一轮扫描中所有页面都是1,则将这些页面的访问位依次置为0,再进行第二轮扫描
改进型时钟置换算法
简单的时钟置换算法仅考虑到一个页面最近是否被访问过,事实上,如果被淘汰的页面没有被修改过,就不需要执行I/O操作写回外存,只有被淘汰的页面修改过,才需要写回外存
因此,操作系统还应考虑页面有没有被修改过,在其他条件都相同时,应优先淘汰没有修改过的页面,避免I/O操作,这就是改进型的时钟置换算法思想
修改位=0,表示未修改过,修改位为1,表示页面修改过,用(访问位,修改位)表示页面
第一轮:从当前位置开始扫描到第一个(0,0)替换
第二轮:若第一轮失败,则查找第一个(0,1)替换,本轮将所有扫描过的帧访问为设为0
第三轮:若第二轮失败,则重新扫描,查找到第一个(0,0)替换
第四轮:若第三轮扫描失败,则重新扫描,查找到第一个(0,1)替换
页面分配策略,抖动,工作集
驻留集:指请求分页存储管理中给进程分配的物理块的集合
工作集:指在某段时间间隔里,进程实际访问页面的集合
在任一时刻t
,都存在一个集合,它包含所有最近k
次(即为窗口大小)内存访问所访问过的页面。这个集合w(k,t)
即为工作集
在使用虚拟存储技术的系统中,驻留集大小一般小于进程总大小
固定分配:操作系统为每个进程分配一个固定数目的物理快
可变分配:先为每个进程分配一定的数目,在运行期间,可根据情况做适当的增加或减少
局部置换:发生缺页的时候只能选进程自己的物理块进行置换
全部置换:可以将操作系统保留的空闲物理块分配给缺页进程,也可以将别的进程有的物理快置换到外存,在分配给缺页进程
全部置换肯定是可变分配
分配策略
固定分配局部置换
系统为每个进程分配一定数量的物理块,在整个运行期间都不改变。若进程在运行中发生缺页,则只能从该进程在内存中的页面中选出一页换出,然后再调入需要的页面。
这种策略的缺点是:很难在刚开始就确定应为每个进程分配多少个物理块才算合理。(采用这种策略的系统可 以根据进程大小、优先级、或是根据程序员给出的参数来确定为一个进程分配的内存块数)
可变分配全部置换
刚开始会为每个进程分配一定数量的物理块。操作系统会保持一个空闲物理块队列。当某进程发生缺页时,从空闲物理块中取出一块分配给该进程;若已无空闲物理块,则可选择一个未锁定的页面换出外存,再将该物理块分配给缺页的进程。
采用这种策略时,只要某进程发生缺页, 都将获得新的物理块,仅当空闲物理块用完时,系统才选择一个未锁定的页面调出。被选择调出的页可能是系统中任何一个进程中的页,因此这个被选中的进程拥有的物理块会减少,缺页率会增加。
可变分配局部置换
刚开始会为每个进程分配一定数量的物理块,若进程在运行中发生缺页,则只能从该进程在内存中的页面中选出一页换出,然后再调入需要的页面。若进程在运行中频繁缺页,系统会为该进程多分配几个物理块,直至该进程缺页率趋势适当程度
调入页面策略
预调页策略:根据局部性原理,一次调入若干个相邻的页面可能比一次调入一个页面更高效,但如果提前调入的页面中大多数都没被访问过,则又是低效的。因此可以预测不久之后可能访问到的页面,将它们预先调入内存,但目前预测成功率只有50%左右。故这种策略主要用于进程的首次调入, 由程序员指出应该先调入哪些部分。
请求调入策略:进程在运行期间发现缺页时才将所缺页面调入内存。由这种策略调入的页面一定会被访问到,但由于每次只能调入一页,而每次调页都要磁盘I/O操作,因此I/O开销较大。
调入页面的位置
系统拥有足够的对换区空间:页面的调入、调出都是在内存与对换区之间进行,这样可以保证页面的调入、调出速度很快。在进程运行前,需将进程相关的数据从文件区复制到对换区。
系统缺少足够的对换区空间:凡是不会被修改的数据都直接从文件区调入,由于这些页面不会被修改,因此换出时不必写回磁盘,下次需 要时再从文件区调入即可。对于可能被修改的部分,换出时需写回磁盘对换区,下次需要时再从对换区调入。
UNIX 方式:运行之前进程有关的数据全部放在文件区,故未使用过的页面,都可从文件区调入。若被使用过的页面需要换出,则写回对换区,下次需要时从对换区调入。
抖动现象
刚刚换出的页面马上就要换入内存,刚刚换入的页面马上又换出外存,这种频繁的页面行为称为抖动现象
产生抖动现象的主要原因是进程频繁访问的页面数目高于可用的物理块数
操作系统会根据窗口尺寸来算出工作集,工作集大小可能小于窗口尺寸,在实际应用中,操作系统可以统计进程的工作集大小来考虑分配的驻留集
预防方法
- 采用局部置换策略,即使发送了抖动也不会对其他进程造成影响
- 在调度中融入工作集算法
- 综合考虑缺页平均时间和平均缺页服务时间
内存映射文件
传统IO中当对文件进行操作的时候,一般总是先打开文件,然后申请一块内存用做缓冲区,再将文件数据循环读入并处理,当文件长度大于缓冲区长度的时候需要多次读入。
内存映射文件是将一个文件直接映射到进程的进程空间中(“映射”就是建立一种对应关系,这里指硬盘上文件的位置与进程逻辑地址空间中一块相同区域之间一 一对应,这种关系纯属是逻辑上的概念,物理上是不存在的),这样可以通过内存指针用读写内存的办法直接存取文件内容。 在内存映射过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑上放入了内存,具体到代码,就是建立并初始化了相关的数据结构,这个过程由系统调用mmap()实现,所以映射的效率很高.
经验表明,内存映射IO允许加载不能直接访问的潜在巨大文件,在大文件处理方面性能更加优异。它的不足是增加了页面错误的数目(由于操作系统只将一部分文件加载到内存,如果一个请求页面没有在内存中,它将导致页面错误)。
映射文件区域的能力取决于于内存寻址的大小。在32位机器中,你不能一次访问超过4GB或2 ^ 32(以上的文件),只能分批映射。
虚拟内存和内存映射文件的联系和区别
联系:
虚拟内存是内存映射文件的基础,内存映射文件的底层还是依赖虚拟内存。虚拟内存和内存映射文件都是将一部分内容加载到内存,另一部分放在磁盘上,二者都是应用程序动态性的基础,由于二者的虚拟性,对于用户都是透明的.
虚拟内存是硬盘的一部分,是计算机RAM(随机存取存储器)与硬盘的数据交换区,因为实际的物理内存可能远小于进程的地址空间,这就需要把内存中暂时不用到的数据放到硬盘上一个特殊的地方,当请求的数据不在内存中时,系统产生缺页中断,内存管理器便将对应的内存页重新从硬盘调入物理内存。
内存映射文件是由一个文件到一块内存的映射,使应用程序可以通过内存指针对磁盘上的文件进行访问,其过程就如同对加载了文件的内存的访问,因此内存文件映射非常适合于用来管理大文件。
区别:
虚拟内存实现的基础是分页机制和局部性原理,架构在物理内存之上,其引入是因为实际的物理内存运行程序所需的空间,即使现在计算机中的物理内存越来越大,将所有运行着的程序全部加载到内存中非常不现实。
内存映射文件虚拟性并不是由于局部性,而是使进程虚拟地址空间的某个区域建立映射磁盘文件的全部或部分内容,通过该区域可以直接对被映射的磁盘文件进行访问,而不必执行文件I/O操作也无需对文件内容进行缓冲处理。