《现代操作系统》03章 存储管理(二)
0 前文
3 虚拟内存
为什么需要虚拟内存呢?
这是软件发展带来的需求,软件功能越来越丰富导致体积越来越大,即使内存的空间也在不断增大但依然无法赶上软件的发展速度。这就带来了矛盾:怎样在有限的内存中运行多个大型应用程序?
3.1 简介
交换技术也仅仅对体积较小的程序有用,因为SATA硬盘的传输速率也非常的有限。
在早期解决大型软件运行的问题时(一般在工程和科研领域),程序员将程序分为多段——覆盖(【n】overlay),由操作系统根据运行需要将覆盖从硬盘读入内存。程序的分段枯燥、费时且容易出错。
在这样的发展背景下虚拟内存(Virtual Memory)应运而生:每个程序拥有自己的地址空间,地址空间被分为多个块——页(page),每个页有连续的地址范围,并且部分页被映射到物理内存,程序运行时若该page映射在物理内存则直接执行,若该page未映射在物理内存则由操作系统读取相应的page到物理内存并执行失败的代码。
虚拟内存特别适用于多道程序设计,在一个程序等待内存读入时可以交出CPU让其他程序运行。
虚拟地址(virtual address):由程序产生的地址,例如 MOV REG,1000
中的1000就是一个虚拟地址
虚拟地址空间:由虚拟地址构成的地址空间
虚拟内存系统 与 无虚拟内存系统的区别:
上图为虚拟内存系统,寻址命令会先经过MMU(Memory Management Unit)处理再送到地址总线完成寻址动作。无虚拟内存系统直接将地址送到地址总线。
有些系统中MMU集成如CPU内部,有些系统中MMU为单独的芯片
3.2 分页
3.2.1 page & page frame
分页的目的:
一台16位的机器可以产生0-64KB的虚拟地址空间,若机器只装载了32KB的运行内存,若超过了32K的程序是无法一次性装入内存的,为了方便内存管理(虚拟与真实的映射),需要将内存分页,这与空闲内存管理的分块思想不谋而合,甚至可以说二者是相辅相成的。
分页:
分页 是一种虚拟内存管理技术,该技术将虚拟内存的地址空间分为等长连续块。例如一个以4KB为页面大小的16位机器,其虚拟地址空间被分为0-4095、4096-8191、8192-12287…(以此类推)
页框:
物理内存中与页面(page)对应的是页框(page frame),通常二者大小相同,常用大小为512byte-64KB,RAM与磁盘的交换是以整页为单位的。
3.2.2 MMU的工作机制
MMU:
MMU的工作就是维护一张虚拟内存与物理内存的映射表——页表(page table),如执行 “MOV REG 0” ,MMU查表0-4096的page映射到8192-12287的page frame,因此MMU把8192送到了Address Bus,这条指令被转义为"MOV REG 8192" 。
对于page有没有page frame的映射,表中会用 present/absent bit来表示。
缺页中断:
当page与page frame无映射关系时,MMU通过 缺页中断(page fault)使CPU陷入操作系统,操作系统找到一个使用频率低的page frame并将其写入磁盘(若磁盘内无该page frame的内容),然后把需要访问的page读入回收的page frame,修改MMU中的映射表,退出中断。
虚拟地址的前四位作为页表的索引,将索引送入页表搜索,检查映射情况,存在即进行组合输出并送至地址总线,不存在即陷入操作系统。
3.3 页表
虚拟地址与页表关系:
虚拟地址由虚拟页号和偏移量构成,虚拟页号的位数和偏移量的位数共同决定了page的大小。虚拟页号作为页表的索引,在也表中搜寻对应的page frame number,页表可认为是一个以page number为输入,page frame number为输出的函数。
页表项的结构
页表项的结构与机器类型密切相关,但存储内容大致相同。32bit是页表项的常用大小。
区域 | 功能 |
---|---|
保护位(protection bit) | 指出该页允许何种类型访问(读/写/只读/只写等等) |
修改位(dirty bit) | 在写入一页时由硬件自动置位,在重新分配页框时。若此位为1(该页框被改写过,表示此页框是脏的),需要将页框内容写回磁盘(因磁盘中的副本已和页框内的不一致),若此位为0(该页框内容未被改写,表示此页框是干净的),内容无需写回磁盘,可直接丢弃 |
访问位 | 不论读写该位都被系统置位,帮助系统进行page淘汰决策 |
高速缓存禁止位 | 禁止页面被高速缓存,用于映射到寄存器的页面,寄存器中存储大量状态,系统和进程的状态是时刻变化的,高速缓存中存放的是相应内容的副本,若寄存器被高速缓存,实时性降低(具有独立IO空间而不使用内存映射的机器不需要此位) |
注意 在缺页中断中,保存淘汰页面内容的磁盘地址不在MMU中,而是在操作系统的软件表格中
页表一般存储在内存中(每个进程都有自己的页表)
虚拟内存系统需要解决的问题:
-(1)虚拟地址到物理地址的映射必须非常快
每条指令要进行一两次或多次内存访问(若进程的页表存储在内存中),若查表时间过长将影响运行速度
-(2)虚拟空间的增大带来页表的增大
现代计算机多为32bit或64bit的虚拟地址,以4KB为页长,32bit虚拟空间有100,0000页,64bit虚拟空间超乎想象
3.4 加速分页过程
两种极端的设计思路:
(1)设计一组“快速硬件存储器”用于存放进程页表副本,之后就不必再访问内存。缺点是成本高、上下文切换要重装页表,性能降低
(2)仅用一个寄存器存放进程页表的起始位置,每条指令都要一次或多次内存访问,速度慢
3.4.1 转换检测缓冲区(TLB)
这种设计思路首先基于一种现象:
大多数程序对 少量 页面 多次 访问
转换检测缓冲区(Translation Lookaside Buffer)
别名:相联存储器
位置:通常位于MMU
表项数量:通常不超过64
表项信息:虚拟页号、修改位、保护位、对应页框号、有效位
应用举例:
进程循环代码虚拟页号:19、20、21
数组地址虚拟页号:129、130
数组计算索引虚拟页号:140
堆栈虚拟页号:860、861
TLB加速分页内容:
有效位 | 虚拟页号 | 修改位 | 保护位 | 页框号 |
---|---|---|---|---|
1 | 140 | 1 | RW | 31 |
1 | 20 | 0 | R X | 38 |
1 | 130 | 1 | RW | 29 |
1 | 129 | 1 | RW | 62 |
1 | 19 | 0 | R X | 50 |
1 | 21 | 0 | R X | 45 |
1 | 860 | 1 | RW | 14 |
1 | 861 | 1 | RW | 75 |
TLB工作过程:
注意 匹配不成功淘汰表项时,将其修改位复制到内存的页表项中,访问位置位,其余位不改变
页表项从页表装入TLB时,所有的值都从内存复制
3.4.2 软件TLB管理
为何用软件管理
对于TLB的管理和TLB失效处理可以选择由MMU的硬件来完成,也可以使用软件(操作系统)来完成。现在的主流机器大多使用软件管理TLB,当TLB访问失效时产生一个TLB失效并交给操作系统来处理,TLB失效处理应在几条指令间完成,因为TLB的失效较为频繁。这样简化了MMU的设计,为改善机器性能的设计留出空间。
TLB管理策略
其中一种策略是预判即将被访问的页并将其加入到TLB中;还有一种是在内存中维护一个较大的软TLB(该页面常驻硬TLB),操作系统先检索软TLB再检索硬TLB,有效减小TLB失效。
硬失效与软失效
软失效:访问页面不在TLB中,在内存页表中
硬失效:访问页面在TLB与内存页表中均没有,需要磁盘IO
3.5 针对大内存的页表
3.5.1 多级页表
概念:可以类比为网络中的IP地址,IPv4的地址也分为多级
例如把32位地址分为10位PT1域、10位PT2域和12位Offset(偏移)域,页面长度位4KB,共2^20个页面
目的:避免把没用的页表一直存储在内存中,如需要12MB内存的进程,底端4MB程序,之后是4MB数据,顶部是4MB堆栈,中间均为空闲区(不需要存储这些页表)
举例:
按照上面提到的参数假设,32bit=4GB,PT1将4GB分为4MB的1024份,PT2的一个页表将4MB分为4KB的1024份
现在要访问 0x00403004(0000000001 0000000011 000000000100)
这样,在这个例子中内存中仅需要存放4张页表就可以满足该进程的需求,在PT1页表中,没有使用的表项present/absent bit全都置0即可,若该进程访问这些表项会引起缺页中断,操作系统将根据情况处理。
PT1、PT2、Offset位数可以自由设计,当级数超过3级复杂度也随之升高,超过三级的设计价值还有待研究。
为什么使用倒排表:
对于64位机器,多级页表也显得力不从心了,需要更好的解决之道。若64位机器以4KB为页面大小,需要一个有2^52表项的页表,若一个表项为8byte,整个页表要用3000wGB,即使使用多级页表,占用的空间也是相当惊人的(指数函数的增长速度永远都不会让人失望)
概念:
倒排表是一种逆向思维,原先是有多少虚拟空间就要对应有多少表项,倒排则是根据RAM的大小设计表项的多少,即每一个页框有一个表项。例如64bit虚拟空间,4KB的page,1GB的RAM,只需要262144个页表项,表项记录哪一个虚拟页面或进程对应该页框。
问题:
从虚拟地址到物理地址的转换变得困难,虚拟地址不在具有页表内的索引功能(页表的排序是按照页框顺序排的),必须搜索整个页表才能得到结果,并且每一次内存访问都要执行一次,这又降低了效率。
解决:
使用TLB:将频繁使用的表项放入TLB,并维护TLB表,TLB失效时再搜索全表
使用哈希表(散列表):将虚拟地址作为key,设计哈希函数(散列函数),将表项存储在哈希表(散列表)中,当散列表槽数和页框数相等,散列表冲突链平均长度为1,效率很高(哈希表的知识之后专写一篇博客讲述)
倒排表在64bit机器中很常见,其他处理大虚拟内存方法参见Tallurl等人的论文(1995)
X 往期文章
Python+OpenCV+imutils的简单图片处理(放缩、翻转、旋转、灰度RGB提取)
如果文中有误,还请在评论区指正。这里是海小皮,我们一同进步!!!