├── .DS_Store ├── Lab1.md ├── Lab2.md ├── Lab3.md ├── Lab4.md ├── Lab5.md ├── Lab6.md └── Picture ├── .DS_Store ├── 7.JPG ├── Lab1 ├── 100.JPG ├── 2.3.JPG ├── HEADreset.JPG ├── bag.JPG ├── branch.JPG ├── cat.JPG ├── grep_c.JPG ├── grep_color.JPG ├── grep_n.JPG ├── grep_regular.JPG ├── grep_v.JPG ├── hash.JPG ├── mainstart.JPG ├── mv.JPG ├── readelf.JPG ├── ubuntu denglu.JPG ├── xianxiang.JPG ├── 回滚后文件ls.JPG └── 捕获.JPG ├── Lab2 ├── mem64.JPG ├── page_free_list.png └── 结果.JPG ├── Lab3 └── elf.JPG ├── lab5.JPG └── lab6.JPG /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/.DS_Store -------------------------------------------------------------------------------- /Lab1.md: -------------------------------------------------------------------------------- 1 | #
**Lab1实验报告** 2 | ## Chapter 1概述 3 | 通过本章的学习和实验,我初步了解了本学期OS实验的基本框架和一些实验所必须的基本技能。自己安装了虚拟机,但由于提供了远程虚拟机,所以最终还是选择在远程虚拟机上完成操作系统实验。同时,熟悉并了解了linux的一些基本操作和指令,为以后的实验打下了基础。 4 | ## Exercise 1.1 5 | 成功安装了Linux虚拟机并且设置账号密码进行登陆。与ssh远程虚拟机不同的是,安装的虚拟机具有图形化界面。 6 | 7 |
![本地虚拟机登陆界面](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab1/ubuntu%20denglu.JPG)
8 | ### ls 指令 9 | ls 的意思是list,顾名思义,是列出当前目录下所有文件的意思。这条指令还含有多个参数,例如,ls -a即为列出目录下所有文件指令(包括以“.”为前缀的文件),这在实验初期也得到了验证。 10 | 使用实例: 11 | `ls -a` 12 | ### cat 指令 13 | cat 指令允许我们可以将一个文件内容显示到Terminal上,而不用使用文本编辑器打开之。这一指令使我们对一些文件进行修改后可以方便地进行查看是否保存成功,也可以让我们以只读的方式查看一些文件而避免误操作对文件的更改。 14 | 使用实例: 15 | `cat test.c` 16 |
![使用cat指令](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab1/cat.JPG)
17 | ### cd 指令 18 | 这条指令几乎是最常用的指令。由于没有图形化的操作界面,这就意味着我们在目录间进行切换时只能使用linux指令,即cd指令。根据自己的实验结果和查阅linux指令速查手册,得出了以下结论: 19 | - `cd ..` 是回到上一级目录 20 | - `cd /` 是回到根目录 21 | - `cd .` 是当前目录 22 | - `cd < foldername >` 是进入到该文件夹的目录下 23 | ### cp 指令 24 | cp指令用于复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则它会把前面指定的所有文件或目录复制到此目录中.除此以外,该指令还提供了许多参数,用于进行各种有条件的复制。 25 | 使用实例: 26 | `cp file1 file2` 27 | ### mv 指令 28 | mv 是move的缩写,是提供移动文件的功能的指令。经常用来备份文件和目录。同时,该指令还有一个作用就是文件重命名。 29 | 使用实例: 30 | `mv a b` 可将文件名a改为b,文件的位置不发生变化。 31 | `mv a b(a folder)` 可将文件a挪动到b文件夹去。 32 | 33 |
![使用mv指令](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab1/mv.JPG)
34 | ## Exercise 1.2 35 | 由于本年度操作系统实验在远程虚拟机上完成,故未完成该部分实验。 36 | ## Thinking 1.1 37 | ### ls -l指令 38 | ls指令加上-l参数后,显示的文件名以长格式形式显示,每个文件名占一行,包括读写权限,创建者,创建者所在组,和创建时间等详细信息。 39 | ### mv test1.c teset2.c 40 | 将test1.c 文件改名为test2.c。 41 | ### cp test1.c test2.c 42 | 创建一个test1.c的副本,并命名为test2.c。 43 | ### cd .. 44 | 回到上层目录。 45 | ## Thinking 1.2 46 | ### grep简介 47 | grep指令是"Globally search a Regular Expression and Print"的缩写。顾名思义,是指全局地使用正则匹配的方式搜索文本,并把匹配的行打印出来。 48 | 其完整格式是`grep [-acinv] [--color=auto] '搜寻字符串' filename`其中"acinv"表示5个常用的参数,还可以给找到的关键词部分加上颜色,例如: 49 |
![使用grep](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab1/grep_color.JPG)
50 | ### grep的5个常用参数 51 | 1. -a 可以将二进制文件以text文件的方式搜索数据。本次实验里未用到,但以后会用。 52 | 2. -c 计算找到搜索字符串的次数,例如: 53 |
![使用grep-c](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab1/grep_c.JPG)
54 | 3. -i 查找时忽略大小写的不同。 55 | 4. -n 同时输出行号,而不仅是匹配的行的内容。例如: 56 |
![使用grep-c](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab1/grep_n.JPG)
57 | 5. -v 反向选择,输出没有搜索字符串的行的内容。例如: 58 |
![使用grep-c](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab1/grep_v.JPG)
59 | ### grep与正则表达式的配合使用 60 | 由于还涉及到正则表达式的用法,这里不再赘述,只举一个实验的例子。我们先新建一个含有两行英文的txt文件名为testgrep。内容为:`I love OS.I live in N625. `执行grep命令运行结果如下: 61 |
![使用grep-c](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab1/grep_regular.JPG)
62 | 参考博客 : [ggjucheng - 博客园](http://www.cnblogs.com/ggjucheng/archive/2013/01/13/2856896.html) 63 | ## Thinking 1.3 64 | `Werror`是把所有警告信息转化为错误信息,并和错误作相同处理,即在警告发生时终止编译过程。 65 | `Wall`是允许发出gcc提供的所有有用的报警信息。 66 | 这两个指令是非常必要的,尤其是在工程上。一个大型的工程绝不止一个程序员完成,对警告的纵容就是给别人和整个工程埋下了隐患,可能这个警告在你的工作范围内无关紧要,或者你知道无关紧要,然而对于别人,这个警告可能就是一个大坑。更何况在人才流动相当频繁的今天,工程里的人还在不停地变动,要想维护好一个工程,必须对错误和警告都做到零容忍。 67 | 对于编译器来说,一个好的编译器可以发现更多的警告,如果无视这些警告,那还费劲弄那么好的编译器干什么呢?"regard warnings as errors"是一个好的程序员的基本修养。 68 | 69 | *** 70 | 71 | ## Chapter 2概述 72 | 本章,按照实验指导书的要求和顺序,了解了第一次实验的目的(成功加载linux内核到gxemul仿真器),结合理论课上王雷老师所讲的关于bootloader的知识,初步了解了机器开机加电后操作系统是如何被加载运行的。同时,在了解了一些相关文件的作用和原理,以及掌握如何使用文本编辑器后,对一些关键文件进行了改动,成功使内核正确运行。最后,还练习了强大的git工具,以及git的相关指令(以前只使用过图形化界面的git工具,虽然对git的status较为熟悉,但是对指令并不熟悉)。 73 | ## Exercise 2.1 74 | 关于修改include.mk的文件,实质上是修改makefile文件里的内容,根据指导书上的内容,makefile是“内核代码的地图”,make工具会读取makefile文件,并以此为依据编译和维护工程。在我们的实验里,为了编译出vmlinux可执行文件,就必须使用make指令,而makefile里的交叉编译器位置有误,故我们需要修改交叉编译器的路径,使之成为正确的路径。 75 | 最终,修改路径为`/OSLAB/compiler/usr/bin/mips_4KC_gcc`即可。 76 | 至此,我们的交叉编译器路径已经正确,可以成功执行make指令,生成了vmlinux文件,完成。 77 | ## Exercise 2.2 78 | 进行该部分实验前,根据指导书的介绍,又参考了网上[GUN LD Simple Linker Script Example](https://www.sourceware.org/binutils/docs/ld/Simple-Example.html#Simple-Example)的内容,知道了可以用Linker Script增强连接器的通用性,使得它可以为不同的平台生成可执行文件。Linker Script中记录了各个section该如何被映射到segment上,又该加载到何处。 79 | 在完成2.2之前,还完成了一个小实验:用Linker Script修改text、data和bss的位置.如图: 80 |
![使用readelf](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab1/readelf.JPG)
81 | 有了这些基础后,我们只需知道内核的代码段需要被加载的地址,便可以利用Linker Script来修改内核加载的位置了。根据32 bit Mips CPU的特性,我们需要一个地址可直接转化为物理地址的区段,并且通过cache进行存取的区段。经过选择,只能选择seg0段。读取mmu.h的相关内容后,最终确定位置为Kernel Text段,即0x80010000。有了这些知识,我们便可以进行修改了。最终添加SECTION的相关内容到scse0_3.lds即可。 82 | ## Exercise 2.3 83 | 结合上学期计算机组成原理的相关知识(或指导书中关于MIPS汇编的相关部分),我们知道最终汇编语言调用函数实际上分为减小sp指针,为栈分配空间,增加sp指针,释放栈空间这几步。可见sp指针有着非常强大的控制作用。我们为了正确地运行内核,就需要设置好栈指针。结合mmu.h内的图片,我们知道栈的指针应设在0x80400000位置。 84 | 设置方法很简单,我选择了`lui sp,0x8040`这个指令。 85 | 完成start.S后,运行 86 | `/OSLAB/gxemul -E testmips -C R3000 -M 64 /home/14231027/14231027-lab/gxemul/vmlinux` 指令 87 | 可以成功跳转到main函数了。实验现象如下: 88 |
![使用readelf](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab1/mainstart.JPG)
89 | 这里有两点需要注意:由于我们的交叉编译器在OSLAB目录下,所以要写`/OSLAB/gxemul`(不同于指导书)。在修改start.S时,需要把加入的一行代码放在死循环前面(或把死循环删去,目前看来这种做法暂时没有什么副作用),否则无法正确跳转到main函数。 90 | ## Exercise 2.4 91 | 通过阅读代码,printf的大部分内容已经被实现了。我们还需要对一些参数进行赋值,比如width,longFlag等。具体的代码如下: 92 | ``` 93 | in for loop: 94 | length=0; 95 | while((*fmt)!='%' && (*fmt)!='\0'){ 96 | buf[length]=*fmt; 97 | fmt++; 98 | length++; 99 | } /* scan for the next '%' */ 100 | OUTPUT(arg, buf, length); 101 | length=0; /* flush the string found so far */ 102 | if(*fmt=='\0') 103 | break; /* are we hitting the end? */ 104 | /* we found a '%' */ 105 | /* check for long */ 106 | /* check for other prefixes */ 107 | /* check format flag */ 108 | fmt++; /*skip the '%'*/ 109 | 110 | 111 | //flag section: 112 | if(*fmt=='-' || *fmt=='+' || *fmt=='0' || *fmt==' ' || *fmt=='#'){ 113 | if(*fmt=='-'){ladjust=1;padc=' ';} 114 | if(*fmt=='+'); 115 | if(*fmt=='0'){ladjust=0;padc='0';} 116 | if(*fmt==' '); 117 | if(*fmt=='#'); 118 | fmt++; 119 | } 120 | else{ 121 | ladjust=0; 122 | padc=' '; /*not mentioin flag*/ 123 | } 124 | 125 | //width section: 126 | if(IsDigit(*fmt)){ 127 | for(width=0;IsDigit(*fmt);fmt++) 128 | width = width * 10 + Ctod(*fmt); 129 | } 130 | else 131 | width = 0; /*not mention width*/ 132 | 133 | //precise section: 134 | prec=0; 135 | //F|N|h|l section: 136 | if(*fmt=='l'){ 137 | longFlag=1; 138 | fmt++; 139 | } 140 | else 141 | longFlag=0; /*not mention long or short*/ 142 | ...... 143 | ``` 144 | 参考内容: 145 | [func printf introduction](http://www.cplusplus.com/reference/cstdio/printf/ ) 146 | [va_start和va_end介绍](http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html) 147 | ## Exercise 2.5 148 | `14231027@ubuntu:~/Learngit$ nano readme.txt` 创建readme.txt 149 | 使用`git add readme.txt`指令将readme.txt加入到仓库中。并使用`git status > Stage.txt`指令将当前的git状态写入到Stage.txt中。可以看到Stage.txt中的文件如下 150 | ``` 151 | On branch master Initial commit 152 | 153 | Changes to be committed: 154 | (use "git rm --cached ..." to unstage) 155 | 156 | new file: readme.txt 157 | 158 | ``` 159 | 这说明,readme.txt还没有commit。 160 | 使用git commit提交后,写入提交说明(为自己的学号),则显示 161 | ``` 162 | On branch master 163 | nothing to commit (working directory clean 164 | 165 | ``` 166 | 修改readme.txt后,git status显示结果如下: 167 | ``` 168 | On branch master 169 | Changes not staged for commit: 170 | (use "git add ..." to update what will be committed) 171 | (use "git checkout -- ..." to discard changes in working directory) 172 | 173 | modified: readme.txt 174 | ``` 175 | 这说明,修改后的文件与新添加的文件同样需要add操作。 176 | 虽然新添加的文件和修改过的文件都需要修改,但是还是有区别。 177 | ``` 178 | On branch master Changes not staged for commit: 179 | (use "git add ..." to update what will be committed) 180 | (use "git checkout -- ..." to discard changes in working directory) 181 | modified: readme.txt 182 | 183 | Untracked files: 184 | (use "git add ..." to include in what will be committed) 185 | 186 | README.TXT 187 | ``` 188 | 189 | 可以看到,对于修改过的文件,还有一个checkout的选项。 190 | ## Exercise 2.6 191 | add the file : `git add ` 192 | stage the file : `git add ` 193 | commit : `git commit` 194 | ## Thinking 2.1 195 | #### 1.深夜,小明在做操作系统实验。困意一阵阵袭来,小明睡倒在了键盘上。等到小明早上醒来的时候,他惊恐地发现,他把一个重要的代码文件printf.c 删除掉了。苦恼的小明向你求助,你觉得怎样能帮他把代码文件恢复呢? 196 | 使用`git checkout – print.c`即可从暂存区恢复print.c到工作区。“重新恢复到美妙的样子”。 197 | #### 2.正在小明苦恼的时候,小红主动请缨帮小明解决问题。小红很爽快地在键盘上敲下了git rm printf.c,这下事情更复杂了,现在你又该如何处理才能弥补小红的过错呢? 198 | 既然已经remove掉了,那我们只能在版本库中寻找曾经的文件了。 199 | 使用`git reset --hard HEAD^`即可回到上个版本,找回丢失的文件。 200 | #### 3.处理完代码文件,你正打算去找小明说他的文件已经恢复了,但突然发现小明的仓库里有一个叫Tucao.txt,你好奇地打开一看,发现是吐槽操作系统实验的,且该文件已经被添加到暂存区了,面对这样的情况,你该如何设置才能使Tucao.txt 在不从工作区删除的情况下不会被git commit 指令提交到版本库? 201 | 使用`git rm –cached Tucao.txt`即可从暂存区去掉一些奇奇怪怪的东西 202 | ## Exercise 2.7 203 | 修改三次 readme.txt 后,查看git log,显示结果如下: 204 | >commit 92f6c073bdede8cdaad6c57ab5fa3be1e3e1f4f2 205 | Author: 14231027 <14231027@ubuntu.(none)> 206 | Date: Tue Mar 22 15:10:11 2016 +0800 207 | 208 | version 3 209 | 210 | >commit 95c2fa257a372ac60d801a3ea958bcafc56ea5b7 211 | Author: 14231027 <14231027@ubuntu.(none)> 212 | Date: Tue Mar 22 15:09:12 2016 +0800 213 | 214 | version 2 215 | 216 | >commit d9772cdc3e30b9f39d5619e08fd16b5bd06c484e 217 | Author: 14231027 <14231027@ubuntu.(none)> 218 | Date: Tue Mar 22 15:08:41 2016 +0800 219 | 220 | version 1 221 | 222 | 随后: 223 | >14231027@ubuntu:~/gittest$ git reset --hard HEAD^ 224 | > 225 | > 226 | > HEAD is now at 95c2fa2 version 2 227 | 228 | 229 | 说明我们已经实现了版本回滚。同时,也可以: 230 | >14231027@ubuntu:~/gittest$ git reset --hard 92f6c073bdede8cdaad6c57ab5fa3be1e3e1f4f2 231 | > 232 | > HEAD is now at 92f6c07 version 3 233 | 234 | 235 | 这样,我们又回滚到了版本3。 236 | ## Thinking 2.2 237 | #### 1.克隆时所有分支均被克隆,但只有HEAD 指向的分支被检出 (正确) 238 | 理由:我在实验时,clone到本地只有master分支被检出,而实际上有lab1分支。需要运行git checkout lab1指令。 239 | #### 2.克隆出的工作区中执行git log、git status、git checkout、git commit 等操作不会去访问远程版本库。(正确) 240 | 理由:在实验时确实是这样,无论是查看版本日志,还是切换分支,commit都只是在本地的操作。 241 | #### 3.克隆时只有远程版本库HEAD 指向的分支被克隆。 (错误) 242 | 理由:实际上所有分支都被克隆了,只是没有被检出,需要使用checkout。checkout后本地和远程分支会自动建立tracking的关系(pull和push都会在对应的远程和本地分支下进行了)。 243 | #### 4.克隆后工作区的默认分支处于master 分支。 (正确) 244 | 理由:事实就是如此。 245 | ## Exercise 2.8 246 | ``` 247 | 14231027@ubuntu:~/14231027-lab$ git branch -a 248 | lab1 249 | master 250 | *testbranch 251 | remotes/origin/HEAD -> origin/master 252 | remotes/origin/lab1 253 | remotes/origin/lab1-result 254 | remotes/origin/master 255 | remotes/origin/testbranch 256 | 14231027@ubuntu:~/14231027-lab$ cat readme.txt 257 | 14231027 258 | 14231027@ubuntu:~/14231027-lab$ git status 259 | /#On branch testbranch 260 | nothing to commit (working directory clean) 261 | ``` 262 | 从实验现象可以看到,建立了testbranch的本地分支,并创建了相应的远程分支,同时完成了该分支下readme.txt的commit。 263 | 264 | *** 265 | # 总结 266 | ### 最终报告 267 | #### 实验现象 268 |
![使用readelf](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab1/xianxiang.JPG)
269 | #### 实验成绩 270 |
![使用readelf](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab1/100.JPG)
271 | ### 小建议 272 | 在做到实验2.2时感觉难度突然增高,许多地方读懂了实验手册,却由于不熟悉指令无从下手,许多同学(包括我)还利用了搜索引擎去了解操作的步骤。我个人认为该Lab1重点在理解我们是在干什么,而不是如何快速地操作linux,所以希望在实验手册中(至少Lab1中)加入每一步操作的指令(或者给一个详细操作的例子),这样不仅能使实验手册更具亲和力,也能让同学们更快地完成实验目的,获得满足感,对以后的实验充满信心。 273 | -------------------------------------------------------------------------------- /Lab2.md: -------------------------------------------------------------------------------- 1 | #
Lab2实验报告
2 | 赵岳 3 | 142321027 4 | ##Thinking 3.1 5 | ####我们注意到我们把宏函数的函数体写成了 do { // ... } while(0) 的形式,而不是仅仅写成形如 {// ... } 的语句块,这样的写法好处是什么? 6 | 答:简单来说,就是为了避免一些歧义和语法错误,do保证了大括号内的语句始终会被执行,while(0)保证了大括号的语句只被执行一遍,并且`do{}while()`是一个一定语法正确的独立整体,不会与其他部分发生语法冲突。下面,我举一个使用大括号产生语法错误的例子: 7 | ```cpp 8 | #define f(x) {x++;} 9 | ... 10 | ... 11 | for(i=0;i![](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab2/mem64.JPG)
30 | 所以将相应初值赋好即可。值得注意的是: 31 | 1. 物理地址从0开始递增至最大值。 32 | 2. 页面大小每页4KB,需将总的内存大小除以4KB算出有多少页。 33 | ##Exercise3.2 34 | ####完成 page\_init 函数,使用 include/queue.h 中定义的宏函数将未分配 的物理页加入到空闲链表 page\_free\_list 中去。思考如何区分已分配的内存块和未 分配的内存块,并注意内核可用的物理内存上限。 35 | 由于精妙而复杂的宏定义,这段思路很清晰的代码写了很久。以对page\_free\_list变量的使用为例,先由变量找到了定义,然后在定义里又出现了LIST_HEAD宏的使用,用宏定义了一个由Page类型变量组成的新的数据结构,称之为Page\_list(页的链表),并定义了这种类型的变量Page\_free\_list来记录所有空闲的页整个构建过程的流程图如下: 36 |
![](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab2/page_free_list.png)
37 | 经过仔细推敲,结合注释,给出了最终代码如下: 38 | ```cpp 39 | void 40 | page_init(void) 41 | { 42 | /* Step 1: Initialize page_free_list. */ 43 | /* Hint: Use macro `LIST_INIT` defined in include/queue.h. */ 44 | LIST_INIT(&page_free_list); 45 | 46 | /* Step 2: Align `freemem` up to multiple of BY2PG. */ 47 | freemem = ROUND(freemem,BY2PG); 48 | 49 | /* Step 3: Mark all memory blow `freemem` as used(set `pp_ref` 50 | * filed to 1) */ 51 | int j = 0; 52 | while(j < PPN(PADDR(freemem))){ // j < (freemem-0x80000000)/4KB? 53 | pages[j].pp_ref = 1; 54 | j++; 55 | } 56 | 57 | /* Step 4: Mark the other memory as free. */ 58 | while(jpp_ref>0){ 98 | return; 99 | } 100 | 101 | /* Step 2: If the `pp_ref` reaches to 0, mark this page as free and return. */ 102 | else if(pp->pp_ref==0){ 103 | LIST_INSERT_HEAD(&page_free_list,pp,pp_link); 104 | } 105 | 106 | /* If the value of `pp_ref` less than 0, some error must occurred before, 107 | * so PANIC !!! */ 108 | else 109 | panic("cgh:pp->pp_ref is less than zero\n"); 110 | } 111 | ``` 112 | 具体实现过程是:由于pp\_ref记录的是当前物理页面是否还在使用,那么如果大于零,就不能对其进行 free操作,直接return,什么都不做。如果pp\_ref==0,说明该页面已经不再需要存在于物理内存中,可以调用LIST_INSERT将其插入到page\_free\_list链表当中了。如果pp\_ref的值小于零,那么输出一个panic警告。 113 | ##Thinking 3.2 114 | ####了解了二级页表页目录自映射的原理之后,我们知道,Win2k 内核的 虚存管理也是采用了二级页表的形式,其页表所占的 4M 空间对应的虚存起始地址 为 0xC0000000,那么,它的页目录的起始地址是多少呢? 115 | 答:0xc0000000+((0xc0000000>>12)<<2) = 0xc0000000+0x00300000 = 0xc0300000. 116 | 所以页目录起始地址为0xc0300000。 117 | ##Exercise3.4 118 | ####完成 mm/pmap.c 中的 boot_pgdir_walk 和 pgdir_walk 函数,实现 虚拟地址到物理地址的转换以及创建页表的功能。 119 | ```cpp 120 | static Pte *boot_pgdir_walk(Pde *pgdir, u_long va, int create) 121 | { 122 | 123 | Pde *pgdir_entryp; 124 | Pte *pgtable, *pgtable_entry; 125 | 126 | /* Step 1: Get the corresponding page directory entry and page table. */ 127 | /* Hint: Use KADDR and PTE_ADDR to get the page table from page directory 128 | * entry value. */ 129 | pgdir_entryp=pgdir+PDX(va); 130 | if(*pgdir_entryp&PTE_V) 131 | pgtable=(Pte *)KADDR(PTE_ADDR(*pgdir_entryp)); 132 | 133 | /* Step 2: If the corresponding page table is not exist and parameter `create` 134 | * is set, create one. And set the correct permission bits for this new page 135 | * table. */ 136 | else if(create){ 137 | /*temp=(Pte *)alloc(BY2PG,BY2PG,1); 138 | if(temp==NULL){ 139 | panic("boot_pgdir_walk failed. Run out of memory!\n"); 140 | return NULL; 141 | }*/ 142 | *pgdir_entryp=PADDR((Pde)alloc(BY2PG,BY2PG,1))|PTE_V; 143 | //printf("%x\n%x",*pgdir_entryp,PTE_ADDR(*pgdir_entryp)); 144 | pgtable=(Pte *)KADDR(PTE_ADDR(*pgdir_entryp)); 145 | } 146 | else{ 147 | panic("boot_pgdir_walk failed! No valid memory and create is 0!\n"); 148 | return NULL; 149 | } 150 | 151 | /* Step 3: Get the page table entry for `va`, and return it. */ 152 | pgtable_entry=pgtable+PTX(va); 153 | return pgtable_entry; 154 | 155 | } 156 | ``` 157 | ```cpp 158 | int 159 | pgdir_walk(Pde *pgdir, u_long va, int create, Pte **ppte) 160 | { 161 | Pde *pgdir_entryp; 162 | Pte *pgtable; 163 | struct Page *ppage; 164 | 165 | /* Step 1: Get the corresponding page directory entry and page table. */ 166 | pgdir_entryp=pgdir+PDX(va); 167 | if(*pgdir_entryp&PTE_V){ 168 | pgtable=(Pte *)KADDR(PTE_ADDR(*pgdir_entryp)); 169 | } 170 | 171 | /* Step 2: If the corresponding page table is not exist(valid) and parameter `create` 172 | * is set, create one. And set the correct permission bits for this new page 173 | * table. 174 | * When creating new page table, maybe out of memory. */ 175 | else if(create){ 176 | if(page_alloc(&ppage)==-E_NO_MEM){ 177 | *ppte=NULL; 178 | return -E_NO_MEM; 179 | } 180 | ppage->pp_ref++; 181 | *pgdir_entryp=page2pa(ppage)|PTE_V|PTE_R; 182 | pgtable=(Pte *)KADDR(PTE_ADDR(*pgdir_entryp)); 183 | } 184 | else{ 185 | //panic("pgdir_walk failed! Page is not valid and create is 0\n"); 186 | *ppte=NULL; 187 | return -E_INVAL; 188 | } 189 | 190 | /* Step 3: Set the page table entry to `*ppte` as return value. */ 191 | *ppte=pgtable+PTX(va); 192 | return 0; 193 | } 194 | ``` 195 | ##Exercise3.5 196 | ####实现 mm/pmap.c 中的 boot_map_segment 函数,实现将制定的物理 内存与虚拟内存建立起映射的功能。 197 | ```cpp 198 | void boot_map_segment(Pde *pgdir, u_long va, u_long size, u_long pa, int perm) 199 | { 200 | int i, va_temp; 201 | Pte *pgtable_entry; 202 | 203 | /* Step 1: Check if `size` is a multiple of BY2PG. */ 204 | if(size%BY2PG){ 205 | panic("size must be a multiple of BY2PG!\n"); 206 | return; 207 | } 208 | 209 | /* Step 2: Map virtual address space to physical address. */ 210 | /* Hint: Use `boot_pgdir_walk` to get the page table entry of virtual address `va`. */ 211 | for(i=0;i![](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab2/结果.JPG) 232 | ###小建议 233 | 1.实验指导书中的正确结果展示有两句顺序有误。 234 | 2.希望指导书中不光给出我们需要的函数填空的相关说明,适当地对例如panic和assert以及page_check函数做相关说明可以让我们更加明白整个程序的运行流程,对我们调试代码也是大有裨益的。 235 | 236 | -------------------------------------------------------------------------------- /Lab3.md: -------------------------------------------------------------------------------- 1 | # Lab3实验报告 2 | 赵岳 14231027 3 | 备注:Thinking 3.7为选做。Exercise3.1代码已经给出,不再赘述。 4 | ## Exercise 3.2 5 | #### 仔细阅读注释,填写 env_init 函数,注意要按照逆序插入。 6 | 7 | void 8 | env_init(void) 9 | { 10 | int i; 11 | LIST_INIT(&env_free_list); 12 | for(i=NENV-1;i>=0;i--){ //NENV=1024, with backward sequence 13 | envs[i].env_status=ENV_FREE; 14 | LIST_INSERT_HEAD(&env_free_list,envs+i,env_Link); 15 | } 16 | } 17 | 18 | 与page\_init类似,甚至比其简单。因为在page\_init函数中,我们还需要考虑哪些物理页已经使用了,哪些还未使用。而在env_init函数中所有进程都是ENV\_FREE的。唯一需要注意的只是需要逆序插入。 19 | 20 | ## Thinking 3.1 21 | #### 为什么我们在构造空闲进程链表时使用了逆序插入的方式? 22 | 23 | 注释中说:Insert in reverse order, so that the first call to env_alloc() returns envs[0]. 24 | 我们使用的是LIST_INSERT_HEAD宏,即每次都插入到链表的头部,我们从freelist中取出一项时,使用LIST_FIRST宏,那么既然要让取出的是env[0],那我们必须要让env[0]最后插入,也就是倒序插入的方式构造链表。除此原因以外,我们在后面的时间片轮转算法中,是从envs数组的envs[0]开始的,因此我们需要逆序插入,这样才能保证最先来到的进程最先被分配,最先被轮转。 25 | ## Thinking 3.2 26 | #### 1.第三点注释中的问题: 为什么我们要执行pgdir[i] = boot\_pgdir[i]这个 赋值操作?换种说法,我们为什么要使用boot\_pgdir作为一部分模板?(提 示:mips 虚拟空间布局) 27 | #### 2.UTOP 和 ULIM 的含义分别是什么,在 UTOP 到 ULIM 的区域与其他用户区 相比有什么最大的区别? 28 | #### 3. (选做) 我们为什么要让pgdir[PDX(UVPT)]=env_cr3?(提示: 结合系统自映射 机制) 29 | 1.每一个进程都有 4G 的逻辑地址可以访问,而多个进程的UTOP~4G是内核态,是在不切换进程的情况下(由于我们是2G/2G的模式,切换到内核态并不用切换进程,也就自然不用改变CR3寄存器了),切换到内核态的。那既然每个进程都可以切换到内核态,自然就需要给每个进程都拷贝一份boot\_pgdir的内容到envs\_pgdir中。因此需要该赋值操作。 30 | 31 | 32 | 2.UTOP和ULIM差距是3*4Mb的大小。这12Mb的虚拟地址实际上存储的是记录页面使用情况的4M大小的PAGES数组,4M进程控制块ENVS数组和用户页表域的那4M虚拟空间。由于这些变量应该是全局的,它们也应属于“内核态”。因此,我们在赋值的时候,是从第PDX(UTOP)项开始把boot\_pgdir赋给envs\_pgdir的,而不是ULIM。而ULIM以上的地址只是可以直接去掉高位进行物理-虚拟地址转换。 33 | 34 | 35 | 3.env\_cr3存储的是用户进程页目录的物理地址, e->env\_pgdir[PDX(UVPT)] = e->env\_cr3 | PTE\_V | PTE\_R;的赋值就是让用户进程页目录中的第PDX(UVPT)项存储的物理地址等于用户进程页目录的物理地址,这样就完成了自映射。当我们给出一个属于UVPT域(UVPT ~ UVPT+4M)的虚拟地址时,进行虚拟到物理地址转换时,就会找到用户进程页目录的物理地址。 36 | 37 | ## Exercise 3.3 38 | #### 根据提示与代码注释,填写 env_ alloc 函数。 39 | 40 | int 41 | env_alloc(struct Env **new, u_int parent_id) 42 | { 43 | int setup_return_value; 44 | struct Env *temp; 45 | /*precondtion: env_init has been called before this function*/ 46 | /*1. get a new Env from env_free_list*/ 47 | temp=LIST_FIRST(&env_free_list); 48 | 49 | /*2. call some function(has been implemented) to intial kernel memory layout for this new Env. 50 | *hint:please read this c file carefully, this function mainly map the kernel address to this new Env address*/ 51 | if((setup_return_value=env_setup_vm(temp))<0) 52 | return setup_return_value; 53 | 54 | /*3. initial every field of new Env to appropriate value*/ 55 | temp->env_id = mkenvid(temp); 56 | temp->env_parent_id=parent_id; 57 | temp->env_status=ENV_NOT_RUNNABLE; 58 | 59 | /*4. focus on initializing env_tf structure, located at this new Env. especially the sp register, 60 | * CPU status and PC register(the value of PC can refer the comment of load_icode function)*/ 61 | temp->env_tf.regs[29]=USTACKTOP; 62 | temp->env_tf.pc=UTEXT+0xb0; 63 | temp->env_tf.cp0_status=0x10001004; //MIPS R3000 64 | 65 | /*5. remove the new Env from Env free list*/ 66 | LIST_REMOVE(temp,env_link); 67 | *new=temp; 68 | return 0; 69 | } 70 | 71 | ## Thinking 3.3 72 | #### 思考 user\_data 这个参数的作用。没有这个参数可不可以?为什么?(如果你能说明哪些应用场景中可能会应用这种设计就更好了。可以举一个实际的库中的例子) 73 | 74 | 这个参数很关键。在我写load\_icode\_mapper、load\_icode、load\_elf这三个函数时,由于这三个函数是同一个进程的操作,那我们需要将记录该进程信息的数据结构一脉相承地作为参数传递下去,user\_data这个参数位可以为我们很好的服务。比如,我们需要在load\_icode\_mapper中申请物理页面,把物理页面使用page\_insert函数映射到该进程的页目录、页表中,那么我们当然就需要一个记录该进程的数据结构的指针,这样我们才能顺利找到该进程的页目录。否则,没有这个参数的穿线,这三个函数怎么算是为一个进程服务的呢?或者,一些关于I\O的进程,也需要这个参数传递与用户交互的数据。 75 | ## Exercise 3.4 76 | #### 通过上面补充的知识与注释,填充 load\_ icode\_ mapper 函数。 77 | 78 | static int load_icode_mapper(u_long va, u_int32_t sgsize, 79 | u_char *bin, u_int32_t bin_size, void *user_data) 80 | { 81 | struct Env *env = (struct Env *)user_data; 82 | struct Page *p = NULL; 83 | u_long i; 84 | int r; //return value 85 | u_long offset = va - ROUNDDOWN(va, BY2PG); 86 | 87 | /*Step 1: load all content of bin into memory. */ 88 | for (i = 0; i < bin_size; i += BY2PG) { 89 | if((r= page_alloc(&p))<0) 90 | return r; 91 | if((r=page_insert(env->env_pgdir,p,va,PTE_V)) < 0) 92 | return r; 93 | bzero((void *)page2kva(p),BY2PG); 94 | bcopy((void *)bin,(void *)(page2kva(p)+offset),BY2PG); 95 | bin+=BY2PG; 96 | va+=BY2PG; 97 | /* Hint: You should alloc a page and increase the reference count of it. */ 98 | } 99 | /*Step 2: alloc pages to reach `sgsize` when `bin_size` < `sgsize`. 100 | * i has the value of `bin_size` now. */ 101 | while (i < sgsize) { 102 | if((r= page_alloc(&p))<0) 103 | return r; 104 | if((r=page_insert(env->env_pgdir,p,va,PTE_V)) < 0) 105 | return r; 106 | bzero((void *)page2kva(p),BY2PG); 107 | i+=BY2PG; 108 | va+=BY2PG; 109 | } 110 | return 0; 111 | } 112 | 113 | 该函数总体分为两大部分,第一部分是将进程的某一段段拷入用户空间里对应的地址上,第二部分是将剩下的未被代码填充的区域 填0。自然地想到了在init.c中的两个函数:bzero和bcopy。在这里,我猜想传入的va可能不是按页对齐的,因此我在使用bcopy函数时,起始地地址并不是一页的开始,而是加上页内偏移量。即page2kva(p)+offset。 114 | ## Exercise 3.5 115 | #### 通过补充的 ELF 知识与注释,填充 load\_ icode 函数。 116 | 117 | static void 118 | load_icode(struct Env *e, u_char *binary, u_int size) 119 | { 120 | struct Page *p = NULL; 121 | u_long entry_point; //set this value in load_elf function 122 | u_long r; //return value 123 | u_long perm; 124 | /*Step 1: alloc a page. */ 125 | if((r= page_alloc(&p))<0) 126 | return; 127 | 128 | /*Step 2: Use appropriate perm to set initial stack for new Env. */ 129 | /*Hint: The user-stack should be writable? */ 130 | perm=PTE_V|PTE_R; 131 | page_insert(e->env_pgdir,p,(USTACKTOP-BY2PG),perm); 132 | 133 | /*Step 3:load the binary by using elf loader. */ 134 | load_elf(binary,size,&entry_point,e, load_icode_mapper); 135 | 136 | /***Your Question Here***/ 137 | /*Step 4:Set CPU's PC register as appropriate value. */ 138 | e->env_tf.pc = entry_point; 139 | e->env_status=ENV_RUNNABLE; 140 | } 141 | 这个函数是拷贝进程镜像的最后一关。通过调用load\_elf找到了代码的各个区段,然后在load\_elf中又调用mapper将各个区段(segment)映射好。最后,在该函数内,申请一页,添加映射关系(page\_insert),将该进程的栈空间放在该物理页上,虚拟地址设置为USTACKTOP-BY2PG的位置上,保证栈顶位于USTACKTOP处。最后,由于我们没有在env\_alloc函数内为进程设置PC,因此我们在该函数内还需要给进程设置PC值。这里,需要感谢学长将本年的操作系统实验难度降低,去除了给PC赋值的部分。使PC的赋值还有个各个segment的定位都放到了elf\_loader函数内实现。 142 | ## Thinking 3.4 143 | #### 思考上面这一段话,并根据自己在 lab2 中的理解,回答: 144 | #### 1.我们这里出现的” 指令位置” 的概念,你认为该概念是针对虚拟空间,还是物理内存所定义的呢? 145 | #### 2.你觉得entry_point其值对于每个进程是否一样?该如何理解这种统一或不同? 146 | #### 3.从布局图中找到你认为最有可能是entry_point的值。 147 | 1.虚拟空间。 148 | 2.是一样的。由于每个进程都有属于自己的地址空间,我们在切换进程的时候,切换了cr3寄存器,也就是对于每个进程有不同的页目录、页表,那么就有不同的虚拟、物理地址映射关系。因此,虽然entry_point的值对于每个进程都一样,但是其对应的物理内存是不一样的,自然也对应了不同的指令。虚拟地址相同,具体内容不同。 149 | 3.UTEXT+0xb0。也就是0x004000b0。首先,UTEXT顾名思义,就像我们将操作系统的text放在0x80010000一样,这里应该存放的就是某个进程的代码段,也就是PC的起始位置。查阅了网上资料,发现ELF文件并不是一开始就是代码,而是有一些叫做magic的字节用于标识这是一个可执行文件,还有一些header。我们可以利用mips\_4KC-readelf工具来直接观察这个地址。 `Entry point address: 0x4000b0`就是PC值设置为0x4000b0的证据。 150 | ![enter image description here](https://raw.githubusercontent.com/tigerzhaoyue/BUAA_MIPS_OS_DOC/master/Picture/Lab3/elf.JPG) 151 | 参考博客:([可执行文件(ELF)格式的理解 ](http://blog.csdn.net/memray/article/details/42038011)) 152 | 153 | ## Exercise 3.6 154 | #### 根据提示,完成 env_create 函数的填写。 155 | 156 | struct Env *e; 157 | /*Step 1: Use env_alloc to alloc a new env. */ 158 | if((env_alloc(&e,0))< 0) 159 | return; 160 | 161 | /*Step 2: Use load_icode() to load the named elf binary. */ 162 | load_icode(e,binary,size); 163 | 这里没什么难度,只需依次调用alloc函数和进程加载镜像函数,来创建一个进程即可。值得一提的是,由于我们是新创建的进程,所以不算进程创建进程,因此没有父进程,新的env的父env结构体id是0,作为alloc的第二个参数。还有一点,就是我实现进程的创建时,在env\_alloc后,并未将进程的status直接设置为ENV_RUNNABLE,而是在load\_icode函数的最后才将其设置为ENV_RUNNABLE。这也是符合逻辑的,因为在alloc的时候,进程的栈还未设置好,PC值也未设置好。这或许就是"合适的值"的意义所在吧。 164 | ## Exercise 3.7 165 | #### 根据注释与理解,将上述两条进程创建命令加入 init/init.c 中。 166 | 查看这两个文件我们可以看到,每个文件实际上都定义了两个全局变量。 我们以 code\_a.c 为例, unsigned char binary\_user\_A\_start[] 代表的是二进制代码, unsigned int binary\_user\_A\_size 代表的是二进制代码的大小。通过阅读宏 167 | 168 | #define ENV_CREATE(x) \ 169 | { \ 170 | extern u_char binary_##x##_start[];\ 171 | extern u_int binary_##x##_size; \ 172 | env_create(binary_##x##_start, \ 173 | (u_int)binary_##x##_size); \ 174 | } 175 | 就明白了,原来我们需要拼接形成变量名binary\_user\_A\_start[],那么实际上x=user\_A,user_B同理。然后我们将两行代码插入到init.c的注释处即可。 176 | ## Thinking 3.5 177 | #### 思考一下,要保存的进程上下文中的env_tf.pc的值应该设置为多少? 为什么要这样设置? 178 | 需要把env\_tf.pc的地址设置为cp0\_epc的位置。进程切换是由于各种中断触发的,所以,这个进程在再次被恢复执行的时候, 应该执行导致(或遭受)异常的那条指令,相当于异常处理程序处理结束,返回到原来的程序中, 所需要执行的那条指令。而异常返回地址记录在EPC中。也就是说,应该执行EPC(异常返回地址)寄存器中储存的那条指令。 这个知识实际在上学期计算机组成原理P7中已经学习过,这里没有什么难度。 179 | ## Thinking 3.6 180 | #### 思考 TIMESTACK 的含义,并找出相关语句与证明来回答以下关于 TIMESTACK 的问题: 181 | #### • 请给出一个你认为合适的 TIMESTACK 的定义 182 | #### • 请为你的定义在实验中找出合适的代码段作为证据 (请对代码段进行分析) 183 | #### • 思考 TIMESTACK 和第 18 行的 KERNEL_SP 的含义有何不同 184 | 答:TIMESTACK是一个保存上一个现场的栈结构。每个新的进程在run的时候,都需要将其拷贝一份到自己的trap frame里,以便记录该进程中断返回时回到的状态。这也符合栈的直观印象,是一个时间栈,越新的进程将在顶部的位置,每次只需要取顶部的拷贝进新进程的相关结构中即可。 185 | ## Exercise 3.8 186 | #### 根据补充说明,填充完成 env\_ run 函数。 187 | void 188 | env_run(struct Env *e) 189 | { 190 | /*Step 1: save register state of curenv. */ 191 | /* Hint: if there is a environment running,you should do 192 | * context switch.You can imitate env_destroy() 's behaviors.*/ 193 | struct Trapframe *old; 194 | if(curenv) 195 | { 196 | old=(struct Trapframe*)(TIMESTACK-sizeof(struct Trapframe)); 197 | bcopy(old,&curenv->env_tf,sizeof(struct Trapframe)); 198 | curenv->env_tf.pc=old->cp0_epc; 199 | } 200 | /*Step 2: Set 'curenv' to the new environment. */ 201 | curenv = e ; 202 | /*Step 3: Use lcontext() to switch to its address space. */ 203 | lcontext(curenv->env_pgdir); 204 | /*Step 4: Use env_pop_tf() to restore the environment's 205 | * environment registers and drop into user mode in the 206 | * the environment. 207 | */ 208 | /* Hint: You should use GET_ENV_ASID there.Think why? */ 209 | env_pop_tf(&(curenv->env_tf),GET_ENV_ASID(curenv->env_id)); 210 | } 211 | 这里有一点值得注意, lcontext(curenv->env\_pgdir)最初我写成了lcontext(curenv->env\_cr3),导致会出现LOW REFRENCE的警告。仔细查找,在pmap.c中发现了证据: 212 | 213 | pgdir = alloc(BY2PG, BY2PG, 1); //0x0400000 to 0x80401000 4096 bytes 214 | printf("to memory %x for struct page directory.\n", freemem); 215 | mCONTEXT = (int)pgdir; 216 | 也就是说,mCONTEXT中记录的是当前地址空间的页目录的虚拟地址,而查看lCONTEXT汇编函数发现,其作用非常简单,就是将参数传递给mCONTEXT。因此,我们在进行进程切换时,需要把当前进程的页目录的虚拟地址(这里一定是虚拟地址!)作为lCONTEXT汇编函数的参数。而我们在初始化一个进程时,将页目录的虚拟地址存在了env->env\_pgdir中。所以将当前的curenv->env\_pgdir传入调用函数即可。 217 | ## Exercise 3.9 218 | #### 将异常分发代码填入 init/start.S 合适的部分。 219 | .section .text.exc_vec3 220 | NESTED(except_vec3, 0, sp) 221 | .set noat 222 | .set noreorder 223 | 1: 224 | mfc0 k1,CP0_CAUSE 225 | la k0,exception_handlers 226 | andi k1,0x7c 227 | addu k0,k1 228 | lw k0,(k0) 229 | NOP 230 | jr k0 231 | nop 232 | END(except_vec3) 233 | .set at 234 | 代码指导书已经给出,非常简单,我们只需要将其插入到start.S中合适的位置。至于合适的位置,我的理解是这样:首先阅读代码,大意是处理中断的过程。那么处理中断肯定应当是在\_start之后了。其次,其中有一句.set noat我没有看懂,查阅网上资料发现它的意思是屏蔽以后对at的使用,配合后面的.set at打开屏蔽。那么我猜想如果没有.set at,并且把这段代码放到start.S的头部,可能会使后面的代码报错。当然,现在有了.set at应该就可以解决该问题了。 235 | ## Exercise 3.10 236 | #### 将 lds 代码补全使得异常后可以跳到异常分发代码。 237 | 238 | . = 0x80000080; 239 | .except_vec3 : { 240 | *(.text.exc_vec3) 241 | } 242 | 插入到.end以前即可。 243 | ## Exercise 3.12 244 | #### 根据注释,完成 sched_yield 函数的补充。 245 | 246 | void sched_yield(void) 247 | { 248 | static int i = 0; 249 | while(1){ 250 | i = (i+1)%NENV; 251 | if (envs[i].env_status == ENV_RUNNABLE) 252 | env_run(&envs[i]); 253 | } 254 | } 255 | 根据注释/\*Implement simple round-robin scheduling.\*/,我们需要实现一个轮转调度算法。所以,我们需要定义一个局部静态变量(注释中说关于计数的变量应当为静态变量), 用于记录我们当前轮转到了哪里。同时,寻找下一个可以被调度的进程(if (envs[i].env_status == ENV_RUNNABLE)),调用 env_run 来执行这个进程。至于在切换进程时发生了什么,并不是该函数所关心的,应该交给env\_run函数来做。 256 | 257 | ## Thinking 3.7 258 | #### 思考一下你的调度程序,这种调度方式由于某种不可避免的缺陷而造 成对进程的不公平。 259 | #### • 这种不公平是如何产生的? 260 | #### • 如果实验确定只运行两个进程,你如何改进可以降低这种不公平? 261 | -------------------------------------------------------------------------------- /Lab4.md: -------------------------------------------------------------------------------- 1 | # lab4实验报告 2 | 赵岳 3 | 14231027 4 | #### Exercise 4.1 5 | ##### 填写 msyscall,使得系统调用机制可以正常工作。 6 | 7 | move v0,a0 8 | sw a0,0(sp) 9 | sw a1,4(sp) 10 | sw a2,8(sp) 11 | sw a3,12(sp) 12 | syscall 13 | jr ra 14 | 15 | mysyscall主要完成的功能是,设置syscall的各种参数,包括系统调用号(传入v0),和其他参数。由于寄存器的个数限制,只是将a0~a3四个参数使用寄存器进行传递,其余的参数使用栈传递。因此,结合注释,不难写出如上代码。值得注意的是,始终需要记得填写.s汇编函数时,要使用jr ra返回。 16 | #### Exercise 4.2 17 | ##### 实现 lib/syscall\_all.c 中的 void sys\_ipc\_recv(int sysno,u\_int dstva) 函数和 int sys\_ipc\_can\_send(int sysno,u\_int envid, u\_int value, u\_int srcva, u\_int perm) 函数。 18 | 19 | void sys_ipc_recv(int sysno,u_int dstva) 20 | { 21 | if ((unsigned int)dstva >= UTOP || dstva != ROUNDDOWN(dstva, BY2PG)){ 22 | return; 23 | } 24 | curenv->env_ipc_dstva = dstva; 25 | curenv->env_ipc_recving = 1; 26 | curenv->env_status = ENV_NOT_RUNNABLE; 27 | sys_yield(); //not sched yield!! 28 | return 0; 29 | } 30 | 这个函数的主要目的如下:设置当前进程为可接收的状态,然后使当前运行的进程不可运行,暂停当前进程调度,调度其它进程进入cpu。其中参数 dstva是收到的页面的虚拟地址。暂停当前进程调度后,需要使用进程调度函数重新进行进程调度。值得注意的是,这里我认为应该使用在syscallall.c中给出的sys\_yield()函数,而不是我们在lab3中实现的sched\_yield函数。实际上,这两个函数的唯一区别就是 ` bcopy((int)KERNEL_SP-sizeof(struct Trapframe),TIMESTACK-sizeo f(struct Trapframe),sizeof(struct Trapframe)); `这一句话。这句代码的目的是将当前进程的运行环境保存到TIMESTACKsizeof(struct Trapframe)。在理论课中我们知道进程的切换很关键的步骤就是保存线程,既然我们需要将cpu交给其他进程,那这个暂时被搁置的进程就不能不管。因此这一步是很关键的 31 | 32 | int sys_ipc_can_send(int sysno,u_int envid, u_int value, u_int srcva, u_int perm) 33 | { 34 | struct Env *target; 35 | struct Page *page; 36 | Pte *pte; 37 | int r, ret = 0; 38 | if ((r = envid2env(envid, &target, 0)) < 0) 39 | return -E_BAD_ENV; 40 | if (!target->env_ipc_recving) 41 | return -E_IPC_NOT_RECV; 42 | if (srcva) { 43 | if (((unsigned int)srcva >= UTOP)||(srcva != ROUNDDOWN(srcva, BY2PG))||((page = page_lookup(curenv->env_pgdir, srcva, &pte)) == NULL)) 44 | return -E_INVAL; 45 | //illegal address or not rounded or no such a page return -E_INVAL 46 | if (page_insert(target->env_pgdir, page, target->env_ipc_dstva, perm) < 0) 47 | return -E_NO_MEM; 48 | ret = 1; //success = 1 49 | } 50 | target->env_ipc_recving = 0; 51 | target->env_ipc_value = value; 52 | target->env_ipc_from = curenv->env_id; 53 | target->env_ipc_perm = ret? perm : 0 ; 54 | target->env_status = ENV_RUNNABLE; //runnable again 55 | return 0; 56 | } 57 | 这个函数的目的是: 尝试向目标进程(env) 'envid'发送'value' ,如果目标进程没有使用sys\_ipc\_recv请求IPC,那么发送会失败,并且返回-E\_PC\_NOT\_RECV。 否则,发送成功,目标进程的ipc相关的域被按照如下方式设置:env\_ipc\_recving 被设置为 0 以阻塞之后发送的值,env\_ipc\_from 被设置为发送者的 envid,env\_ipc\_value 被设置为'value' 。目标进程被重新设置为可运行的。成功时返回0,错误时返回值小于0。 58 | 那么,根据这个详细的步骤,我们不难写出如上代码。当然,目前还存在几个难以回答的问题: 59 | 1.sysno参数的作用是什么?该函数中好像并没有使用。 60 | 2.难道srcva的作用只是为了判断一下目标地址的正确性吗? 61 | 3.在本函数中的ret是什么作用,为了标记进程通信的成功吗?在我的函数中,如果ret成功置1才能对perm进行赋值,否则为0。 62 | 最后,仔细理解一下该函数中调用的env2envid函数: 63 | 64 | int envid2env(u_int envid, struct Env **penv, int checkperm) 65 | { 66 | struct Env *e; 67 | 68 | /* Hint: 69 | * If envid is zero, return the current environment.*/ 70 | if (envid == 0) { 71 | *penv = curenv; 72 | return 0; 73 | } 74 | 75 | e = &envs[ENVX(envid)]; 76 | 77 | if (e->env_status == ENV_FREE || e->env_id != envid) { 78 | *penv = 0; 79 | return -E_BAD_ENV; 80 | } 81 | 82 | /* Hint: 83 | * Check that the calling environment has legitimate permissions 84 | * to manipulate the specified environment. 85 | * If checkperm is set, the specified environment 86 | * must be either the current environment. 87 | * or an immediate child of the current environment. */ 88 | 89 | if (checkperm && e != curenv && e->env_parent_id != curenv->env_id){ 90 | *penv = 0; 91 | return -E_BAD_ENV; 92 | } 93 | *penv = e; 94 | return 0; 95 | } 96 | 这个函数是作用是:传入一个 envid,然后函数会把 envid 所对应的 env 结构体的指针存在传入的 penv参数中。如果出错会返回 -E\_BAD\_ENV。 97 | 那么至此,我们就相对清楚了该函数的作用。我们在ipc_send函数中,我们将&target作为调用该函数的第二个参数,envid作为第一个参数。这里,由于没有看懂checkperm的机制,因此我们直接跳过了checkperm这一阶段,将0传入。最后,我们将该函数的错误返回值作为调用该函数时的错误返回值即可。 98 | 至此,进程通信部分已经完成。 99 | #### Thinking 4.1 100 | ##### 思考下面的问题,并对这两个问题谈谈你的理解: 101 | ##### • 子进程完全按照 fork() 之后父进程的代码执行,说明了什么? 102 | ##### • 但是子进程却没有执行 fork() 之前父进程的代码,又说明了什么? 103 | 子进程完全按照父进程fork之后的代码执行,说明子进程的代码段和父进程的代码段是一致的。他们是同一个程序,只不过是两个不同的进程。然而子进程是“半路杀出”,从一半开始执行,说明子进程虽然和父进程代码一样,可是开始的位置却不同。具体一点,子进程执行的代码的起始位置应该是父进程的epc。这一点在我们的代码中也有体现:`child->env_tf.pc = child->env_tf.cp0_epc;`。 104 | #### Thinking 4.2 105 | ##### 关于 fork 函数的两个返回值,下面说法正确的是: 106 | ##### A、fork 在父进程中被调用两次,产生两个返回值 107 | ##### B、fork 在两个进程中分别被调用一次,产生两个不同的返回值 108 | ##### C、fork 只在父进程中被调用了一次,在两个进程中各产生一个返回值 109 | ##### D、fork 只在子进程中被调用了一次,在两个进程中各产生一个返回值 110 | C. 111 | #### Exercise 4.3 112 | ##### 填写./lib/syscall_all.c 中的函数 sys\_env\_alloc,可以不填返回值。 113 | 114 | int sys_env_alloc(void) 115 | { 116 | struct Env *child; 117 | if (env_alloc(&child, curenv->env_id) < 0) 118 | return -E_NO_FREE_ENV; 119 | bcopy(KERNEL_SP - sizeof(struct Trapframe), &child->env_tf,sizeof(struct Trapframe)); 120 | child->env_status = ENV_NOT_RUNNABLE; 121 | child->env_tf.pc = child->env_tf.cp0_epc; 122 | child->env_tf.regs[2] = 0; 123 | 124 | return child->env_id; 125 | } 126 | 这个函数我认为有如下几点需要注意: 127 | 1.首先在申请新的进程控制块时判断一下能否申请新的进程,不能的话返回-E\_NO\_FREE\_ENV,尽管这种情况出现的较少。 128 | 2.当前运行进程的现场保存在了`(int)KERNEL_SP-sizeof(struct Trapframe) `(这是有证据可循的,比如sys_yield函数),因此我们需要将其拷贝到新创建进程的tf中。 129 | 3.将新申请的进程状态标记为env_status = ENV_NOT_RUNNABLE,这也是符合逻辑的,我们只是创建了一个新的进程控制块,其数据、栈、代码等其他还未设置好。 130 | 4.在将父进程的运行环境拷贝到子进程后,还需要调整一些东西,首先,使得子进程的调用返回0。由于返回值v0是2号寄存器,因此我们还需要一句`child->env_tf.regs[2] = 0`。 131 | 5.根据实验前的父子进程fork小实验,我们知道子进程是从父进程fork它的地方继续执行,而不是代码的开头。因此我们要在创建子进程时的pc置为父进程的epc,因此我们需要`child->env_tf.pc = child->env_tf.cp0_epc;` 132 | 6.父子进程在fork函数中`envid = sys_env_alloc()`后便“分道扬镳”了,那么我们设置了子进程的返回值为0后,我们还需要管一下父进程。为了区别父子进程,将父进程的返回值设置为子进程的env_id。 133 | #### Exercise 4.4 134 | ##### 补充./lib/syscall\_all.c 中的函数 sys\_env\_alloc 的返回值。 135 | 如上,不再赘述。 136 | #### Exercise 4.5 137 | ##### 补充./user/fork.c 中的函数 fork 中关于 sys\_env\_alloc 的部分和“子进程”执行的部分。 138 | 139 | int fork(void) 140 | { 141 | u_int envid; 142 | int pn; 143 | extern struct Env *envs; 144 | extern struct Env *env; 145 | 146 | set_pgfault_handler(pgfault); 147 | if((envid = syscall_env_alloc()) < 0) 148 | user_panic("syscall_env_alloc failed!"); 149 | if(envid == 0){ 150 | env = &envs[ENVX(syscall_getenvid())]; 151 | return 0; 152 | } 153 | for(pn = 0; pn < ( UTOP / BY2PG) - 1 ; pn ++){ 154 | if(((*vpd)[pn/PTE2PT]) != 0 && ((*vpt)[pn]) != 0) 155 | duppage(envid, pn); 156 | } 157 | if(syscall_mem_alloc(envid, UXSTACKTOP - BY2PG, PTE_V|PTE_R) < 0) 158 | user_panic("In fork! syscall_mem_alloc error!"); 159 | 160 | if(syscall_set_pgfault_handler(envid, __asm_pgfault_handler, UXSTACKTOP) < 0) 161 | user_panic("In fork! syscall_set_pgfault_handler error!"); 162 | 163 | if(syscall_set_env_status(envid, ENV_RUNNABLE) < 0) 164 | user_panic("In fork! syscall_set_env_status error!"); 165 | 166 | return envid; 167 | } 168 | 这个函数可以理解为父进程“恋恋不舍地为子进程开辟了闯荡的道路并为其做好出发的准备”。那么我们要做的就是,为子进程设置页面错误的处理函数(我们暂时不需管怎样处理,这个在最后的函数中会写),用sys\_env\_alloc函数申请一个子进程的进程控制块。然后按页遍历2G的用户空间,如果存在页映射,那就用duppage函数给子进程的页面设置好权限位。调用syscall\_mem\_alloc函数为子进程申请一个错误栈。最后,将子进程的状态利用syscall\_set\_env\_status设置为可以运行的。至此,子进程就可以正式“出发”了。 169 | 这里有一些问题需要注意,首先,我们一再强调了fork是用户态下的函数,那么我们就只能利用大量提供的syscall_x函数来进行一些参数的设置。由于这些syscall函数都是int类型的,所以再调用时进行了一步判断,如果调用错误就会进入user\_panic(由于用户态,不能使用panic),方便调试错误。 170 | 还有一个非常关键的问题,,我们需要将 \_asm\_pgfault\_handler 设置为子进程的页中断函数入口。我们知道,\_asm\_pgfault\_handler 函数(这是一个汇编函数)将出错的地址提取了出来,并作为参数传递给 pgfault 。也就是说,如果我们将子进程的页中断函数设置为pgfault,那么页中断时,就会直接进入 pgfault 函数。这样的结果就是:从cp0中提取出错的虚地 址,恢复寄存器等工作就没代码来做了。所以,我们需要将\_asm\_pgfault\_handler 设置为子 进程的页中断处理函数。 171 | 在后来和其他同学的讨论中,发现错误栈的申请和映射工作可以放在sys\_env\_alloc函数中进行。但在这里,为了和lab3中env\_alloc函数的对应关系,在sys\_env\_alloc只进行了进程控制块的申请工作,其余fork的额外工作全部在fork函数中实现。 172 | #### Thinking 4.3 173 | ##### 如果仔细阅读上述这一段话, 你应该可以发现, 我们并不是对所有的用户空间页都使用 duppage 进行了保护。那么究竟哪些用户空间页可以保护,哪些不可以呢,请结合 include/mmu.h 里的内存布局图谈谈你的看法。 174 | 根据mmu.h中的内存布局,我们可以看到,UTOP是用户空间的极限,duppage肯定为比UTOP小的地址空间服务。其次,我们还注意到,UTOP也叫UXSTACKTOP (`#define UXSTACKTOP (UTOP)`)它的下面一页是 exception stack,每个进程的异常栈都是我们单独处理的,因此我认为这一页也不需要duppage。综上,duppage保护的页应该是截至到(UTOP/BY2PG-1)这一页。 175 | #### Exercise 4.6 176 | ##### 结合注释,补充 user/fork.c 中的函数 duppage。 177 | 178 | static void duppage(u_int envid, u_int pn) 179 | { 180 | u_int perm = ((*vpt)[pn]) & 0xfff;; 181 | if( (perm & PTE_R)!= 0 || (perm & PTE_COW)!= 0){ 182 | if(!(perm & PTE_LIBRARY)) { 183 | perm = perm | PTE_V | PTE_R | PTE_COW; 184 | } 185 | else{ 186 | perm = perm | PTE_V | PTE_R; 187 | } 188 | 189 | if(syscall_mem_map(syscall_getenvid(), pn * BY2PG, envid, pn * BY2PG, perm) < 0) 190 | user_panic("In duppage:mem_map_envid error!"); 191 | 192 | if(syscall_mem_map(syscall_getenvid(), pn * BY2PG, 0, pn * BY2PG, perm) < 0) 193 | user_panic("In duppage:mem_map_0 error!"); 194 | } 195 | else{ 196 | if(syscall_mem_map(syscall_getenvid(), pn * BY2PG,envid, pn * BY2PG, perm) < 0) 197 | user_panic("In duppage:mem_map_envid error!"); 198 | } 199 | } 200 | 这个函数原理较为简单,就是为fork函数填写一个可以正确给出子进程某个页权限位的函数。由于duppage还是在用户态进行的,所以只能使用系统调用提供的函数。我们先利用`perm = ((*vpt)[pn]) & 0xfff;`得到当前传入页面的权限位,然后开始判断:如果父进程中的页是可写或COW的,那么再判断一下是否是共享内存的(`#define PTE_LIBRARY 0x0004 // share memmory`)如果不是,则COW,否则子进程中该页就不用COW。然后在父子进程中分别调用syscall\_mem\_map进行页面的映射,均以COW的权限。当然,还存在父进程中那些只读的页,那么只需要不变地将其映射到子进程中即可。 201 | 在这里,syscall_mem_map函数的第一个参数,也就是本进程的id的确定值得思考。这里我使用了另一个系统调用来得到本进程函数,但在调试过程中发现传0也可以。猜测可能是由于envid2env函数中,传入0则会得到当前的进程,证据如下: 202 | 203 | int envid2env(u_int envid, struct Env **penv, int checkperm) 204 | { 205 | 。。。。。。 206 | 207 | /* Hint: 208 | * If envid is zero, return the current environment.*/ 209 | if (envid == 0) { 210 | *penv = curenv; 211 | return 0; 212 | } 213 | 214 | 。。。。。。 215 | } 216 | 至此,duppage函数就完成了 217 | #### Exercise 4.7 218 | ##### 结合注释,补充 user/fork.c 中的函数 pgfault。 219 | static void 220 | pgfault(u_int va) 221 | { 222 | int r,i; 223 | va = ROUNDDOWN(va, BY2PG); 224 | 225 | if (!((*vpt)[VPN(va)] & PTE_COW )) 226 | user_panic("In pgfault:PTE_COW error!"); 227 | 228 | if (syscall_mem_alloc(0, PFTEMP, PTE_V|PTE_R) < 0) 229 | user_panic("In pgfault:syscall_mem_alloc error!"); 230 | 231 | user_bcopy((void*)va, PFTEMP, BY2PG); 232 | 233 | if (syscall_mem_map(0, PFTEMP, 0, va, PTE_V|PTE_R) < 0) 234 | user_panic("In pgfault:syscall_mem_map error!"); 235 | 236 | if (syscall_mem_unmap(0, PFTEMP) < 0) 237 | user_panic("In pgfault:syscall_mem_unmap error!"); 238 | } 239 | 像前两个函数一样,这个函数还是运行在用户空间的,这对我们函数的实现造成了很大的阻碍。好在丰富的系统调用给了我们足够多的工具。 240 | 步骤大致如下:首先判断该虚拟地址对应的页是否是COW的,不是得话则panic一个error(由于用户态,还是需要使用user\_panic函数)。然后,在PFTEMP位置申请一个新的物理页,将va这里的页拷贝到这个临时物理页上,最后,把临时位置映射的的物理页映射到va上,并且解除临时位置对内存页的映射。 241 | #### Thinking 4.4 242 | ##### 请结合代码与示意图, 回答以下两个问题: 243 | ##### 1、vpt 和 vpd 宏的作用是什么, 如何使用它们? 244 | ##### 2、它们出现的背景是什么? 如果让你在 lab2 中要实现同样的功能, 可以怎么写? 245 | 246 | 1.首先,在mmu.h中,我们可以找到如下定义: 247 | 248 | extern volatile Pte* vpt[]; 249 | extern volatile Pde* vpd[]; 250 | 251 | 查阅资料得知,volatile修饰变量 ,说明这两个变量可能在汇编等C语言之外的地方被改变。那么我们很自然地想到了汇编,因此继续查找.S文件,最终在entry.S发现了这两个变量的定义。 252 | 253 | 254 | .globl vpt 255 | vpt: 256 | .word UVPT 257 | .globl vpd 258 | vpd: 259 | .word (UVPT+(UVPT>>12)*4) 260 | 261 | 根据 mmu.h 和 lab3 得知,UVPT是用户空间中页表的位置。那么,vpd就也是页目录的位置。所以,遍历页目录也就是遍历vpd。遍历vpd时,我们将所有分配给内存的物理页映射给子进程。遍历时还需要判断二级页表是否存在,存在则映射。在映射二级页表的同时,二级页表中分配的物理页也需要映射。 二级页表则通过vpt来访问。 262 | 263 | 2.假设有这样一种需求,我们知道了一个确定的虚拟地址va,需要知道该地址对应的页目录项和页表项的虚拟地址,该如何求?实际上,这个问题的答案就是vpd和vpt。va首先可以拆分为3部分,高10位的PDX,中间10位的PTX,以及低12位的OFFSET。那么要求va对应页目录项的虚拟地址,实际上就是UVPT+(PDX<<2),由于UVPT的低12位是为0的(按页对齐),vpt求法同理。因此: 264 | 265 | ``` 266 | vpd = UVPT[31:12] | PDX | 00; 267 | vpt = UVPT[31:22] | PDX | PTX | 00; 268 | ``` 269 | 至于如何使用,那就显而易见了。由于我们的定义方法,vpt里存着UVPT的首地址,所以在lab2中,作如下代换即可:`(*vpt)[N] = UVPT[N]`。只不过有一点不同就是lab4我们都是工作在用户空间的,而lab2中大量工作在内核空间。 270 | -------------------------------------------------------------------------------- /Lab5.md: -------------------------------------------------------------------------------- 1 | # Lab5实验报告 2 | 赵岳 3 | 14231027 4 | ### Thinking 5.1 5 | ##### 查阅资料,了解Linux/Unix 的/proc 文件系统是什么?有什么作用?Windows 操作系统又是如何实现这些功能的?proc 文件系统这样的设计有什么好处? 6 | 其实/proc并不是一个真正的文件系统,只存在于内存中,访问时,通过访问根目录下的/proc目录即可访问到内核数据。 7 | ### Exercise 5.1 8 | ##### 参考ide_read 函数和read_sector 函数的实现方式,实现fs/ide.c中的ide_write 函数,以及fs/ide_asm.S 中的write_sector 函数,实现对磁盘的写操作。 9 | 10 | void 11 | ide_write(u_int diskno, u_int secno, void *src, u_int nsecs) 12 | { 13 | int offset_begin = secno * 0x200; 14 | int offset_end = offset_begin + nsecs * 0x200; 15 | int offset = 0; 16 | writef("diskno: %d\n", diskno); 17 | while (offset_begin + offset < offset_end) { 18 | user_bcopy(src + offset, (void *)0x93004000, 0x200); 19 | if (write_sector(diskno, offset_begin + offset)) { 20 | // copy data from disk buffer(512 bytes, a sector) to destination array. 21 | offset += 0x200; 22 | } else { 23 | // error occurred, then panic. 24 | user_panic("disk O error"); 25 | } 26 | } 27 | } 28 | 这个函数根据ide_read照猫画虎就行了。 29 | 30 | LEAF(write_sector) 31 | sw a0, 0x93000010 // select the IDE id. 32 | sw a1, 0x93000000 // offset. 33 | li t0, 1 34 | sb t0, 0x93000020 // start write. 35 | lw v0, 0x93000030 // get status as return value. 36 | nop 37 | jr ra 38 | nop 39 | END(write_sector) 40 | 需要注意第四行t0的值应该是1表示写。 41 | ### Exercise 5.2 42 | ##### 文件系统需要负责维护磁盘块的申请和释放,在回收一个磁盘块时,需要更改bitmap 中的标志位。如果要将一个磁盘块设置为free,只需要将bitmap中对应的bit 的值设置为0x0 即可。请完成fs/fs.c 中的free_block 函数,实现这一功能。同时思考为什么参数blockno 的值不能为0 ? 43 | 44 | void 45 | free_block(u_int blockno) 46 | { 47 | // Step 1: Check if the parameter `blockno` is valid (`blockno` can't be zero). 48 | if(blockno==0) 49 | user_panic("zero block in free_block"); 50 | // Step 2: Update the flag bit in bitmap. 51 | bitmap[blockno / 32]=1 << (blockno % 32); //according to guide book,here should be 0x0. but....... 52 | 53 | } 54 | 只能说感谢助教的注释助我轻松完成代码。只是有一点比较疑惑不知道为何指导书中说把bit值设置为0,之前不是规定了空闲是1吗?疑惑。 55 | 根据布局图,blockno=0存储的是Boot Sector and Partition table,这部分当然不能被free,因此出于安全性需要对blockno进行一步check,确保不为0。 56 | ### Thinking 5.2 57 | ##### 请思考,在满足磁盘块缓存的设计的前提下,我们实验使用的内核支持的最大磁盘大小是多少? 58 | 根据fs.h中的宏定义及其注释: 59 | 60 | 61 | /* Maximum disk size we can handle (3GB) */ 62 | #define DISKMAX 0xc0000000 63 | 64 | 得知最大支持磁盘大小为3GB。 65 | 66 | ### Exercise 5.3 67 | ##### fs/fs.c 中的diskaddr 函数用来计算指定磁盘块对应的虚存地址。完成diskaddr 函数,根据一个块的序号(block number),计算这一磁盘块对应的512bytes 虚存的起始地址。(提示:fs/fs.h 中的宏DISKMAP 和DISKMAX 定义了磁盘映射虚存的地址空间) 68 | 69 | u_int 70 | diskaddr(u_int blockno) 71 | { 72 | u_int result; 73 | result=DISKMAP+blockno*BY2BLK; 74 | if( super && blockno>=super->s_nblocks) 75 | user_panic("diskaddr error of blockno %08x\n", blockno); 76 | else 77 | return result; 78 | } 79 | 这道exercise非常简单,就是线性叠加地根据块数计算虚拟地址。既然起点知道,每块大小知道,那么就迎刃而解了。 80 | ### Exercise 5.4 81 | ##### 实现map_block 函数,检查指定的磁盘块是否已经映射到内存,如果没有,分配一页内存来保存磁盘上的数据。对应地,完成unmap_block 函数,用于接触磁盘块和物理内存之间的映射关系,回收内存。(提示:注意磁盘虚拟内存地址空间和磁盘块之间的对应关系)。 82 | 83 | int 84 | map_block(u_int blockno) 85 | { 86 | int r; 87 | // Step 1: Decide whether this block is already mapped to a page of physical memory. 88 | if(block_is_mapped(blockno)) 89 | return 0; 90 | // Step 2: Alloc a page of memory for this block via syscall. 91 | else{ 92 | r=syscall_mem_alloc(syscall_getenvid(), diskaddr(blockno) , PTE_V|PTE_R); 93 | return r; 94 | } 95 | } 96 | 97 | void 98 | unmap_block(u_int blockno) 99 | { 100 | int r; 101 | 102 | // Step 1: check if this block is mapped. 103 | if(block_is_mapped(blockno)){ 104 | // Step 2: if this block is used(not free) and dirty, it needs to be synced to disk, 105 | // can't be unmap directly. 106 | user_assert(block_is_free(blockno) || !block_is_dirty(blockno)); 107 | ; //do nothing (be synced to disk,) 108 | // Step 3: use `syscall_mem_unmap` to unmap corresponding virtual memory. 109 | if(r=syscall_mem_unmap(syscall_getenvid(), diskaddr(blockno))<0) 110 | user_panic("In unmap_block error!"); 111 | } 112 | // Step 4: validate result of this unmap operation. 113 | user_assert(!block_is_mapped(blockno)); 114 | } 115 | 感谢注释,根据和注释填写即可。注意map函数中如果对一个已经map过的block继续map就会导致返回0。 116 | ### Thinking 5.3 117 | ##### 一个Block 最多存储1024 个指向其他磁盘块的指针,试计算,我们的文件系统支持的单个文件的最大大小为多大? 118 | 1024*4KB=4MB,即最大可以指向的其他磁盘块大小和为4MB,也就是支持的最大单个文件。 119 | ### Thinking 5.4 120 | ##### 阅读serve函数的代码,我们注意到函数中包含了一个死循环for (;;) {...},为什么这段代码不会导致整个内核进入panic 状态? 121 | 我们的操作系统是微内核的,文件系统实际上还是一个进程,那么就会有进程调度。以我们的测试程序为例, 122 | 123 | #include "lib.h" 124 | 125 | void umain() 126 | { 127 | while (1) { 128 | writef("IDLE!"); 129 | } 130 | } 131 | 这个代码也有死循环,但运行时也不会引起内核进入panic,那么文件系统中的死循环自然也不会产生panic咯。 132 | 133 | ### Exercise 5.5 134 | ##### 文件user/fsipc.c 中定义了请求文件系统时用到的IPC 操作,user/file.c文件中定义了用户程序读写、创建、删除和修改文件的接口。完成user/fsipc.c中的fsipc_remove 函数、user/file.c 中的remove 函数, 以及fs/serv.c 中的serve_remove 函数,实现删除指定路径的文件的功能。 135 | 136 | int 137 | remove(const char *path) 138 | { 139 | int r; 140 | u_int i; 141 | 142 | if ((r = fsipc_remove(path)) < 0) { 143 | writef("cannont remove file %s\n", path); 144 | return r; 145 | } 146 | return 0; 147 | } 148 | 149 | 150 | int 151 | fsipc_remove(const char *path) 152 | { 153 | struct Fsreq_open *req; 154 | 155 | req = (struct Fsreq_remove *)fsipcbuf; 156 | // Step 1: decide if the path is valid. 157 | if (strlen(path) >= MAXPATHLEN) { 158 | return -E_BAD_PATH; 159 | } 160 | strcpy((char *)req->req_path, path); 161 | // Step 2: Send request to fs server with IPC. 162 | return fsipc(FSREQ_REMOVE, req, 0 , 0); 163 | } 164 | 165 | 166 | void 167 | serve_close(u_int envid, struct Fsreq_close *rq) 168 | { 169 | struct Open *pOpen; 170 | 171 | int r; 172 | 173 | if ((r = open_lookup(envid, rq->req_fileid, &pOpen)) < 0) { 174 | ipc_send(envid, r, 0, 0); 175 | return; 176 | } 177 | 178 | file_close(pOpen->o_file); 179 | ipc_send(envid, 0, 0, 0); 180 | } 181 | 182 | 根据主观上的经验,remove应该是一个比较简单的文件操作,从它的参数数量上也可以看出来。对比open操作,只有open的一个参数。那么我们就可以根据open函数照猫画虎来进行remove函数的填写。 183 | 184 | 185 | 186 | ### 总结 187 | 纵观本次lab5,我们的小操作系统终于有了一些像样了,竟让我有一丝成就感。然而,咋实验过程中,许多函数都可以照猫画虎地填写,有些地方不知为何那样填写,只是对比着蒙对了正确答案。还有的地方在不得其解的时候发现助教已经给出了详尽的注释帮助我们,再次感谢!总之,在交上报告之后还要再重新思考一遍lab5的点点滴滴。最后,把实验的最终结果附在下方: 188 | ![Alt text](./lab5.JPG) 189 | -------------------------------------------------------------------------------- /Lab6.md: -------------------------------------------------------------------------------- 1 | # Lab6实验报告 2 | 赵岳 3 | 14231027 4 | 此版本为待修订版! 5 | ### Thinking 6.1 6 | ##### 示例代码中,父进程操作管道的写端,子进程操作管道的读端。如果现在想让父进程作为“读者”,代码应当如何修改? 7 | 很简单,调换case0和default的代码即可。此时就读写者互换了。 8 | ### Exercise 6.1 9 | ##### 仔细观察pipe 中新出现的权限位PTE_LIBRARY,根据上述提示修改fork 系统调用,使得管道缓冲区是父子进程共享的,不设置为写时复制的模式。 10 | Lab4中已经写好了,就不再赘述了。 11 | ### Exercise 6.2 12 | ##### 根据上述提示与代码中的注释, 填写user/pipe.c 中的piperead、pipewrite、_pipeisclosed 函数并通过testpipe 的测试。 13 | 14 | static int 15 | piperead(struct Fd *fd, void *vbuf, u_int n, u_int offset) 16 | { 17 | int i; 18 | struct Pipe *p; 19 | char *rbuf; 20 | 21 | /*Step 1: Get the pipe p according to fd. And vbuf is the reading buffer. */ 22 | p = (struct Pipe*)fd2data(fd); 23 | rbuf = vbuf; 24 | /*Step 2: If pointer of reading is ahead of writing,then yield. */ 25 | while(p->p_rpos>=p->p_wpos){ 26 | if(_pipeisclosed(fd,p)) // _pipeisclosed to check whether the pipe is closed. 27 | return 0; 28 | syscall_yield(); //syscall 29 | } 30 | /*Step 3: p_buf's size is BY2PIPE, and you should use it to fill rbuf. */ 31 | for(i=0 ; (p->p_rposp_wpos)&&(n--) ; i++ , p->p_rpos++) 32 | rbuf[i] = p->p_buf[p->p_rpos%BY2PIPE]; 33 | return i; 34 | } 35 | 36 | 37 | static int 38 | pipewrite(struct Fd *fd, const void *vbuf, u_int n, u_int offset) 39 | { 40 | int i; 41 | struct Pipe *p; 42 | char *wbuf; 43 | 44 | /*Step 1: Get the pipe p according to fd. And vbuf is the writing buffer. */ 45 | p = (struct Pipe*)fd2data(fd); 46 | wbuf = vbuf; 47 | /*Step 2: If the difference between the pointer of writing and reading is larger than BY2PIPE, then yield. */ 48 | while(p->p_wpos-p->p_rpos>=BY2PIPE){ 49 | if(_pipeisclosed(fd,p)) // _pipeisclosed to check whether the pipe is closed. 50 | return 0; 51 | syscall_yield(); //syscall 52 | } 53 | /*Step 3: p_buf's size is BY2PIPE, and you should use it to fill rbuf. */ 54 | for(i=0 ; ip_wpos++){ 55 | p->p_buf[p->p_wpos%BY2PIPE] = wbuf[i]; 56 | while((ip_wpos-p->p_rpos>=BY2PIPE)) 57 | syscall_yield(); 58 | } 59 | 60 | return n; 61 | } 62 | 63 | 64 | 65 | static int 66 | _pipeisclosed(struct Fd *fd, struct Pipe *p) 67 | { 68 | int pfd, pfp, runs; 69 | 70 | /*Step 1: Get reference of fd and p, and check if they are the same. */ 71 | pfd = pageref(fd); 72 | runs = env->env_runs; 73 | pfp = pageref(p); 74 | while(runs!=env->env_runs){ 75 | pfd = pageref(fd); 76 | runs = env->env_runs; 77 | pfp = pageref(p); 78 | } //again and again 79 | /*Step 2: If they are the same, return 1; otherwise return 0. */ 80 | if(pfd == pfp) 81 | return 1; 82 | return 0; 83 | } 84 | 85 | ### Thinking 6.2 86 | ##### 上面这种不同步修改pp_ref 而导致的进程竞争问题在user/fd.c 中的dup 函数中也存在。请结合代码模仿上述情景,分析一下我们的dup 函数中为什么会出现预想之外的情况? 87 | dup函数的作用是将旧的文件描述符和对应的数据映射到新文件描述符上,类似于我们Lab4中的duppage。dup原来的机制是先映射文件描述符本身再映射其中的内容,通俗地讲,也就是先增加了fd\_ref,再增加了pipe\_ref,类似于close的竞争,我们应该先增加pipe\_ref,否则在某一时刻会出现fd\_ref=prpe_ref的情况从而关闭端口。 88 | ### Thinking 6.3 89 | ##### 阅读上述材料并思考:为什么系统调用一定是原子操作呢?如果你觉得不是所有的系统调用都是原子操作,请给出反例。希望能结合相关代码进行分析。 90 | Linux中并不是所有的系统调用都是原子操作,例如write系统调用就不是。两个进程同时写一个文件,就会出现各自写的数据中相互穿插叠加的情况。我们可以设置一个“专职打字员”负责写,其他进程均以IPC的形式给它发送消息,然后打字员负责将待写入内容排成队列有序地写入。这样可以实现write的原子性。 91 | ### Thinking 6.4 92 | #### 仔细阅读上面这段话,并思考下列问题 93 | ##### • 按照上述说法控制pipeclose 中fd 和pipe unmap 的顺序,是否可以解决上述场景的进程竞争问题?给出你的分析过程。 94 | ##### • 我们只分析了close 时的情形,那么对于dup 中出现的情况又该如何解决?请模仿上述材料写写你的理解。 95 | 1.不能。dup还有竞争存在,还需要解决。 96 | 2.照猫画虎,先处理fd,再处理pipe。 97 | ### Exercise 6.3 98 | ##### 修改user/pipe.c 中的pipeclose 与user/fd.c 中的dup 函数以避免上述情景中的进程竞争情况。 99 | 调换两个if语句即可,先处理fd,再处理pipe。 100 | ### Exercise 6.4 101 | ##### 根据上面的表述,修改_pipeisclosed函数,使得它满足“同步读”的要求。注意env_runs 变量是需要维护的。 102 | 103 | static int 104 | _pipeisclosed(struct Fd *fd, struct Pipe *p) 105 | { 106 | int pfd, pfp, runs; 107 | 108 | /*Step 1: Get reference of fd and p, and check if they are the same. */ 109 | pfd = pageref(fd); 110 | runs = env->env_runs; 111 | pfp = pageref(p); 112 | while(runs!=env->env_runs){ 113 | pfd = pageref(fd); 114 | runs = env->env_runs; 115 | pfp = pageref(p); 116 | } //again and again 117 | /*Step 2: If they are the same, return 1; otherwise return 0. */ 118 | if(pfd == pfp) 119 | return 1; 120 | return 0; 121 | } 122 | ### 阶段性现象 123 | ![Alt text](./7.JPG) 124 | 至此,pipe和竞争测试已经成功完成,下来完成shell部分。 125 | ### Exercise 6.5 126 | ##### 模仿现有的系统调用,增加系统调用syscall_cgetc。(提示:sys_cgetc函数不需要传入参数) 127 | 128 | int 129 | syscall_cgetc() 130 | { 131 | return msyscall(SYS_cgetc, 0, 0, 0, 0, 0); 132 | } 133 | 134 | 135 | ### Exercise 6.6 136 | ##### 根据以上描述,补充完成user/sh.c 中的void runcmd(char *s)。 137 | 138 | case '|': 139 | pipe(p); 140 | if((r = fork())<0) 141 | writef("fork error %d",r); 142 | else if(r == 0) { 143 | dup(p[0],0); 144 | close(p[0]); 145 | close(p[1]); 146 | goto again; 147 | } 148 | else{ 149 | dup(p[1],1); 150 | close(p[1]); 151 | close(p[0]); 152 | rightpipe = r; 153 | goto runit; 154 | } 155 | 156 | 157 | ### 实验现象与总结 158 | Lab6还有许多不完美的地方,还需继续加深理解,调试! 159 | ![Alt text](./lab6.JPG) 160 | -------------------------------------------------------------------------------- /Picture/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/.DS_Store -------------------------------------------------------------------------------- /Picture/7.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/7.JPG -------------------------------------------------------------------------------- /Picture/Lab1/100.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/100.JPG -------------------------------------------------------------------------------- /Picture/Lab1/2.3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/2.3.JPG -------------------------------------------------------------------------------- /Picture/Lab1/HEADreset.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/HEADreset.JPG -------------------------------------------------------------------------------- /Picture/Lab1/bag.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/bag.JPG -------------------------------------------------------------------------------- /Picture/Lab1/branch.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/branch.JPG -------------------------------------------------------------------------------- /Picture/Lab1/cat.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/cat.JPG -------------------------------------------------------------------------------- /Picture/Lab1/grep_c.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/grep_c.JPG -------------------------------------------------------------------------------- /Picture/Lab1/grep_color.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/grep_color.JPG -------------------------------------------------------------------------------- /Picture/Lab1/grep_n.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/grep_n.JPG -------------------------------------------------------------------------------- /Picture/Lab1/grep_regular.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/grep_regular.JPG -------------------------------------------------------------------------------- /Picture/Lab1/grep_v.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/grep_v.JPG -------------------------------------------------------------------------------- /Picture/Lab1/hash.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/hash.JPG -------------------------------------------------------------------------------- /Picture/Lab1/mainstart.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/mainstart.JPG -------------------------------------------------------------------------------- /Picture/Lab1/mv.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/mv.JPG -------------------------------------------------------------------------------- /Picture/Lab1/readelf.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/readelf.JPG -------------------------------------------------------------------------------- /Picture/Lab1/ubuntu denglu.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/ubuntu denglu.JPG -------------------------------------------------------------------------------- /Picture/Lab1/xianxiang.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/xianxiang.JPG -------------------------------------------------------------------------------- /Picture/Lab1/回滚后文件ls.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/回滚后文件ls.JPG -------------------------------------------------------------------------------- /Picture/Lab1/捕获.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab1/捕获.JPG -------------------------------------------------------------------------------- /Picture/Lab2/mem64.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab2/mem64.JPG -------------------------------------------------------------------------------- /Picture/Lab2/page_free_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab2/page_free_list.png -------------------------------------------------------------------------------- /Picture/Lab2/结果.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab2/结果.JPG -------------------------------------------------------------------------------- /Picture/Lab3/elf.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/Lab3/elf.JPG -------------------------------------------------------------------------------- /Picture/lab5.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/lab5.JPG -------------------------------------------------------------------------------- /Picture/lab6.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/troyzhaoyue/BUAA_MIPS_OS_DOC/c4c5635889d83b30ba79261d6cc1c961403fa43a/Picture/lab6.JPG --------------------------------------------------------------------------------