操作系统内存管理-原理

          任何新技术都是在一点一点的积累中成熟并呈现在世人的面前,就像猿人进程成人也不是一簇而就的,而是在漫长的岁月中一点一点的进化与完善。还比如现代的吸尘器,当前发明吸尘器的那个人只是用了一台风扇的电机和叶片以及一个布口袋制成的,后来也是一点一点的改善成为现在的使用方便的吸尘器。

          操作系统的内存管理也是同样的道理,起初的操作系统并没有现代操作系统的虚拟内存管理机制,而是指令直接访问物理内存,并且在内存中同一时间只能运行一个进程,因为如果是多个进程,进程A中的指令有可能会修改进程B的内存地址,造成进程B崩溃或错误的计算结果。

        后来开始支持多任务也就是同时可以运行多个进程,为每个进程都分配一块独立的内存地址并写有保护位,其它进程指令是无法访问当前进程的地址空间的,并且利用上了cpu的基址寄存器和界限寄存器来表达当前进程的物理地址访问开始端和范围,开始支持程序指令以相对内存地址运行。支持多任务固然是好,让cpu的利用率大大提高,提高了工作效率,同时让程序以相对地址访问更加解放了程序员的工作。可是多进程带来的问题就是内存空间大小的局限性,就算有1G内存空间,如果我的程序加上正文加上数据算100M,也就最多能运行10个,这还不算操作系统占用的,所以人们后来就发明了交换技术,当内存无法同时容纳多个程序是,就根据策略比如很久以后才会运行的进程,就将程序的正文还有堆栈以及程序计数器这些进程映像存储到硬盘上,当运行时再次装入内存,恢复寄存器和程序计数器,这样虽然解决了内存空间有限的问题,但是也带来了新的问题,程序数据在内存与硬盘上的往复交换带来了大量的io读写磁盘操作,大大降低了程序的运行效率。

        经过以上的两个阶段的问题与整理,人们终于走上了虚拟内存的道路,现代的大多操作系统内存管理都采用分页和分段的内存管理机制,所以下面我们详细的说一下机制。

       所谓虚拟内存,简单的来说就是程序指令访问的内存地址不是真的内存的物理地址,而是需要一个转换过程才能访问到物理地址,读取或存取数据。

       在操作系统中让运行的每个程序拥有自己的空间地址,这个地址被分成多个块,每个块被称为一页或“页面”,每一页有连续的虚拟内存地址,每一个页都对应物理内存中的一块,这种块被称为“页框”,页框和页面同等大小,页面与页框的对应关系被称为页表存储在进程的地址空间中。当指令访问一个内存地址是,经过硬件(cpu中的mmu,负责虚拟地址转换,每一个程序运行时,就会将页表信息设置mmu,让mmu知道页面与页框的关系,从而快速计算出实际物理地址)快速计算出实际的物理地址,读取地址获取数据,如果发现访问的虚拟地址不存在,则有操作系统负责将缺失的页面由磁盘装如内存,并更新页表中页面与页框的对应关系。

       页表中存储着页面与页框的映射关系,当cpu发出一条访问内存的指令,mmu会根据内存地址的前几位在页表中找出对应的页面与页框的记录,从而知道了页框的地址,采用地址的后几位作为物理地址区间页框的偏移量,这样找到了页框并且知道了该页框上的位置,就知道了访问的真实的内存地址。

      页表中还保存了一些保护位。第一个是状态位,就是页面是否在内存中,如果存在则根据页框与页面的对应关系找出物理地址,如果不存在,则引起缺页中断有操作系统将缺失页面装入内存并更新页表。第二则是保护位,记录只读或可读写,第三是记录页面修改情况,以便在将页框中的信息置换到硬盘时写入磁盘还是直接丢弃。第四个是禁止高速缓存位,如果操作系统一直在读取软盘上的数据,保证cpu读取到的数据只是最新的是很有必要的,所以要设置这个位。

     当页框也就是物理内存不够使用的时候,操作系统会根据一些页面置换算法将内存中的数据置换到磁盘上的交换空间(swap),腾出空闲的页框来存储需要在内存中运行的程序和相关数据。

     在所有的页面置换算法中,以老化算法应用最广泛和最实用,下面大概说一下这个页面置换算法。

     在cpu刚使用的页面很可能在后面的几条指令中被使用,反过来说,已经很久没有使用的页面在未来的一段时间很可能仍然不被使用,因为程序指令是顺序执行的,当然存在在jmp这样的跳转,可是大多数情况,程序指令还是以此来执行的。老化算法就是根据这个思想来实现的,在发生缺页中断时,置换很久没有使用的页面,这个策略被称为LRU。

    老化算法的大概实现思路是,在内存中存储一个数据结构,里面存储页面的使用次数,每次时钟滴答后,将内存中页面的引用次数加到数据结构里面的数值上,每次先将数值右移一位(1000=>0100)然后将本次使用设置到数值的最左边((使用)1100 不使用0100),然后在发生页面替换时,则替换数值最小的那个也就是使用次数最小的.

    下面说一下操作系统中发生缺页中断时的处理流程

    1.当前进程的某条指令被放入cpu,cpu发出指令访问内存,该地址不存在,系统则陷入内核。

    2.内核首先存储当前进程的堆栈寄存器以及程序计数器来保存当前进程的进度印象,为处理完缺页中断后继续运行当前程序做准备。

    3.操作系统根据cpu中寄存器发现发生缺页中断的内存地址。如果寄存器中没有,则必须检索上一部保存的程序计数器来发现发生缺页中断的虚拟内存地址

    4.发现了需要处理的地址后,系统检查这个地址是否有效,是否越界。如果不符合检查条件,则杀掉该进程,继续运行别的进程。当符合条件。则系统检查是否有空闲页框,如果不存在空闲页框,则调用页面置换算法寻找一个页面调换出去,为此页面腾出一个页框。

    5.发现需要置换的页面后,则检查该页面是否属于脏页面也就是数据装入内存后被修改过,则需要调用程序将此数据写回磁盘,并发生一次上下文切换,挂起当前处理缺页中断的进程,让其他进程继续运行。

    6.当页面干净后,则调用io程序将数据从磁盘写入内存,并挂起当前进程,让系统继续调度其他待运行进程。

    7.数据装入页框后,挂起进程被唤醒。系统将程序计数器以及各个寄存器以及堆栈恢复到cpu和产生缺页中断的进程空间地址中,继续运行产生缺页中断的进程。

     在操作系统中,一个进程包含数据,程序正文,还有堆栈,这些数据都放在一个进程地址空间中,堆栈会随着程序的执行变大和变小,数据也会跟着程序的进度而变化,当将这些不同类型的数据都放在一起时管理起来是很复杂的,比如堆栈的变大需要更多的内存空间时,是发生缺页中断还是增加进程地址的内存空间。其实作为程序员的我们没有必要关心这个,这些完全可以交由系统去做,故现代的操作系统采用了分段技术,就是将一个程序的进程地址空间分为 数据段 程序指令正文段 堆栈段 还有全局常量,将这些不同类型的数据放入不同的空间并维护自己的页表,这样的几个独立的数据空间,空间扩容或减小都有操作系统携带分页程序来管理,程序员不需要关心这些。

     

     总结:我们大概说了一下操作系统的内存管理机制,其实无非就是通过各种手段去调度交换,让有限的资源得到最大程度的利用。分页处理或分段处理也是通过分页程序或分段程序,将有限的内存空间放入最需要的数据。这也是人们在一步一步发展中总结出来的解决问题的办法的展示。

     

posted @ 2014-03-24 08:19  阿正-WEB  阅读(2946)  评论(5编辑  收藏  举报