操作系统 内存篇

概述

内存的调度是操作系统一个重要的话题,所有的程序都是运行在内存中。内存的分类和内存的管理调度是怎样的呢?

我们先来看看一个程序的运行过程:程序的运行主要分为编译 汇编 链接 运行这四个步骤,程序源码经过编译 汇编两步后转换为.o文件是汇编语言。然后通过链接来将公共的函数库引入进来,最后装载到内存中运行。

在这个过程中,程序本身去看内存是连续的,例如我们一个程序始终认为他有从0开始的连续可用的一段内存。这叫做逻辑地址。而实际上的物理地址并不是从0开始连续可用,有其他的程序占用了内存,内存本身也不一定连续。实际的内存地址我们叫做物理地址。因为两个地址不可能一致,因为我们程序肯定不知道当前的内存情况,所以只能认为有一段连续的内存供我使用。实际的内存则需要重新分配。所以需要有一种映射关系来维护逻辑和物理地址,这样程序中如果按照逻辑地址0去寻址的时候能找到对应的实际的具体物理地址。具体参考下面的内存分配。

又因为每个程序的内存都是分布在主存中,每个程序必须独占自己部分的内存区域不被其他程序篡改,所以操作系统还需要保护每个程序的内存区域实现隔离。

内存分类

内存主要分为cache主存虚存
所谓cache就是cpu自带的三级缓存(有些cpu只有二级)。主存则是我们平时所说的内存条,虚存顾名思义是虚拟内存一般是磁盘中的一部分空间。
三级缓存123距离cpu越来越远,大小越来越大,速度越来越慢。但是相比于主存和虚存是高速的存储区,cpu快速读写的区域。

内存分配

1.连续内存分配

因为逻辑地址是连续的,所以我们最容易想到的物理地址的分配也是连续的。例如一个程序占用0-100这么多内存地址,那么我们可以找到一块空闲的物理地址100-200来分配给这个程序。内存映射则只需要记住100这个偏移,在逻辑地址上+100就是对应的物理地址。

连续内存分配的方法主要有最先最优和最差三种方式,即最先找到的空闲内存大于程序所需内存的,找出所有可用内存块大小刚好满足的,以及相差最大的。 三种方法都会不同程度的导致内存碎片问题。内存碎片就是不能利用的内存,分为内外两种。外部碎片就是两个程序占用的内存块中间的区域,因为大小不合适不能被其他程序利用。而内碎片则是程序内部时钟无法被使用的内存。

2.非连续内存分配

因为连续内存分配导致了大量的内存碎片的产生使得内存连用率低下,所以有了非连续内存分配。非连续内存分配的基本思想是程序的逻辑地址虽然是连续的,但是映射到物理地址上可以是零散的,只要维护好对应关系就可以保证正常的寻址。

2.1分段

一种非连续分配的思想是分段,分段是对程序友好的,将一个程序分为四个段:CS是代码段,DS是数据段,SS是堆栈段,ES是附加段。当然早期的思想中可能会有更多的段,段的数目和大小是灵活可调的。分段便于隔离和共享某些特定的内存。然后每个段分别映射到内存的连续区域。

例如一个程序逻辑地址还是0-100,四个段的逻辑地址分别是0-25-50-75-100。那么映射到内存中的时候可以是100-125,200-225,300-325,400-425这样不连续的四个区域。这样将内存的粒度降低了,就减少了内存碎片的问题。

那么在程序执行的过程中如何知道逻辑地址0对应的是物理地址100而逻辑地址26对应物理地址200呢?这用到了cpu的段寄存器其中存储了程序中段号、物理内存段号以及该段首的物理地址。这样例如我们上面四个段号为1-4,在段寄存器中就存储了段号1对应物理地址100,段号2对应物理地址200…这样的内容。于是我们的逻辑地址的定义方式进行了修改不再是原来的0-100,而是段1:0-25,段2:0-25,段3:0-25,段4:0-25。当我们对段1:0这个逻辑地址寻址物理地址的时候会去查段表对应的是100为首地址的内存偏移是0,所以最后寻址结果是物理地址100+0这个地址。

2.2 分页

