250707-250714 周记 2024-xv6-labs(1)
本周主要完成了2024-xv6-labs的util到cow的五个实验。
1. util lab
1.1 sleep
这个算是最简单的实验了。按照hints一步步写即可。没有任何额外需要注意的地方。
1.2 pingpong
这个实验的主要难点是理解pipe函数的使用。
先查看 sys_pipe函数:
1 | void |
再继续查看 pipealloc fdalloc copyout等相关函数的定义。
可见,pipe是把两个文件描述符分别传给了fdarray[0]和fdarray[1],且fdarray[0]只读,fdarray[1]只写。
而本题中,需要父->子 子->父两个管道。且对于父进程和子进程都需要读和写,最后代码如下:
1 | int p1[2]; // 父进程->子进程 |
1.3 primes
这题更为考验 pipe的使用,而且要特别小心,需留意哪些文件描述符不会使用了并把他们都关闭。否则会导致资源耗尽,在大约37的地方输出乱码。
该题提取质数的流程如下:
1 | //Input: file descriptor |
1.4 find
实际上,find的写法跟 ls写法几乎相同,对于目录文件而言 ls是打印下面的所有文件,而 find只打印下面名字与第二个参数相同的文件。只是要注意到 find是递归查看目录文件,所以要额外处理”.”和”..”。
1.5 xargs
这题的关键是,如何处理 xargs前 |之前的部分的输出。
首先要查明,这些输出是被管道重定向到了 stdin里,而 stdin对应的 fd为0.
然后就是使用read将 stdin里的拼接在原argv后面。注意 ' '和 '\n'的情况,前者代表一个参数输入结束,后者不仅代表结束,还需要立即执行一次。
注意在 exec()中,argv末尾需以 0标志。
2. syscall lab
2.1 trace
这题其实理清流程即可,并不复杂:
将 trace的第一个参数(掩码)给 myproc()保存 ———— 所以 proc结构体需要新添加一个属性 mask来存。然后每当 myproc()执行系统调用时候,检查掩码中对于该系统调用对应的位是否为1(p->mask&(1<<num)),以此来决定是否打印该系统调用。
2.2 attack
这道题。。。。emmmm。。。说实话我现在都没有想通。
这道题思路很明显:既然新分配的内存保留了其先前使用的內容,那么attack也申请内存,在其中的某一页中的32个偏移开始8字节就一定是 secrest。
关键在于:哪一页?
说实话,我真是万策尽矣,无论是打印申请的每一页开头40字节,还是用前缀字符来比较(这个甚至诡异得一塌糊涂:attack申请后那一存着secret的页开头2字节是乱码),都一一失败了😭。
最后是在不得已,去github上找到了参考:申请32页的第16页。
以下是作者的原话:
不能理解!这个16是怎么来的??
(我也不能理解。。。)
3. pgtbl lab
第3章是我目前为止xv6中做过的最痛苦的实验了😭,我真花了两三天时间做这个。尤其是最后一个超级块的部分,我搞了约14个小时,才过测试。。。
3.1 ugetpid
这个题目的难点。。。在于看不懂题目,找不到该在哪里写什么(bushi)。
但其实也很简单:在用户空间里拿一页空间 USYSCALL存 usyscall,usyscall来存一些东西(这里是 getpid())proc定义里添加它的地址,allocproc freeproc proc_pagetable proc_freepagetable这些与进程内存分配释放的函数里面也类似地加上处理 USYSCALL的部分即可。
但是有一点要注意:关于 USYSCALL页”user can access but read only”.
3.2 vmprint
这个的关键点在于,搞清楚虚拟页和对应的三级页表。
- 如果PTE叶在0级页表上,则对应的虚拟页相对于初始虚拟页的偏移是在30~38位上;
- 如果PTE叶在1级页表上,则对应的虚拟页相对于初始虚拟页的偏移是在21~29位上;
- 如果PTE叶在2级页表上,则对应的虚拟页相对于初始虚拟页的偏移是在12~20位上。
(PTE叶指的是有效的,且不表示下一级页表的pte项)
3.3 super block
太困难了!太困难了!😭
我最后甚至也是钻了空子(我留了10个超级块,而测试最多用8个超级块)才过的。
题目的思路很清晰:留几个”super block”使得申请大内存空间时不是给他很多个小页而是给他几个超级块。那么,只要在管理分配页的数据结构中添加超级页并给它们标志,并在处理页的申请释放的函数中类似地加上相应处理逻辑即可。
但是!!!有一些坑!
- 当地址未对齐时,普通页可以直接
PGROUNDUP对齐。但超级页不能类比!如果直接SUPERPGROUNDUP再分配超级块的话,会导致内存地址空间出现“空隙”,panic(“uvmcopy: page not present”)。所以为了防止这个问题,要先通过分配普通块的方式进行对齐。 - 超级页必须是在第1级页表上的pte表示,而不是普通页的第0级。因为超级页2MB,普通页4KB,一个第1级页表上的pte表示512个第0级pte————第1级的pte表示2MB。
4. trap lab
4.1 backtrace
这题也比较简单,照着这个笔记上关于栈帧布局的示意图写代码并打印即可。
4.2 alarm
虽是 红 题,但我觉得比 pgtbl那道 蓝 题超级块简单得多。。。
事实上,跟着hints一步步做就好,只是要注意:在进入handler函数时,时钟计数并不会自然停止,我们该在 proc额外设置一个属性来判断当前是否处于handler中。
而且,为了恢复上下文,我们还应该设一个结构体属性作为备份,来存进入handler之前的trapframe的所有内容。
5. cow lab
这个实验也挺。。。先是filetest死活过不了,花了我几个小时找bug,然后把bug一改,嘿!您猜怎么着?threetest也过不了了🤣。
最后找了两个多小时才找到原来是 kfree这边的问题。
做法其实也是跟着hints一步步来,(注意trap要处理的是 r_scause()==15的情景)只是还是有坑:
由于我把减引用计数的逻辑与kfree写一起的,
所以我天真的以为只需要开始减引用计数时和末尾更新 freelist才需要加锁,而中间是不用锁的。然后 kerneltrap给我狠狠上了一课。😂
最后还得别忘了在 usetrap添加处理虚拟地址超出最大值时杀死进程的逻辑,否则 usertests -q过不了。🤣
6. 感谢
- MIT的cs6.828以及xv6-labs的设计者们,感谢他们提供如此优质的项目。虽然写的时候很痛苦😂但确实我感到自己对于C语言和操作系统的理解与认识有所深入。
- siriusyaoz,在做实验的时候,你的关于这个实验的代码给予了我很多启发🥹。