引起Page Fault的原因有很多种,其中一个很常见的原因就是Lazy Page Allocation,本文主要介绍其中几种Lazy Page Allocation方式。
eager: 饿汉方式申请物理内存,即在需要时马上申请。lazy: 懒汉方式申请物理内存,即在用到时才申请。用户空间存在三个区域
data: 已经被初始化的全局变量
text: 程序的指令
bss: 未被初始化和初始化为0的全局变量
bss段的值都为0,且bss段可能占用大量的page,如果在程序启动时我们就为bss分配物理页,则会减慢程序启动的速度,且如果我们一直未用到bss里的数据,就会导致大量的物理内存浪费。
一个解决方法就是zero-fill-on-demand:刚开始时,将所有的bss页映射至一个物理地址(只读不可写)。如果后续需要为bss里的变量执行写操作,将其变为不为0的值,则新创建一个物理页,并将对应的BSS映射到新的物理页。这也是懒加载的一种体现。
优点:减少内存使用量,提升程序的启动速度;缺点:会增加发生page fault的几率(也是时间换空间的一种体现)。
shell执行命令时,会涉及两个主要的系统调用,fork和exec,fork会将父进程的地址空间拷贝至子进程,子进程在执行exec之前,会将当前的地址空间丢弃,并重新加载新程序,重新生成地址空间。eager的方式方式执行上面的过程,则在fork时会申请内存空间、拷贝父进程的物理内存、映射内存空间等操作,在exec时又将fork申请的内存空间进行释放。这其实会浪费资源。fork之后,子进程并不会申请新的物理内存,而是将自己虚拟空间中的PTE指向对应父进程的物理地址(为方便叙述,之后将这部分内存称为共享内存)。page fault,并进行真正意义上的物理内存分配,并将对应的物理内存拷贝至新分配的内存里。exec的加载程序时eager加载的方式,但是实际上我们可以使用懒加载的方式。data、text、bss),并在某个地方记录好相应page对应的程序文件。Demand Paging.OOM的问题,采用Demand Paging可以很好的解决这个问题。缺点:会产生缺页中断,影响程序的执行效率。LRU,即最近最少使用的页换出去;a的优化,即在a的基础上,换页时进行相关的考虑。若有两种类型的页面,一种是Dirty Page(即最近被写过的页),一种是No DirtyPage(即最近没被写过的页,但是可能被读过),那么换页的时候推荐将No-DirtyPage换出去。备注:在PTE里,有10个标志位,这10个标志位标志了页面的一些属性,比如D(页面是否为Diryty Page),比如A(页面最近是否被访问过)。采用MMap的技术将文件映射到进程的虚拟内存里,这样读写文件会很快,因为省去了很多内核态和用户态之间的上下文切换(读写文件都走的系统调用,都需要进行内核态和用户态之间的反复切换)。
Dirty的block写回文件。在Page Fault里,我们可以借助以下三个寄存器排查问题:
SEPC:程序出错时用户程序计数器的值,表明发生错误时,用户空间的位置。
STVAL: 引起出错时的内存地址。
SCAUSE: 程序出错的原因。
https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081
https://pdos.csail.mit.edu/6.S081/2020/schedule.html