遗憾的是分段的方法只能将程序分为较大的几个段,如果分的段太多又导致段寄存器存储不过来。较大的段稍微降低了颗粒度,还是有很多内存碎片。我们需要更小的“段”,和更多的“段表”存放空间。于是有了分页,分页可以简单的说成是更细化的分段,分页规定页大小不能改变。每个操作系统的页大小是不变的,例如页大小512。我们在程序中将逻辑内存全部分成页page,在物理内存中同样也需要分页才能做对应。物理内存中我们一般叫做帧frame。

还是那个例子0-100的内存,此时我们以10为pageSize将其分为10页,他们分别对应物理内存中十个帧。和段十分相似,我们也需要有个表记录对应关系,这里是页表。因为页的大小固定所以页号x页大小就是该页的首地址了。页表中存放的信息是页号–帧号。同样类似段中的做法,我们将逻辑地址的表示方法改成了页1:0-10,页2:0-10….这种了。例如(3,5)表示第三页5个偏移的地址,逻辑地址为3x10+5=35。

在cpu寻址的时候发现(3,5)的逻辑地址则到页表中找到页号3对应的帧号假如是7,则7x10+5=75就是他对应的实际物理地址。

页表中还会存一些其他东西,我们之前没有提的是,其实逻辑地址空间一般总是大于实际地址空间的,例如一个win32的程序他认为自己可以操作的是4个G的内存,而实际可用的内存可能只有1个G。这样就会导致页号没有对应的帧号,此时的操作可能会产生一个页缺失的异常。页表中对于这种常见的情况专门设置了一些标志位。

2.2.x 页表的设计

我们之前说段不能太细的一条原因是段寄存器存不下那么大的段表,实际上分页就是细化了分段,那么页表的存储也面临问题。尤其是程序的逻辑地址范围很大,例如上面举例的4个G。这个程序的页表就会很大因为页大小通常很小。解决页表过大的方法有两种:
1 缓存–CPU中的TLB区域缓存近期访问的页-帧对应项。
2 多级–多级页表的设计,例如上面的例子的4G逻辑内存的程序的页表,可能只有1G是能对应到物理地址的,此时页表却还要把4G的页都写上对应关系条目。而如果是两级页表第一级有4个条目1234,分别对应一个二级页表的存储地址,于是234的项就是空了。所以只需要1号一级页表及其下属的二级页表的内容,减少了3/4的存储量。

2.3 段页式存储管理

分段对程序是友好的更好的分离了程序的逻辑结构,便于共享和保护。而分页则极大的颗粒化了内存,解决了内存碎片的问题,提高了内存利用率。将两者结合起来就形成了段页式内存管理。

首先程序的逻辑内存被分为多个逻辑段,然后每个段被分为多个页。此时主存中只需要按帧来栅格化。程序中的地址表示变为(段号,页号,页内偏移)。操作系统为每个程序设置一个段表,内容为段号–页表地址,每个段有自己的页表,内容为页号—帧号。

程序运行中cpu寻址(1,3,10),则找到段表中1号段对应的页表的地址,然后找到该页表,找到3号页对应的帧号。假如对应帧号100则100x10+10=1010就是其对应的物理地址。【前一个10是我们假设的pageSize,后一个10是页内偏移】。

虚拟内存

一句话概括虚存就是:不常访问或不急迫的内存块放到磁盘存储。出现的原因是随着应用内存需求增大,为了满足多个程序同时运行的需求,主存的空间不够用的情况下,需要放到虚拟内存(磁盘)中存储一部分暂时不需要访问的数据。

虚拟内存早期就有原型,例如之前dos系统内存只有几M的时候要跑几百M的程序,需要程序员写程序的时候调度谁先执行其他放磁盘,然后谁再执行,这样的顺序。后来有了swapping自动交换技术,可以将暂时不着急使用的程序占用的内存块整个放到磁盘中。例如一个sleep的程序。不过swapping粒度是整个程序。

虚存技术可以将程序的一部分暂时不用的内存放到磁盘中,实现了更细的粒度。听上去虽然简单但是实际上是很复杂的,尤其是cpu寻址内存的时候,查页表。如果放到虚存中的会有标志位,这时候需要将虚存中的对应数据拿回内存,然后修改页表对应的帧号。不过如果恰好没有空闲的帧,则还需要将不那么急迫需要的内存换到虚存中去。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值