update enhanced clock algorithm, as well as COW.

This commit is contained in:
Shine wOng
2019-11-28 21:28:10 +08:00
parent a0b4ea433f
commit 0bc19f6da7
3 changed files with 32 additions and 3 deletions

View File

@@ -92,11 +92,15 @@
将这样修改的思路应用到时钟置换算法上,就是这里的改进的时钟置换算法。具体的思路就是上面那样,总是优先选择未被访问(访问位为零),且未被修改的页面被置换。
具体的实现可以参考访问位的思路,在页表项中再添加一个修改位,来表示该页面被修改的情况。在发生缺页异常时,同样地依次检查驻留在内存中的页面的页表项,若访问位为一,则将访问位清零;若访问位为零且修改位为一,则将该页面写回到外存,且将修改位清零;直到发现一个访问位和修改位都是零的页面,选择将其置换。改进的时钟置换算法如下图所示
具体的实现可以参考访问位的思路,在页表项中再添加一个`修改位`,来表示该页面被修改的情况。在发生缺页异常时,通过两次循环来找到一个合适的换出页面
+ 第一次循环查找`访问位`为零且`修改位`也为零的页面,同时将经过的页面的`访问位`清零;
+ 如果第一次循环没有找到一个这样的页面,则进行第二次循环,继续查找`访问位``修改位`同时为零的页面,但是这次不做其他任何操作;
+ 如果第二次循环仍然失败,则直接将当前指针所指的页面换出,因为此时内存的所有页面都有`访问位`为零,而`修改位`为一,只能换出第一个被修改的页面。
![improved_clock](images/improved_clock.png)
可以看到,通过增加了修改位,可以使被修改的页面延迟被写回到外存中的时间,从而减少修改页的缺页处理开销。
可以看到,通过增加了`修改位`,可以使被修改的页面延迟被写回到外存中的时间,从而减少修改页的缺页处理开销。
### 最不常用算法(LFU)

View File

@@ -372,3 +372,17 @@ _fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick
```
这里并没有显式地调用`swap_out`函数来将某一页面换出,这是因为在`swap_in`函数的实现中,是首先调用了前面提到的`alloc_pages`函数,在物理内存不够时,通过这个函数来调用`swap_out`以将某个页面换出。由此也可以看出,我们采用的是消极的页面换出机制,即只有在内存空间不足时,才执行页面的换出;在还有剩余的内存空间时,直接给要换入的页面分配新的内存空间,并不执行换出操作。
### `extended clock`页替换算法设计方案
为了实现`extended clock`算法,需要管理物理页面的访问特征,即被访问(`accessed`)以及被修改(`dirty`),这两个参数在页表中分别对应了`PTE_A``PTE_D`
此外,还需要一个驻留页面的循环链表,可以采用前面的双向循环链表来作为它的底层结构,用`struct mm.sm_priv`作为时钟的指针。为了选择被置换的页面,需要进行两次循环:
+ 第一次循环寻找`PTE_A == 0 && PTE_D == 0`的页面,并且同时将经过的页面的`PTE_A`赋值为零。如果的确找到这样的页面,则将它作为要换出的页面,从循环链表中删除,并且将指针`sm_priv`移向它的后一个页面。
+ 如果第一次循环没找到适合换出的页面,则进行第二次循环,仍然是查找满足`PTE_A == 0 && PTE_D == 0`,后续操作也与第一步相同。
+ 如果第二步也失败,直接将当前指针`sm_priv`指向的页面换出。这是因为,此时链表中的所有页面都满足`PTE_A == 0 && PTE_D = 1`,此时只能换出一个修改过的页面。
此外,在`swap_out`函数中也要做相应的修改,增加对页面`PTE_D`的判断,只有在`PTE_D = 1`时,才将页面写回到外存当中。将页面换出后,记得要修改页表项相关的标志位。
`map_swappable`函数中,需要将换入的页面置于被换出的页面的位置,为此只需要将它插入到当前指针`sm_priv`的前一个位置即可。

View File

@@ -223,7 +223,18 @@ copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share) {
### `COW`机制实现
简单的版本的话,就是在`do_fork`函数中,只是让子进程拷贝父进程的地址空间。一旦父进程或者子进程试图写它们共享的空间,可以通过`Page Fault`机制,为该页新分配一个空间,并且修改对应进程的页表。这样,被修改的页就只有修改页的那个进程可以看到,对于其他进程修改都是不可见的。当然,要是具体实现的话,需要考虑许多问题,情况比较复杂,我以后再好好钻研吧
为了实现`COW`,只需要在`mm_copy`函数中,设置子进程的页表为父进程的一个拷贝,而并不实际为子进程分配这些内存空间。需要注意的是,这与`CLONE_VM`的情形是不同的,`CLONE_VM`是指父子进程共用一个`mm_struct`,当然也共用一个页表;`COW`机制则是子进程拥有自己独立的`mm_struct`,只不过其中的内容是父进程的一个拷贝而已。当父进程或者子进程写它们共有的内存空间时,就利用`Page Fault`机制,为该进程分配一个新的内存空间,该内存空间时私有而非共享的,并且将要写的页面复制到新分配的内存空间中,这样修改对共享页面的其他进程都是不可见的
上面说的很简单,但是实现起来还要考虑一些复杂的问题,比如如果共享页面被换出到外存当中,如何在共享该页面的所有进程的页表中,也修改它的状态为`换出`;此外,写操作时触发`Page Fault`具体又是如何实现的呢?在`Page Fault`中就如何判断触发原因是`COW`呢?
具体的实现方式如下:
+ 在`mm_copy`当中,如果
- `CLONE_VM = 1`,则直接设置父子进程共享`mm_struct`,不使用`COW`机制。
- `CLONE_VM = 0`,且页表`PTE_P = 1`,此时创建子进程独立的`mm_struct`,将父进程的`mm_struct`拷贝到其中,并且设置两者页表的`PTE_W = 0`,表示不可写,是只读的页面。并且为了简单起见,将该页标记为不可换出`PTE_L`(lock)。并且设置`vma_struct.vm_flags |= VMA_SHARE`,表示该虚拟地址块是共享的。
- `CLONE_VM = 0`,且页表`PTE_P = 0`,此时将该页面换入,随后按照上一步骤进行。
+ 如果一个进程需要修改共享页面,由于页表`PTE_W = 0`,此时将触发`Page Fault`。在`Page Fault`处理函数中,如果发现`vma_struct | VMA_WRITE | VMA_SHARE = TRUE`,即`vma`具有写权限,并且是共享的,但是`PTE_W = 0`,即页表是不可写的,就可以判定为`COW`,此时应该分配新的内存空间,将要写的页面拷贝到其中,并且更新当前进程的页表。
## 练习3: 阅读分析源代码,理解进程执行`fork/exec/wait/exit`的实现,以及系统调用的实现(不需要编码)