\documentclass[a4paper]{ctexart} \input{mypreamble} \renewcommand{\mychapternum}{3} \renewcommand{\mylabname}{虚拟内存管理} \renewcommand{\mydate}{2024年5月7日} \begin{document} \mytitle \begin{enumerate} \item{\textbf{研读理解相关代码后,回答以下问题:}} \begin{enumerate} \questionandanswer[]{ uCore 中的段页式存储管理方案与理论课中讲述的方案有何不同? }{ $\mu$Core 中简化了分段机制,将逻辑地址直接恒等映射到线性地址,之后使用分页机制将线性地址通过分页映射到物理地址。由于使用了分页机制,因此需要对空闲物理块管理,对空闲物理块的管理使用了链表来管理,链表按照地址排序。 跟我们理论课中的“段页式存储管理”和“使用链表管理存储空间”的方案相似,不同之处在于分段机制简化了,并且只使用链表管理了空闲物理块(已分配的物理块在页表中有记录,所以不需要管理)。 } \questionandanswer[]{ 试简述uCore中缺页异常的处理过程,它与理论课中讲述的过程有何不同? }{ 在vmm.c的注释中有这样一行: \mint{C}| * CALL GRAPH: trap--> trap_dispatch-->pgfault_handler-->do_pgfault| 显然,在$\mu$Core中,缺页异常从\mintinline{C}{trap}触发,之后通过\mintinline{C}{trap_dispatch}根据中断号(\mintinline{C}{tf->tf_trapno}分发给不同的处理程序,这里是缺页中断(\mintinline{C}{T_PGFLT}),所以分发给\mintinline{C}{pgfault_handler},再调用\mintinline{C}{do_pgfault},之后在\mintinline{C}{do_pgfault}中找到一个pte,并且分配内存或者从交换分区换入一个页面,或者由于访问权限不正确而直接返回失败。 与理论课中讲述的过程的不同是进行了简化,取消了快表,也就是只有一级索引而没有二级索引。 } \questionandanswer[]{ 为了支持页面置换,在数据结构Page中添加了哪些字段,其作用是什么? }{} {\kaishu \begin{minted}[fontsize=\zihao{-5}]{C} struct Page { int ref; // page frame's reference counter uint32_t flags; // array of flags that describe the status of the page frame unsigned int property; // the num of free block, used in first fit pm manager list_entry_t page_link; // free list link list_entry_t pra_page_link; // used for pra (page replace algorithm) uintptr_t pra_vaddr; // used for pra (page replace algorithm) }; \end{minted} 添加了\mintinline{C}{pra_page_link}和\mintinline{C}{pra_vaddr}这两个字段,\mintinline{C}{pra_page_link}是用来将物理块组织成FIFO队列(使用链表实现)用来进行页面置换的,\mintinline{C}{pra_vaddr}是物理块对应的虚拟地址,用来记录访问哪个虚拟地址的时候产生的缺页,以便进行页面置换。 } \questionandanswer[]{ 请描述页目录项页(PDE)和页表目录项(PTE)的组成部分对uCore实现页面置换算法的潜在用处。请问如何区分未映射的页表表项与被换出页的页表表项?请描述被换出页的页表表项的组成结构。 }{ 用处在于记录某个页面在内存中还是在外存对换区中,以及对应物理地址(在内存中)或者offset(在外存对换区中),在下次页面换入或换出时确保页面被放到正确的位置上。 由于未映射的页表表项全是0,所以被换出的页的offset从1开始以区分。这样如果页表表项全是0就说明是未映射,如果在offset(高24位)中存在1,那么就是被换出的。 被换出页的页表表项复用了pte的结构,在\mintinline{C}{memlayout.h}中有这样一行: \mint{C}|typedef pte_t swap_entry_t; //the pte can also be a swap entry| 再根据示意图: \begin{center} \includegraphics[width=0.5\linewidth]{imgs/2024-05-10-08-49-47.png} \end{center} 可以看到其高24位为offset,代表在外存对换区里的位置;存在位(最低位)为0,表示此页面不存在对应的物理块;中间的7位都保留暂不使用。 } \end{enumerate} \item \textbf{程序设计与实现的基本思路} \begin{enumerate} {\kaishu \item 在$\mu$Core中原先实现的是FIFO页面置换算法,这里要改成第二次机会页面置换算法,可以发现页面的组织方式不需要更改,仍然使用链表,每次插入页面时仍然是添加到链表头部不需要更改,只需要更改换出页面的算法即可。 \begin{center} \includegraphics[width=0.9\linewidth]{imgs/2024-05-10-09-00-25.png} \end{center} \item 课上讲的这张示意图,右侧是链表的头部,左侧是链表的尾部,每次插入的页面在最右侧即链表的头部。当需要换出页面时,从左侧(链表尾部)取出一个页面,如果这个页面的访问位是1,那么将其访问位改为0,并放到右侧(链表头部)。接着再从左侧(链表尾部)检测下一个页面,直到找到一个访问位为0页面的作为换出的页面。所以可以写出代码如下: \begin{minted}{C} // 从后往前,最后面的是最早访问的 list_entry_t *current = head->prev; list_entry_t *le = NULL; for (; current != head; current = current->prev) { pte_t *ptep = get_pte(mm->pgdir, le2vma(current, vm_start), 0); if (*ptep & PTE_A) { // 访问位为1 *ptep &= !PTE_A; // 清除访问位 list_del(current); // 从链表中移除 list_add(head, current); // 添加到链表头部 } else { le = current; break; } } \end{minted} \mintinline{C}{le}即为最终的找到的用来换出的页面。 \item 当然,还需要完善一些问题。当页面只有一个并且访问位为1时,从链表中移除此页面再放入后链表结构并未改变,所以下一次的\mintinline{C}{current}指向链表头结点,循环退出了,但此时\mintinline{C}{le}还是空的!但期望的返回结果应该是这一个页面,所以需要对这种情况进行处理: \begin{minted}{C} if (le == NULL) { le = head->prev; // 循环一遍找不到就最后一个 } \end{minted} 当循环一遍仍然找不到可以换出的页面的时候就选择最后一个页面(和FIFO一样)作为换出的页面。当然,其实使用第二次机会页面置换算法的话,页面大于一个的时候必定能找到一个换出的页面的(请自行验证),而且也不会出现页面为0个情况(总不能页表数量为0吧),所以其实这样就只是解决了页面只有一个时的异常。 \item 测试代码\mintinline{C}{_fifo_check_swap}仍然需要修改,因为使用第二次机会页面置换算法,缺页次数肯定与FIFO算法不尽相同。测试代码中的\mintinline{C}{pgfault_num}即为缺页次数。 \item 由于时间紧迫,一些细节可能没有考虑周到,如有发现错误欢迎在下方的链接中提Issue。 } \end{enumerate} \myitem{代码}{ \item \url{https://gitea.shuishan.net.cn/10213903403/os_kernel_lab} \item 也可以看上传的附件。 } \end{enumerate} \end{document}