操作系统内存管理

内存管理
  • 虚拟地址寻址

    ​ 运行多个程序可能出现问题:进程1给某个物理地址比如0x70写入数据,现在切换到进程2,也给这个相同的物理地址写入数据,就发生覆盖了,这样的后果很严重,可能造成系统崩溃等。于是「虚拟地址」就出现了,大家都有自己的「虚拟地址」,可以利用它访问到自己的数据而且不会冲突,这是因为操作系统会提供一种机制,进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存。 操作系统是如何管理虚拟地址与物理地址之间的关系?主要有两种方式,分别是内存分段和内存分页

  • 内存分段

    ​ 程序是由若干个逻辑分段组成的,如可由代码分段、数据分段、栈段、堆段组成。不同的段是有不同的属性的,所以就用分段(Segmentation)的形式把这些段分离出来。虚拟地址由两部分组成,段选择子段内偏移量,就可以找到真实物理地址。段选择子就保存在段寄存器里面,段选择子里面最重要的是段号,用作段表的索引,段号存在段表中,包括段基地址+段界限+特权等级(用于权限判断,比如内核级和用户级)。 内存分段后,段界限代表内存中这一段的总长度,段基地址代表着是哪一段起始地址,再加上段内偏移量,就可以找到这个虚拟地址的位置。

    但分段也有缺点:

    • 第一个就是内存碎片的问题。

      • 外部内存碎片。也就是产生了多个不连续的小物理内存,导致剩下的连续空闲区可能对于新申请的大小来说太小了。 解决办法就是就是内存交换。 比如Linux 的Swap 空间,这块空间是从硬盘划分出来的,用于内存与硬盘的空间交换。交换过程就是把内存中的内容拷到硬盘,再拷回来的时候邻接着其他程序的内存区域,避免有空隙造成碎片。

        ​ 以及程序所有的内存都被装载到了物理内存,但是这个程序有部分的内存可能并不是很常使用,这也会导致内存的浪费;

    • 第二个就是内存交换的效率低的问题。

      ​ 如果内存交换的时候,交换的是一个占内存空间很大的程序,这样整个机器都会显得卡顿。

    为了解决这些问题,于是就出现了内存分页:

  • 内存分页。为什么要分页

    ​ 总体而言可以认为分段内存的管理粒度太粗了,所以 intel 80386 就出来了个分页管理,一个更加精细化的内存管理方式。简单地说就是把内存等分成一页一页,每页 4KB 大小,按页为单位来管理内存。你看按一页一页来管理这样就不用把一段程序都加载进内存,只需要将用到的页加载进内存。这样内存的利用率就更高了,能同时运行的程序就更多了。并且由于一页就 4KB, 所以内存交换的性能问题得以缓解,毕竟只要换一定的页,而不需要整个段都换到磁盘中。

    ​ 在分页机制下,虚拟地址分为两部分,页号页内偏移。页号作为页表的索引,页表包含物理页每页所在物理块号,这个块号与页内偏移的组合就形成了物理内存地址

    为什么分页有效?

    • 时间局部性:一条指令的一次执行和下次执行,一个数据的一次访问和下次访问都集中在一个较短时间内;

    • 空间局部性:当前指令和邻近的几条指令,当前访问的数据和邻近的几个数据都集中在一个较小区域内。

    分页缺点?

    • 内部内存碎片,内部碎片就是已经被分配出去却不能被利用的内存空间。比如一页4K,但这一页装不满(用了3K),于是形成了内部碎片。
    计算题:十进制逻辑(虚拟)地址9612,一页8K,页表中,对应页号为1,物理块号为3,则实际物理地址:
    	每页8K(十六进制)=8192(十进制),9612(十进制)=8192(十进制)+1420(十进制)。
    	从地址映射表可以看到页1对应物理块3,因此地址9612(十进制)的物理存储位置=8192(十进制)× 3 + 1420(十进制)= 25996。
    
  • 多级页表

    ​ 在32位系统,一个进程可能访问整个4G内存,则至少要有 4MB 的内存来(4G/4K≈2^20=4MB)存储页表。看起来不大,但要是有100个进程,那就要400MB的页表,而实际上操作系统有上千个进程都很正常,那这MMU岂不是要比内存还大了?更别说64位系统了。所以提出了多级页表。

    • 二级分页

      ​ 对这4MB数量再分,分成1024(1级页表)*1024(2级页表),1级页表里面存1级页号+2级页表的地址,2级页表存2级页号+物理页号,就相当于1级页表去引用对应的2级页表,组合起来寻址, 这样好处是充分利用了局部性原理,大部分的1级页表其实都不会用到,假如只有%20用到了,只需要把20%对应的2级页表加进来就足够了,4KB(一级页表) + 20% * 4MB(二级页表)= 0.804MB,这就省下了大量的空间。

    • 多级分页

      ​ 对于64位系统,两级分页肯定不够了,就变成了四级目录:

      ​ 全局页目录项 PGD(Page Global Directory);上层页目录项 PUD(Page Upper Directory);中间页目录项 PMD(Page Middle Directory);页表项 PTE(Page Table Entry);

  • TLB(快表)

    ​ TLB(Translation Lookaside Buffer)通常称为页表缓存、转址旁路缓存、快表等。利用程序的局部性,把最常访问的几个页表项存储到访问速度更快的硬件,即TLB,专门做地址转换的工作,放在CPU中。CPU进行虚拟地址寻址时查TLB,若未命中出现缺页中断,才继续查常规的页表。TLB 的命中率其实是很高的,因为程序最常访问的页就那么几个。

  • 段页式内存管理

    ​ 将分页和分段机制结合起来。先将程序划分为多个有逻辑意义的段,接着再把每个段划分为多个页。每一个程序一张段表,每个段又建立一张页表,段表中的地址是页表的起始地址,而页表中的地址则为某页的物理页号。

    ​ 访问时:第一次访问段表,得到页表起始地址;第二次访问页表,得到物理页号;第三次将物理页号与页内位移组合,得到物理地址。

  • Linux 内存管理

    ​ Linux 内存主要采用的是页式内存管理。早期 Intel X86 CPU 一律对程序中使用的地址先进行段式映射,然后才能进行页式映射,因此Linux将每个段起始地址设为0,所有程序都一样,这样做相当于逻辑上屏蔽了段式内存的概念。

  • 缺页中断

    ​ 缺页中断就是要访问的页不在主存中,需要操作系统通过将页调入主存后再进行访问,此时会暂时停止指令的执行,产生一个页不存在的异常,对应的异常处理程序就会从选择一页调入到内存,调入内存后之前的异常指令就可以继续执行。

    页面置换算法

    ​ 当需要调入新的页面到内存中而内存已满时,需要用算法淘汰一些旧页面。常用的算法有:LFU、LRU、FIFO、最优页面置换算法(预测哪页下次访问的到来最久,不现实但可以作为评价依据)、第二次机会页面置换算法(是FIFO改进版,为了避免移走常用的,多给一次机会,设置一个修改位R,R为0代表没有2次用过,直接淘汰;R为1则说明2次访问过,R置为0页面放到链尾)、时钟页面置换算法(二次机会的改进版,变成环形链表,像时钟一样,R为0直接将新表插到此处;否则R为1,那么R置为0,移动指针到下一位,一直移动指针找到R为0)。

posted @ 2021-04-20 20:22  i%2  阅读(107)  评论(0)    收藏  举报