操作系统为何要进行页面置换呢?这是由于操作系统给用户态的应用程序提供了一个虚拟的“大容量”内存空间,而实际的物理内存空间又没有那么大。所以操作系统就就“瞒着”应用程序,只把应用程序中“常用”的数据和代码放在物理内存中,而不常用的数据和代码放在了硬盘这样的存储介质上。如果应用程序访问的是“常用”的数据和代码,那么操作系统已经放置在内存中了,不会出现什么问题。但当应用程序访问它认为应该在内存中的的数据或代码时,如果这些数据或代码不在内存中,则根据上一小节的介绍,会产生页访问异常。这时,操作系统必须能够应对这种页访问异常,即尽快把应用程序当前需要的数据或代码放到内存中来,然后重新执行应用程序产生异常的访存指令。如果在把硬盘中对应的数据或代码调入内存前,操作系统发现物理内存已经没有空闲空间了,这时操作系统必须把它认为“不常用”的页换出到磁盘上去,以腾出内存空闲空间给应用程序所需的数据或代码。
操作系统迟早会碰到没有内存空闲空间而必须要置换出内存中某个“不常用”的页的情况。如何判断内存中哪些是“常用”的页,哪些是“不常用”的页,把“常用”的页保持在内存中,在物理内存空闲空间不够的情况下,把“不常用”的页置换到硬盘上就是页替换算法着重考虑的问题。容易理解,一个好的页替换算法会导致页访问异常次数少,也就意味着访问硬盘的次数也少,从而使得应用程序执行的效率就高。本次实验涉及的页替换算法(包括扩展练习):
在QEMU里实际上并没有真正模拟“硬盘”。为了实现“页面置换”的效果,我们采取的措施是,从内核的静态存储(static)区里面分出一块内存, 声称这块存储区域是”硬盘“,然后包裹一下给出”硬盘IO“的接口。思考一下,内存和硬盘,除了一个掉电后数据易失一个不易失,一个访问快一个访问慢,其实并没有本质的区别。对于我们的页面置换算法来说,也不要求硬盘上存多余页面的交换空间能够“不易失”,反正这些页面存在内存里的时候就是易失的。理论上,我们完全可以把一块机械硬盘加以改造,写好驱动之后,插到主板的内存插槽上作为内存条使用,当然性能就别想了。(如果半导体工业没有发明出成本和访问速率都介于寄存器和硬盘之间的ram, 我们将不得不这么做!)那么我们就把QEMU模拟出来的一块ram叫做“硬盘”,用作页面置换时的交换区,完全没有问题。你可能会觉得,这样折腾一通,我们总共能使用的页面数并没有增加,原先能直接在内存里使用的一些页面变成了“硬盘”,只是在自娱自乐。确实,我们在这里只是想介绍页面置换的原理,并不关心实际性能。
复制 // kern/driver/ide.c
/*
#include"s
*/
void ide_init ( void ) {}
#define MAX_IDE 2
#define MAX_DISK_NSECS 56
static char ide[MAX_DISK_NSECS * SECTSIZE];
bool ide_device_valid ( unsigned short ideno) { return ideno < MAX_IDE; }
size_t ide_device_size ( unsigned short ideno) { return MAX_DISK_NSECS; }
int ide_read_secs ( unsigned short ideno , uint32_t secno , void * dst ,
size_t nsecs) {
//ideno: 假设挂载了多块磁盘,选择哪一块磁盘 这里我们其实只有一块“磁盘”,这个参数就没用到
int iobase = secno * SECTSIZE;
memcpy(dst , & ide[iobase] , nsecs * SECTSIZE) ;
return 0 ;
}
int ide_write_secs ( unsigned short ideno , uint32_t secno , const void * src ,
size_t nsecs) {
int iobase = secno * SECTSIZE;
memcpy( & ide[iobase] , src , nsecs * SECTSIZE) ;
return 0 ;
}