引起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