早期计算机都是直接访问物理内存的,这样想在内存中同时运行两个程序是不可能的,想想为什么?
下面给出三种存储模型:
第一种和第三种均不常用了,因为用户程序一旦出现错误,可能会销毁OS,当按上述方式装载程序时,新装载的程序会覆盖掉先前装载的程序。唯一能并行的方法就是使用多线程,但是会共享信息,所以不可行。
后来提出内存键的概念来区分在内存中多道程序,此时内存中可以装载多道程序,但是一个程序可能因为jmp指令跳转到另一个程序从而发生程序崩溃。这都是因为使用了绝对地址产生的问题,一种解决办法是采用静态重定位的方法,比如一个程序装入到16000地址位,则程序中地址数都要加上16000这个常数,虽然这种方法一般来讲是可行的,但是无法辨别它是重定位的地址还是不是重定位的地址,来了一个访问地址,那这个访问地址加不加16000,而且该方法会减慢装载速度。
一种存储器的抽象:地址空间
要想多个程序同时处于内存中就需要解决两个问题:保护和重定向。我们希望每个程序都有自己独立的一套地址空间。
一个简单的方法是使用动态重定位,利用基址寄存器(存放程序开始地址)和界限寄存器(存放程序大小),当指令读或写数据字前,CPU硬件会将其发送到内存总线前与基址寄存器中的值相加,并判断相加后的指令是否越界。但是每次都要做加法运算和比较运算就会显得很慢。
交换技术的出现
将所有进程都装载入内存是不大可能的,一种策略是将空闲进程存入磁盘,将需要使用的进程整个装入内存;另一种策略是虚拟内存
交换在内存中产生了多个空闲区,通过把所有内存尽可能向下移动,有可能将这些小的内存区合并为大的内存区,这称为内存压缩,但通常不进行该操作,因为非常耗时,而且OS需要准确的按其需要的大小分配内存。
但是如果数据段可以增长,例如,很多程序语言都允许从堆中动态分配内存。所以我们可以为其预留一部分空间。
其中,堆栈存放私有变量和返回地址,向下增长。数据段作为堆使用供变量动态分配和释放,向上增长。
空闲内存区管理-位图方法
内存可能被划分为小到几个字,大到几千字节的分配单元,每个分配单元对应位图中的一位,0表示空闲,1表示占用。在分配一个k个分配单元的进程时,需要在位图中查找k个连续的分配单元进行分配,这是非常耗时的。
空闲内存区管理-链表
还有一个方法是维护一个记录已分配的内存段和空闲内存段的链表,设当X进程结束后同时需要合并内存。
当然,这里有很多算法可以分配内存,有首次适配法,下次适配法,最佳适配法(会生成更多的小的空闲区,可以考虑最差适配法)等。
虚拟内存
为了防止某程序过大,最开始采用覆盖块的方法,即手动切割程序成一个个小块,但是怎么切割是个问题。于是这个问题干脆交给计算机去做,虚拟内存就诞生了。其基本思想是:
每个程序都有自己的基本空间,这个空间被分成多个块,每个块称作一页和页面。(注意:虚拟内存就是利用磁盘空间来扩大内存,所以称为虚拟)
放张图就明白了:
这里,我们通过页表(基址+偏移量)来管理页面,也可以加上TLB(块表),计算机组成原理的内容吧。有时,单个页表是不够表示页面的,所以我们可以采用二级页表或多级页表,来看下面一张图:
有页面自然也有页面置换算法,这些算法有最优页面置换算法,最近未使用置换算法,先进先出置换算法等
在考虑如何交换时,如果考虑换出单个进程中最小生存时间的页面称为局部页面置换算法,考虑换出整个内存中最小生存时间的页面称为全局页面置换算法。通常情况下全局算法较好。另一种途径是为进程平均分配页面,剩余放入公共池里面。
我们也可以采用测试缺页中断率的方法。
同时,我们也应该选则合适的页面大小,在共享方面,一般只读的页面可以作为共享页面来减少内存消耗。
共享库
在静态链接.o程序时,会造成很大的内存消耗,因为要链接不同库文件,这些库文件直接装载至内存。但是共享库(又称动态链接库,DLL),只会装载一小段能够在运行时绑定被调用函数的存根例程,即用什么函数,才装入对应的页面而不是整个文件装入。当然,如果其它程序装载了该共享库,则本程序就不需要装载它了。
另外,如果DLL文件更新了,其并不需要重新编译执行,用户只需要下载更新的DLL文件下次启动时即可使用。
来看两个进程使用共享库,需要用相对地址:
共享库实际上是内存映射文件的一个特例。
如何进行缺页中断处理?
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !