操作系统(11)----内存管理4:https://developer.aliyun.com/article/1511166
快表是如何工作的?
假设某进程执行过程中要依次访问(0,0)、(0,4)、(0,8)(页号,块号)这几个逻辑地址。若访问TLB只需1us,而访问内存需要100 us。
注:当进程切换时,快表的内容需要被清除。
①首先将进程的页号与页表寄存器中的页表长度对比,若页号大于页表长度则越界异常。
②若没有越界,则查询快表,但是由于进程刚上处理机运行,因此快表中的内容为空,快表中找不到页号为0的页表项,因此快表没有命中
③若快表没有命中,那么就会访问慢表,通过页表始址+页号*页表项长度,得到内存块号,再根据内存块号和页内偏移量即可得到目标物理地址。
④因为快表中没有该页表项,所以会从慢表中复制一份,放到快表中。接着通过内存块号与页内偏移量拼接得到页号为0,页内偏移量为0的目标物理地址。
⑤接下来要访问的是页号为0,页内偏移量为4的地址。
⑥经过比较页号和页表长度,发现没有越界,则查询快表,此时快表中有相应的页表项。那么操作系统就能通过快表的相应页号,直接查询内存块号
⑦再通过内存块号和页内偏移量就可以得到目标物理地址
因为地址变换就需要查询慢表,而访问慢表需要100us的时间,若引入快表,快表中有相应的页表项,那么地址变换就只需要1us的时间。
注:快表中存放的是页表的一部分副本,因为快表造价高,容量小,不足以放下所有副本。
这里补充一下快表和Cache的区别,不知道Cache原理的可以先看:
这样知识点就融会贯通了,即得到物理地址后,看Cache中是否有对应物理地址,若有,则直接访问Cache即可,这样极大提高了CPU访问速度,若没在Cache中,那么就从主存中访问目标数据(访存)。并将主存块调入Cache:
① 快表中存储的是页表项的副本;Cache中存储的是主存块的副本,每个主存块是相对较大的,如下图所示有1KB,而每个页表项可能只有4B左右
② 加入快表,是使逻辑地址到物理地址的转变速度更快,即可以减少一次访存,而Cache是会加快CPU对主存的访问效率,若Cache中有想要访问的目标数据,那么直接访问速度更快的Cache,无需访问主存
总结:
① CPU给出逻辑地址,由某个硬件算得页号、页内偏移量,将页号与快表中的所有页号进行比较。
② 如果找到匹配的页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此若快表命中,则访问某个逻辑地址仅需一次访存即可。
③ 如果没有找到匹配的页号,则需要访问内存中的页表,找到对应页表项,得到页面存放的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此若快表未命中,则访问某个逻辑地址需要两次访存(注意:在找到页表项后,应同时将其存入快表以便后面可能的再次访问。但若快表已满,则必须按照一定的算法对旧的页表项进行替换)
由于查询快表的速度比查询页表的速度快很多,因此只要快表命中,就可以节省很多时间。
因为局部性原理,一般来说快表的命中率可以达到 90%以上。
补充:
时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。
空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。(因为很多数据在内存中都是连续存放的)
上面介绍的基本地址变换机构中,每次要访问一个逻辑地址,都需要查询内存中的页表。
由于局部性原理,可能连续很多次查到的都是同一个页表项。
以上的1+100是快表中有相应页表项的时间,先访问快表(1us),若快表中有相应的页表项,则直接访问内存(100us)
1+100+100是快表中没有相应页表项的情况,即先访问快表(1us),没有相应的页表项,则先访问内存(慢表)(100us),计算得到内存块号后,在访问内存(100us)
若未采用快表机制,则访问一个逻辑地址需要100+100=200us
注:若慢表和快表同时查找,那么从(1+100+100)*0.1到(100+100)*0.1
经过下图可知,若慢表和快表同时查询,当查询快表使用1us的时间时,系统已经花费1us的时间查询慢表了,所以查询慢表的时间-1。
总结:
其实到这里我们就已经有CPU访问主存的大致框架了,当CPU访问主存时,主存中没有对应数据,就需要到磁盘中调入,对于替换主存中的哪一内存块,就涉及到页面置换算法。
若主存中有相应地址,CPU通过将逻辑地址转换为物理地址,得到物理地址,若物理地址存在于Cache中,直接在Cache中访问,若没在Cache中,那么就从主存中访问目标数据(访存)。并将主存块调入Cache。
这就连贯了以下过程:
•两级页表
单极页表存在的问题:
某计算机系统按字节寻址,支持32位的逻辑地址,采用分页存储管理,页面大小为4KB,页表项长度为 4B。
4KB=2^12B,因此页内地址要用12位表示,剩余20位表示页号。
因此,该系统中用户进程最多有2^20页。相应的,一个进程的页表中,最多会2^20=1M=1,048,576个页表项,所以一个页表最大需要2^20*4B=2^22B,共需要 2^22/2^12=2^10个页框存储该页表。
根据页号查询页表的方法:
K号页对应的页表项存放位置=页表始址+K*4
要在所有的页表项都连续存放的基础上才能用这种方法找到页表项
① 因此:需要专门给进程分配2^10=1024 个连续的页框来存放它的页表。页表很大,需要占用很多个连续的页框,并且这样丧失了非连续分配管理方式的优势。
② 根据局部性原理可知,很多时候,进程在一段时间内只需要访问某几个页面就可以正常运行了。因此没有必要让整个页表都常驻内存。
对于问题①的解决方法:
我们可将长长的页表进行分组,使每个内存块刚好可以放入一个分组(比如上个例子中,页面大小4KB,每个页表项 4B,每个页面可存放 1K个页表项,因此每1K个连续的页表项为一组,每组刚好占一个内存块,再讲各组离散地放到各个内存块中)
为了在各个分组中找到各分组的先后顺序,需要再建立一个页表,称为页目录表,或称外层页表,或称顶层页表。
两级页表的地址结构:
32位逻辑地址空间,页表项大小为4B,页面大小为4KB,则页内地址占12位
单级页表结构如下:
进程最多有 2^20 个页面,用 20 位二进制刚好可以表示 0~2^20-1 个页号。
由于页面大小=内存块大小,所以每个内存块就可存放 4K/4 =1K=2^10=1024个页表项
注:一个页号对应一个页表项,这里的相除使想得到内存块中可存放的页表项数。
我们将页表分为1024个页表,所以原来页表中页号为1024的页表项,变为了下面红框中的页表项。
两级页表的结构如下:
上图的一级页号的10个2进制位用于表示页表数量,二级页号的10个2进制位用于表示每个页表中的页表项数量。
为区分各个小页表(二级页表)的顺序而设置的页表即页目录表
页目录表记录的是(页表+内存块号),例如0号页表存放在3号内存块中
如何对两级页表结构的逻辑地址进行地址变换?
例:将逻辑地址(0000000000,0000000001,111111111111)转换为物理地址(用十进制表示)。
① 按照地址结构将逻辑地址拆分成三部分
② 从PCB中读出页目录表始址,再根据一级页号查页目录表,找到下一级页表在内存中的存放位置。例如,0号页表放在内存中的3号内存块中
③再根据3号内存块存放的二级页表,得到最终需要访问的地址
由于二级页号=0000000001,所以对应的块号是4,访问4号内存块
④结合页内偏移量得到目标物理地址,即4号内存块号结合页内偏移量:
内存块大小*内存块号+页内偏移量
对于②问题:进程在一段时间内只需要访问某几个页面就可以正常运行了。因此没有必要让整个页表都常驻内存。
可以在需要访问页面时才把页面调入内存(虚拟存储技术)。可以在页表项中增加一个标志位,用于表示该页面是否已经调入内存
若想访问的页面不在内存中,则产生缺页中断,然后将目标页面从外存调入内存。
注:缺页中断指的是正在执行的某个指令,想访问暂时没有调入内存的页面产生的,所以此中断信号与当前执行的指令有关,属于内中断。
对于多级页表需要注意:
1.若采用多级页表机制,则各级页表的大小不能超过一个页面。
例:某系统按字节编址,采用40位逻辑地址,页面大小为4KB,页表项大小为4B,假设采用纯页式存储,则要采用()级页表,页内偏移量为()位?
由于页面大小是4KB,而系统又是按字节存储的,所以页内偏移量为4KB=2^12B
页号=40-12=28位
页面大小=2^12B,页表项大小=4B,页面大小=内存块大小,则每个内存块可存放2^12/4=2^10个页表项
因此各级页表最多包含 2^10个页表项,需要 10 位二进制位才能映射到 2^10个页表项,因此每一级的页表对应页号应为10位。总共28位的页号至少要分为三级
如果只分为两级页表,则一级页号占 18 位,也就是说页目录表中最多可能有 2^18 个页表项,显然,一个页面是放不下这么多页表项的。
2.两级页表的访存次数分析(假设没有快表机构)
第一次访存:访问内存中的页目录表
第二次访存:访问内存中的二级页表
第三次访存:访问目标内存单元
而单级页表只需要进行两次访存,第一次访问页表,第二次访问目标内存单元
得出结论访问使用n级页表,访存次数为n+1次
所以两级页表在空间利用率上升的同时,牺牲了访存的时间,即访存的时间比单级页表长。
(2)基本分段存储管理
基本分段存储管理与与基本分页存储管理最大的区别就是离散分配时所分配地址空间的基本单位不同。
•分段
进程的地址空间会按照程序自身的逻辑关系划分为若于个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址。以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻。例如,0号段占据的是从80K开始的连续的7KB的地址空间。
由于是按逻辑功能模块划分,用户编程更方便,程序的可读性更高。例如,用户可用汇编语言(低级语言)写这样两条指令。
注:在用户编程时使用的是各个段名来操作各个段,但是在CPU具体执行时,使用的是段号这个参数,所以在编译程序中会将段名转换为段号。CPU在执行各个指令时是根据段号来区分各个段的。
分段系统的逻辑地址结构由段号(段名)和段内地址(段内偏移量)所组成。如:
段号的位数决定了每个进程最多可以分几个段,段内地址位数决定了每个段的最大长度是多少
在上述例子中,若系统是按字节寻址的,则段号占16位,因此在该系统中,每个进程最多有2^16=64K个段。
段内地址占16位,因此每个段的最大长度是 2^16=64KB
写程序时使用的段名 [D]、[X] 会被编译程序翻译成对应段号。单元、单元会被编译程序翻译成段内地址。
与分页存储管理的页表中很不一样的是,这里记录了段长,因为段长是不同的,而分页大小是相同的。
经过编译程序编译后,形成等价的机器指令:“取出段号为2,段内地址为1024的内存单元中的内容,放到寄存器1中”
机器指令中的逻辑地址用二进制表示:0000000000000010 0000000100000000
①一个进程上处理机运行之前,需要将进程切换相关的内核程序负责恢复进程运行环境,其中就包括很重要的硬件寄存器---段表寄存器,用于存放某进程的段表起始地址与段表长度。
所以段表的起始地址与段表长度在没有上处理机运行时是存放在进程的PCB中,当进程上处理机运行时,这两个信息会被放在段表寄存器中。当知道段表基址之后,就能知道段表存放的具体位置。
②系统根据逻辑地址A得到段号S和段内地址W,例如这里段号为2,段内地址为1024
③将段号与段表长度进行对比,判断段号是否越界若S≥≥M,则产生越界中断,否则继续执行
④若没有越界,查询段表,找到对应的段表项,段表项的存放地址为F+S*段表项长度
⑤找到对应的段表项后,系统会检查段内地址是否超过段长。若W≥≥C,则产生越界中断,否则继续执行。由于这里段长为6K,段内地址为1024,即1K,所以不会产生越界中断。
⑥知道了段表项在内存中存放的位置,那么我们将段基址与段内地址相加,就可以得到实际目标物理地址。接着就能访问目标内存单元了,由于基址是40K,段内地址为1024,所以
由于分页存储管理中,每个页面大小相同,而每个段长不同,所以需要将段长与段内偏移量进行检查。
•分段和分页管理的对比
1.页是信息的物理单位。分页的主要目的是为了实现离散分配,提高内存利用率。分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的。
段是信息的逻辑单位。分段的主要目的是更好地满足用户需求。一个段通常包含着一组属于一个逻辑模块的信息。分段对用户是可见的,用户编程时需要显式地给出段名。
2.页的大小固定且由系统决定。段的长度却不固定,决定于用编写的程序。
3.分页的用户进程地址空间是一维的,程序员只需给出一个记忆符即可表示一个地址。
分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址。例如下图,段名为D,段内地址是A。
4.分段比分页更容易实现信息的共享和保护。假设某生产者进程被分为3个段,1号功能段用来判断缓冲区此时是否可访问允许所有生产者、消费者进程共享访问
注:不能被修改的代码称为纯代码或可重入代码(不属于临界资源),这样的代码是可以共享的。可修改的代码是不能共享的(比如,有一个代码段中有很多变量,各进程并发地同时访问可能造成数据不一致)
如果采用"分页"的方式,如果让消费者进程的某个页表项指向这个页面,显然不合理,因为这个页面中的橙色部分是不允许共享的,只有绿色部分可以(也就是只有1号段可以共享)
所以对于分段而言,运行共享的段标记为允许其他进程访问,其他段标记为不允许被其他进程访问即可。
对于分页而言,1、2号页面只有一部分允许其他进程访问,因此很难用页表实现信息保护。
分页(单级页表):第一次访存--查内存中的页表,第二次访存--访问目标内存单元。总共两次
分段:第一次访存---查内存中的段表,第二次访存--访问目标内存单元。总共两次访存。
与分页系统类似,分段系统中也可以引入快表机构,将近期访问过的段表项放到快表中,这样可以少一次访问,加快地址变换速度。
(3)段页式存储管理
•分页和分段的优缺点
如下图所示,若一个分段为20MB的空间想要进入内存,则无法实现,虽然以下内存空间大小总和为20MB,但是这是不连续的空间。分段管理中产生的外部碎片也可以用“紧凑”来解决,只是需要付出较大的时间代价。
•段页式管理的优点
如下图所示,将进程按逻辑模块分段,再将各段分页(如每个页面4KB)。
再将内存空间分为大小相同的内存块/页框/页帧/物理块,进程的各页面分别装入各内存块中。
段页式系统的逻辑地址结构由段号、页号、页内地址(页内偏移量)组成。如:
在上述例子中,若系统是按字节寻址的,则段号占16位,因此在该系统中,每个进程最多有2^16=64K个段
页内偏移量占12位,因此每个页面\每个内存块大小为2^12=4096=4KB
注:“分段”对用户是可见的,程序员编程时需要显式地给出段号、段内地址。而将各段“分页”对用户是不可见的。系统会根据段内地址自动划分页号和页内偏移量。
所以逻辑地址结构由段号、页号、页内地址(页内偏移量)组成。但是编程时只需要显式的给出段号和段内地址,之后系统会自动地将段内地址拆分为页号和页内偏移量。
•段表、页表
一个段表项,每个段表项由段号、页表长度、页表存放块号(页表起始每个段对应地址)组成。每个段表项长度相等,段号是隐含的。
根据块号可以算出页表存放的内存地址,如下图,我们可以知道0号段对应的页表存放块号为1
于是从1号块中就可以读出0号段对应的页表,0号段长度为7KB,而每个页面大小为4KB,所以会被分为两个页面
这两个页面就会依次对应页表当中的一个页表项,每个页表项由页号、页面存放的内存块号组成。每个页表项长度相等,页号是隐含的。
分段式管理中记录的是段号,段表长度和段在内存中的起始地址,而段页式存储记录的是段号,页表长度,以及页表存放块号。
2.对于页表而言,段页式的页表格式与分页式管理的页表格式相同,都是记录了页号与内存块号的映射关系。
3.一个进程只会对应一个段表,但是每个段会对应一个页表,所以一个段表会对应多个页表。
①一个进程上处理机运行之前,需要将进程切换相关的内核程序负责恢复进程运行环境,其中就包括很重要的硬件寄存器---段表寄存器,用于存放某进程的段表起始地址与段表长度。
所以段表的起始地址与段表长度在没有上处理机运行时是存放在进程的PCB中,当进程上处理机运行时,这两个信息会被放在段表寄存器中。当知道段表基址之后,就能知道段表存放的具体位置。
③将段号与段表长度进行对比,判断段号是否越界,若S≥≥M,则产生越界中断,否则继续执行
④ 查询段表,找到对应的段表项,段表项的存放地址为F+S*段表项长度
由于各个段长不相等,各个段分页后,页面数量可能不同,所以要检查页号是否越界,若页号≥≥页表长度,则发生越界中断,否则继续执行
⑤根据页表存放块号、页号查询页表,找到对应页表项。找到页表项后就可以知道这个页面在内存中存放的位置
⑥ 根据内存块号、页内偏移量得到最终的物理地址,就是将这两个数据的2进制进行拼接。
由上图我们可以知道,总共需要3次访存:第一次--查段表、第二次--查页表、第三次--访问目标单元。