├── .gitignore ├── LICENSE ├── Lab5-Challenge └── doc │ ├── Lab5-Challenge Report.md │ ├── Lab5-Challenge Report.pdf │ ├── Lab5-Challenge.pptx │ └── pics │ ├── Dirty.png │ ├── comparison │ ├── directRead-src.jpg │ ├── directRead.jpg │ ├── directWrite-src.jpg │ ├── directWrite.jpg │ └── writef.jpg │ ├── gxemul │ ├── CP0instrcheck.jpg │ ├── Gxemul-status.jpg │ └── MIPS.jpg │ ├── now-alter │ ├── read.jpg │ └── write.jpg │ └── now │ ├── directRead.jpg │ ├── directWrite.jpg │ └── writef.jpg ├── README.md ├── 实验报告 ├── Lab0 Report │ ├── Lab0 Report.md │ ├── Lab0 Report.pdf │ ├── command │ ├── test │ └── test1 ├── Lab1 Report │ ├── Lab1 Report.md │ ├── Lab1 Report.pdf │ ├── elf.png │ └── proc0.png ├── Lab2 Report │ ├── Addr-Translation.png │ ├── Lab2 Report.md │ ├── Lab2 Report.pdf │ ├── List.png │ └── pages.png ├── Lab3 Report │ ├── Lab3 Report.md │ ├── Lab3 Report.pdf │ ├── int.png │ └── tlb.png ├── Lab4 Report │ ├── COW.png │ ├── Lab4 Report.md │ └── Lab4 Report.pdf ├── Lab5 Report │ ├── Lab5 Report.md │ ├── Lab5 Report.pdf │ └── fs调用.png └── Lab6 Report │ ├── Lab6 Report.md │ ├── Lab6 Report.pdf │ └── spawn.png └── 有关环境 ├── README.md ├── gxemul-0.4.6.tar.gz ├── lab5-compile_env.pdf └── lib32-sysroot.tar.gz /.gitignore: -------------------------------------------------------------------------------- 1 | gxemul/fs.img 2 | fs/fsformat 3 | 4 | tags 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 VOIDMalkuth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Lab5-Challenge/doc/Lab5-Challenge Report.md: -------------------------------------------------------------------------------- 1 | # Lab5-Challenge Report 2 | 3 | ## 一、实验过程 4 | 5 | ### 1. 设备访问保护-内存读写 6 | 7 | ``` 8 | 1. 只有文件系统进程能读写IDE磁盘。 9 | 2. 文件系统进程仅能读写IDE磁盘而不能读写其他的内核地址。 10 | ``` 11 | 12 | 读写设备的方法只有两种,一种是通过内存直接读写,另一种是通过系统调用。我们的进程运行在用户态,理论上来说访问内存中0x80000000以上的位置就会出现问题,但实际上访问可以正常进行。 13 | 14 | ![directRead-src](pics/comparison/directRead-src.jpg) 15 | 16 | ![directRead](pics/comparison/directRead.jpg) 17 | 18 | ![directWrite-src](pics/comparison/directWrite-src.jpg) 19 | 20 | ![directWrite](pics/comparison/directWrite.jpg) 21 | 22 | 经过测试我们发现,这是属于Gxemul的Bug,Gxemul对 MIPS R3000 CPU中 SR 寄存器 KUc位的理解和我们使用的MIPS标准不同。 23 | 24 | ![mips](pics/gxemul/MIPS.jpg) 25 | 26 | 而在Gxemul中 27 | 28 | ```c 29 | /* src/include/mips_cpuregs.h 30 | * 31 | * The R2000/R3000-specific status register bit definitions. 32 | * ...... 33 | * MIPS_SR_KU_CUR Current kernel/user mode bit. 1 => user mode. 34 | */ 35 | ``` 36 | 37 | 既然这样,我们控制内存读写就有两个方案了,第一个方案是改动Gxemul,使其符合MIPS规范,第二个方案是改动我们的代码,让它适应Gxemul。 38 | 39 | #### (1) 控制内存读写-更改Gxemul版 40 | 41 | 改动Gxemul,使其符合MIPS规范,主要修改的地方有: 42 | 43 | - src\cpus\memory_mips_v2p.c 44 | 45 | 修改其中关于用户态、内核态的检测 46 | 47 | ```c 48 | if ((status & MIPS1_SR_KU_CUR) == 0) 49 | ksu = KSU_USER; 50 | else 51 | ksu = KSU_KERNEL; 52 | ``` 53 | 54 | 目前的情况下,用户进程访问内核地址会产生TLE Refill,需要我们在handle_tlb里手动处理。既然已经修改了Gxemul,不妨再修改的彻底点,让他返回ADES/ADEL异常。 55 | 56 | ```c 57 | if (ksu == KSU_USER && vaddr >= 0xffffffff80000000ULL) { 58 | #include 59 | //printf("visiting %x with %d, %x\n", vaddr, ksu, status); 60 | //fatal("visiting %x with %d, %x\n", vaddr, ksu, status); 61 | if(writeflag) { 62 | mips_cpu_exception(cpu, EXCEPTION_ADES, 0, vaddr,0, vaddr_vpn2, vaddr_asid, x_64); 63 | //printf("- [Gxemul] Writing to %x with %d(User Mode), %x\n\n", vaddr, ksu, status); 64 | printf("\033[1m\033[33m Writing to %x with %d(User Mode), %x\033[0m\n\n", vaddr, ksu, status); 65 | } 66 | else { 67 | mips_cpu_exception(cpu, EXCEPTION_ADEL, 0, vaddr,0, vaddr_vpn2, vaddr_asid, x_64); 68 | //printf("- [Gxemul] Reading from %x with %d(User Mode), %x\n\n", vaddr, ksu, status); 69 | printf("\033[1m\033[33m Writing to %x with %d(User Mode), %x\033[0m\n\n", vaddr, ksu, status); 70 | } 71 | } 72 | ``` 73 | 74 | - src\cpus\cpu_mips_coproc.c 75 | 76 | 修改其中CP0 SR寄存器新建时的初始状态值 77 | 78 | ```c 79 | // in function mips_coproc_new 80 | c->reg[COP0_STATUS] = 2; 81 | ``` 82 | 83 | - src\cpus\cpu_mips.c 84 | 85 | 修改其中CP0 SR寄存器在发生异常时的处理,使得异常发生时进入内核态 86 | 87 | ```c 88 | // in function mips_cpu_exception 89 | if (exc_model == EXC3K) { 90 | /* R{2,3}000: Shift the lowest 6 bits to the left two steps:*/ 91 | reg[COP0_STATUS] = ((reg[COP0_STATUS] &~0x3f) | 0x2) + ((reg[COP0_STATUS] & 0xf) << 2); 92 | } 93 | ``` 94 | 95 | 到这里,Gxemul要改动的部分就完了,但是经过运行,发现我们的OS代码里,也有类似的问题:KUc位经常会在内核态被设置为0,然后系统就会出现问题。因此,还需要更改部分代码: 96 | 97 | - boot/start.S 98 | - include/asm/cp0regdef.h (CU0 STATUS) 99 | - include/stackframe.h 100 | - lib/env_asm.S 101 | - genex.S 102 | 103 | 此外,还要添加新的异常处理句柄,在此不再赘述。 104 | 105 | 效果如图(黄色为Gxemul的输出,红色为OS内核的输出): 106 | 107 | ![r](pics/now-alter/read.jpg) 108 | 109 | ![w](pics/now-alter/write.jpg) 110 | 111 | #### (2) 控制内存读写-更改操作系统代码版 112 | 113 | 既然是Gxemul和MIPS理解有出入,那么我们不妨按照Gxemul的想法来? 114 | 115 | 那么,操作系统内核KUc就应该是0(仔细一看代码发现原来就是这样),只需要更改新建进程的status为`0x1000100C`,一切就迎刃而解(刚才搞半天不是白搞了么)。 116 | 117 | 此外还需要增加对tlb_refill的判断,在此不再赘述。鉴于此方法修改少,Challenge的其他部分就基于该方法实现。 118 | 119 | 效果如图(黄色为Gxemul的输出,红色为OS内核的输出): 120 | 121 | ![r](pics/now/directRead.jpg) 122 | 123 | ![w](pics/now/directWrite.jpg) 124 | 125 | ### 2. 设备访问保护-系统调用 126 | 127 | 利用进程控制块的`env_nop`域存放设备权限信息,每一位对应一个设备,可以自由组合。 128 | 129 | 在读写设备时对照进行验证,如果无权限返回`-E_INVAL`。 130 | 131 | 添加`env_create_priority_devperm`函数在创建进程时设置权限,对于`syscall_env_alloc`创建的进程,默认与父进程对设备的读写权限相同。 132 | 133 | 新增`syscall_grant_devperm`系统调用,动态可以设置自身及子进程权限,要授予权限时,进程本身必须有此权限。 134 | 135 | 效果如下: 136 | 137 | ``` 138 | FS Program Writing RTC 139 | [Kernel] Invalid write to devices! 140 | result is -3, data should be 4660 141 | FS Program Reading RTC 142 | [Kernel] Invalid read to devices! 143 | result is -3, data is 1 144 | FS Program Writing fs 145 | result is 0, data should be 305419896 146 | FS Program Reading fs 147 | result is 0, data is 305419896 148 | ``` 149 | 150 | ``` 151 | # clg_sctest.b 152 | [00003805] SPAWN: clg_sctest.b 153 | serve_open 00003805 ffff000 0x0 154 | ::::::::::spawn size : d425 sp : 7f3fdfe0:::::::: 155 | pageout: @@@___0xfffe000___@@@ ins a page 156 | (Parent) Granting perm 0x7 to 0x4807, result is -3 157 | (Parent) Granting perm 0x1 to 0x4807, result is 0 158 | (Parent) Granting perm 0x5 to 0x4807, result is 0 159 | NonFS Program Writing RTC 160 | result is 0, data should be 4660 161 | NonFS Program Reading RTC 162 | result is 0, data is 0 163 | NonFS Program Writing fs 164 | [Kernel] Invalid write to devices! 165 | result is -3, data should be 305419896 166 | NonFS Program Reading fs 167 | [Kernel] Invalid read to devices! 168 | result is -3, data is 0 169 | pageout: @@@___0xfffe000___@@@ ins a page 170 | Child running... 171 | (Child) Granting perm 0x1 to 0x0, result is 0 172 | NonFS Program Writing RTC 173 | [Kernel] Invalid write to devices! 174 | result is -3, data should be 4660 175 | NonFS Program Reading RTC 176 | [Kernel] Invalid read to devices! 177 | result is -3, data is 4660 178 | NonFS Program Writing fs 179 | [Kernel] Invalid write to devices! 180 | result is -3, data should be 305419896 181 | NonFS Program Reading fs 182 | [Kernel] Invalid read to devices! 183 | result is -3, data is 0 184 | ``` 185 | 186 | ### 3. writef优化 187 | 188 | 添加新系统调用`sys_print_string`,打印一个字符串,接收一个`char *`型变量作为参数。 189 | 190 | writef时,先将内容输出到缓冲区,再调用系统调用原子性打印整个字符串。 191 | 192 | 为了保护系统安全,添加safeprint.h,此设置开启时,会将缓冲区固定映射到`0x0fffe000`的位置,系统调用时内核会在页面末尾添加`'\0'`,以防打印至其他区域。 193 | 194 | 使用改进的writef前: 195 | 196 | ![before](pics/comparison/writef.jpg) 197 | 198 | 使用改进的writef后: 199 | 200 | ![after](pics/now/writef.jpg) 201 | 202 | ### 4. 文件写回机制 203 | 204 | Gxemul本身支持对img类磁盘镜像的写入,但是在我们的小操作系统里,由于脏位的设置出现问题,对文件的操作并不能真正写回磁盘。 205 | 206 | fs\fs.c中,`file_dirty`函数置脏位的操作是 207 | 208 | ```c 209 | *(volatile char *)blk = *(volatile char *)blk; 210 | ``` 211 | 212 | 但是,我们文件系统页面映射时即有`PTE_R`权限,且我们并没有相应的机制标志页面为脏。 213 | 214 | 简单方法:直接用系统调用映射: 215 | ```c 216 | syscall_mem_map(0, blk, 0, blk, ((*vpt)[VPN(blk)] & 0xfff) | PTE_D); 217 | ``` 218 | 219 | 自找麻烦方法:建立部分页面脏标记机制,设置新PTE_X权限位(不应与PTE_R同时存在,这样写入就会产生pgfault),设置系统、用户的pgfault处理函数,在此权限位存在时,修改权限增加PTE_D和PTE_R。文件系统写回时,只写回脏页面。 220 | 221 | 对应的,修改部分程序`syscall_mem_map`中的权限,包括 222 | 223 | - fs/fs.c 224 | - fs/serv.c 225 | - user/fd.c 226 | - user/file.c 227 | - user/fsipc.c 228 | - user/spawn.c 229 | 230 | 此外,我们修改libos.c以便设置`pgfault_handler`,并且在进程退出时关闭所有fd。 231 | 232 | 修改写回机制前: 233 | 234 | ``` 235 | $ echo.b %%%%% > motd 236 | $ cat.b motd 237 | 238 | %%%%% 239 | s /motd, the message of the day. 240 | 241 | Welcome to the 6.828 kernel, now with a file system! 242 | 243 | ********** close and restart gxemul ********** 244 | 245 | $ cat.b motd 246 | 247 | This is /motd, the message of the day. 248 | 249 | Welcome to the 6.828 kernel, now with a file system! 250 | ``` 251 | 252 | 修改写回机制后: 253 | 254 | ``` 255 | $ echo.b %%%%% > motd 256 | $ cat.b motd 257 | 258 | %%%%% 259 | s /motd, the message of the day. 260 | 261 | Welcome to the 6.828 kernel, now with a file system! 262 | 263 | ********** close and restart gxemul ********** 264 | 265 | $ cat.b motd 266 | 267 | %%%%% 268 | s /motd, the message of the day. 269 | 270 | Welcome to the 6.828 kernel, now with a file system! 271 | ``` 272 | 273 | ### 5. 终端退格支持 274 | 275 | 终端里如果打错字尝试退格时会出现问题,导致测试时非常麻烦。 276 | 277 | 原因是终端接收到的退格会被理解成`127:DEL`,因此只需修改user/console.c中的`cons_read`函数,增加如下内容。 278 | 279 | ```c 280 | if (c == 127) { 281 | c = '\b'; 282 | writef("\b "); 283 | } 284 | ``` 285 | 286 | 即可解决问题。 287 | 288 | ## 二、实验难点图示 289 | 290 | Dirty位相关操作流程: 291 | 292 | ![dirty](pics/Dirty.png) 293 | 294 | ## 三、体会与感想 295 | 296 | Challenge的自由度很高,同时我也认识到了我们的OS代码只是一个很基础的雏形,在很多方面需要仔细雕琢才有可能应用于实际。 297 | 298 | ## 附录:OS代码中可能存在的Bug 299 | 300 | - mm/pmap.c中`tlb_invalidate`函数只能使curenv对应的TLB项无效。因此,在IPC中,如果进程A向某进程B发送页面,B的目标地址处原本存在页面并在TLB项中,由于IPC发送时curenv为A,所以B的原来页面的TLB表项不会被清除,进而引发问题。 301 | 302 | 测试程序: 303 | 304 | ```c 305 | #include "lib.h" 306 | 307 | /* This test could fail with correct vpt entry and wrong data, that's because tlb entry is wrong 308 | * tlb_invalidate() is not functioning as intended because of curenv is not target env, and the clear will fail 309 | * */ 310 | 311 | void umain() 312 | { 313 | int parentId = syscall_getenvid(); 314 | int childId = fork(); 315 | 316 | u_int *va = 0x30000000; // just pick one 317 | 318 | // both parent and child map page to va 319 | syscall_mem_alloc(0,va, PTE_R | PTE_V); 320 | 321 | if (childId == 0) { 322 | va[0] = 0xcccc; 323 | va[1] = 0x0000; 324 | va[2] = 0xeeee; 325 | } else { 326 | va[0] = 0x1111; 327 | va[1] = 0x2222; 328 | va[2] = 0x3333; 329 | } 330 | 331 | u_int pa = (*vpt)[VPN(va)]; 332 | if (childId == 0) { 333 | writef("Child: [%x] [%x] [%x] [%x]\n", va[0], va[1], va[2], va[3]); 334 | writef("ChildVPT Entry: 0x%x\n", pa); 335 | } else { 336 | writef("Parent: [%x] [%x] [%x] [%x]\n", va[0], va[1], va[2], va[3]); 337 | writef("ParentVPT Entry: 0x%x\n", pa); 338 | } 339 | 340 | if (childId == 0) { 341 | writef("Child: IPC recving\n"); 342 | int val = ipc_recv(0, va, 0); 343 | writef("Child: IPC recv fin, val = 0x%x\n", val); 344 | 345 | pa = (*vpt)[VPN(va)]; 346 | writef("Child(IPC fin): [%x] [%x] [%x] [%x]\n", va[0], va[1], va[2], va[3]); 347 | writef("ChildVPT Entry(IPC fin): 0x%x\n", pa); 348 | 349 | // Child and Parent theoretically now have the same data in va 350 | user_assert(va[0] == 0x1111); 351 | user_assert(va[1] == 0x2222); 352 | user_assert(va[2] == 0x3333); 353 | 354 | writef("Child: fin\n"); 355 | } else { 356 | writef("Parent: IPC sending 0x6666 and map [0x%x] to %d\n", va, childId); 357 | ipc_send(childId, 0x6666, va, PTE_R | PTE_V); 358 | writef("Parent: IPC send finished\n"); 359 | writef("Parent: fin\n"); 360 | } 361 | 362 | return; 363 | } 364 | ``` 365 | 366 | - Gxemul的时钟中断需要进行响应,否则CP0的Cause寄存器会对应的中断位一直是1,如在lab5-extra中就可能会影响中断分发。 367 | 368 | ```none 369 | rtc: 370 | 0x0110 Read or Write: Acknowledge one timer interrupt. (Note that if multiple interrupts are pending, only one is acknowledged.) 371 | ``` 372 | 373 | - fs/fs.c `dir_alloc_file`中,`dir->f_size += BY2BLK`发生在拿到block前,因此即使在获取block失败时仍会增大文件大小。 374 | 375 | - fs/fs.c `file_truncate`中,`f->f_indirect = 0`发生在清空所有块之前,所以一个大于直接索引上限的文件,在truncate至小于直接索引上限之后,简介索引占据的块会无法释放。 376 | 377 | - fs/fs.c `read_bitmap`中,bitmap所占硬盘块数目使用`nbitmap = super->s_nblocks / BIT2BLK + 1;`计算,在`s->nblocks`可整除`BIT2BLK`时会出现问题。 378 | 379 | - fs/fsformat.c `init_disk`中,diff应为block数对每block的bit数取余后的位数对应的Byte数,应使用`diff = NBLOCK % BIT2BLK / 8;`而不是`diff = NBLOCK % BY2BLK / 8;` 380 | 381 | - fs/fsformat.c `make_link_block`调用了`save_block_link`,当需要添加超过直接索引上限的块时,会直接执行如下代码。 382 | 383 | ```c 384 | if(f->f_indirect == 0) { 385 | // create new indirect block. 386 | f->f_indirect = next_block(BLOCK_INDEX); 387 | } 388 | ((uint32_t *)(disk[f->f_indirect].data))[nblk] = bno; 389 | ``` 390 | 391 | 此时,f->f_indirect的值和bno的值是一样的。这样,对任何这一块文件内容的写入就会直接破坏文件的间接索引。 392 | 393 | - fs/ide_asm.S, lib/getc.S, drivers/gxconsole/console.c 中,对设备访问所使用的地址:都在Kseg0里,属于被Cache的区域。理论上来说不应该利用这一段内存来访问设备。事实上由于Gxemul几乎未实现缓存机制,所以暂时没受影响,但是这仍然是一种不合理的行为。 394 | -------------------------------------------------------------------------------- /Lab5-Challenge/doc/Lab5-Challenge Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/Lab5-Challenge Report.pdf -------------------------------------------------------------------------------- /Lab5-Challenge/doc/Lab5-Challenge.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/Lab5-Challenge.pptx -------------------------------------------------------------------------------- /Lab5-Challenge/doc/pics/Dirty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/pics/Dirty.png -------------------------------------------------------------------------------- /Lab5-Challenge/doc/pics/comparison/directRead-src.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/pics/comparison/directRead-src.jpg -------------------------------------------------------------------------------- /Lab5-Challenge/doc/pics/comparison/directRead.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/pics/comparison/directRead.jpg -------------------------------------------------------------------------------- /Lab5-Challenge/doc/pics/comparison/directWrite-src.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/pics/comparison/directWrite-src.jpg -------------------------------------------------------------------------------- /Lab5-Challenge/doc/pics/comparison/directWrite.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/pics/comparison/directWrite.jpg -------------------------------------------------------------------------------- /Lab5-Challenge/doc/pics/comparison/writef.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/pics/comparison/writef.jpg -------------------------------------------------------------------------------- /Lab5-Challenge/doc/pics/gxemul/CP0instrcheck.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/pics/gxemul/CP0instrcheck.jpg -------------------------------------------------------------------------------- /Lab5-Challenge/doc/pics/gxemul/Gxemul-status.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/pics/gxemul/Gxemul-status.jpg -------------------------------------------------------------------------------- /Lab5-Challenge/doc/pics/gxemul/MIPS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/pics/gxemul/MIPS.jpg -------------------------------------------------------------------------------- /Lab5-Challenge/doc/pics/now-alter/read.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/pics/now-alter/read.jpg -------------------------------------------------------------------------------- /Lab5-Challenge/doc/pics/now-alter/write.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/pics/now-alter/write.jpg -------------------------------------------------------------------------------- /Lab5-Challenge/doc/pics/now/directRead.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/pics/now/directRead.jpg -------------------------------------------------------------------------------- /Lab5-Challenge/doc/pics/now/directWrite.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/pics/now/directWrite.jpg -------------------------------------------------------------------------------- /Lab5-Challenge/doc/pics/now/writef.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/Lab5-Challenge/doc/pics/now/writef.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BUAA_OS_2019_Code 2 | BUAA Operating System(操作系统,OS)课程设计个人代码 3 | 4 | 这是本人在北航2019年 *操作系统课程设计(Operating System, OS)* 课程中的代码,谨供各位学弟学妹学习该课程时参考使用。 5 | 6 | 该代码是个人的实现,大部分内容通过了课程的测试,具体请查看对应result分支里的评测记录。即便是通过的代码,也不能保证没有问题。 7 | 8 | ## 说明 9 | 10 | 每个Lab存在于不同分支中,**前面的Lab里可能有错误在后面的Lab中被改正** 11 | 12 | exam开头的分支是上机考试的分支(如您所见惨不忍睹) 13 | 14 | lab5-challenge\*分支为challenge项目,其中lab5-challenge-alter是通过对gxemul的修改完成的,而lab5-challenge分支是通过更改代码来适应gxemul来实现的,具体可以参考相关文档 15 | 16 | 部分分支下有UnitTests目录,是本人对代码中某些部分的测试,其中对于链表的测试`test_queue.c`比较有用,而对sched的测试没有太大意义,可以无视。 17 | 18 | master分支下有本人的实验报告和有关思考题,可以加以参考。(不保证正确性,同时建议各位同学先进行自己的思考再参考) 19 | 20 | ## 使用方法 21 | 22 | 这个README在master分支,请使用形如`git checkout lab6`这样的语句切换到对应分支查看代码。 23 | 24 | 或者,直接在github上下载lab6分支的代码。 25 | 26 | ## 一些小建议 27 | 28 | ~~辱OS课设斗士不请自来~~ 29 | 30 | 当你遇到迷惑问题,找不出自己的问题,可以考虑课程代码实现和自己的实现之间存在冲突的可能(大胆点,很有可能就是祖传代码出错了) 31 | 32 | 虽说OS课设是出名的迷惑,但是还是希望看到这个repository的同学,先尽力自己思考,用正常的调试手段解决问题。在实在迫不得已的情况下再参考往届代码。毕竟,真的搞懂问题所在以及有关原理后的惊喜是任何成绩都无法比拟的。 33 | 34 | **任何迷惑行为的背后,都是其原因的**,细读有关代码,你就会发现问题所在(~~当然,如上文所述,问题不一定出在你写的代码上,整个工具链都可能有奇妙Bug,很有可能是gxemul也错了~~) 35 | 36 | 建议阅读:gxemul文档及代码,MIPS R3000手册(部分内容与gxemul不符),See Mips Run Linux(基本没用,与前两个都不符) 37 | 38 | 此外,**大胆忽略祖传~~坑死人不偿命~~注释**,一定范围内可以自由发挥实现。 39 | 40 | 祝大家OS课设能学的明白,体会到~~通宵~~debug成功后的惊喜。 41 | -------------------------------------------------------------------------------- /实验报告/Lab0 Report/Lab0 Report.md: -------------------------------------------------------------------------------- 1 | # Lab 0 实验报告 2 | 3 | ### 一、实验思考题 4 | 5 | ##### 思考题 0.1 6 | 7 | ``` 8 | 通过你的使用经验,简单分析 CLI Shell,GUI Shell 在你使用过程中的各自优劣(100 字以内) 9 | ``` 10 | 11 | CLI Shell:优势:节省资源,速度快,效率高,控制全面;劣势:上手需要一定时间的学习,界面不直观,视觉效果简陋,容错率低 12 | 13 | GUI Shell:优势:视觉效果好,学习难度低,易于上手;劣势:占用资源多,操作效率低,开启不常用功能时消耗时间长 14 | 15 | *** 16 | 17 | ##### 思考题 0.2 18 | 19 | ``` 20 | 使用你知道的方法(包括重定向)创建下图内容的文件(文件命名为test),将创建该文件的命令序列保存在command文件中,并将test文件作为批处理文件运行,将运行结果输出至result文件中。给出command文件和result文件的内容,并对最后的结果进行解释说明(可以从test文件的内容入手) 21 | 具体实现的过程中思考下列问题: 22 | echo echo Shell Start 与 echo 'echo Shell Start'效果是否有区别 23 | echo echo \$c>file1 与 echo 'echo \$c>file1' 效果是否有区别 24 | ``` 25 | 26 | command文件内容: 27 | 28 | ```bash 29 | echo 'echo Shell Start' >>test 30 | echo 'echo set a = 1' >>test 31 | echo 'a=1' >>test 32 | echo 'echo set b = 2' >>test 33 | echo 'b=2' >>test 34 | echo 'echo set c = a + b' >>test 35 | echo 'c=$[$a + $b]' >>test 36 | echo 'echo c = $c' >>test 37 | echo 'echo save c to ./file1' >>test 38 | echo 'echo $c >file1' >>test 39 | echo 'echo save b to ./file2' >>test 40 | echo 'echo $b>file2' >>test 41 | echo 'echo save a to ./file3' >>test 42 | echo 'echo $a>file3' >>test 43 | echo 'echo save file1 file2 file3 to file4' >>test 44 | echo 'cat file1>file4' >>test 45 | echo 'cat file2>>file4' >>test 46 | echo 'cat file3>>file4' >>test 47 | echo 'echo save file4 to ./result' >>test 48 | echo 'cat file4>>result' >>test 49 | ``` 50 | 51 | 运行结果 52 | 53 | ``` 54 | Shell Start 55 | set a = 1 56 | set b = 2 57 | set c = a + b 58 | c = 3 59 | save c to ./file1 60 | save b to ./file2 61 | save a to ./file3 62 | save file1 file2 file3 to file4 63 | save file4 to ./result 64 | ``` 65 | 66 | result文件内容: 67 | 68 | ``` 69 | 3 70 | 2 71 | 1 72 | ``` 73 | 74 | `echo echo Shell Start` 与` echo 'echo Shell Start'`效果是否有区别: 75 | 76 | 无区别,但是值得注意的是前一条指令中空格的多少不影响最终输出,后一条影响 77 | 78 | `echo echo \$c>file1` 与 `echo 'echo \$c>file1'`效果是否有区别: 79 | 80 | 有区别,前一条指令在控制台不输出,file1中内容为`echo $c`;后一条指令在控制台中输出`echo \$c>file1` 81 | 82 | *** 83 | 84 | ##### 思考题 0.3 85 | ``` 86 | 仔细看看这张图,思考一下箭头中的 add the file 、stage the file 和commit 分别对应的是 Git 里的哪些命令呢? 87 | ``` 88 | 89 | - add the file 对应 git add 90 | - stage the file 对应 git add 91 | - commit 对应 git commit 92 | 93 | *** 94 | 95 | ##### 思考题 0.4 96 | ``` 97 | - 深夜,小明在做操作系统实验。困意一阵阵袭来,小明睡倒在了键盘上。等到小明早上醒来的时候,他惊恐地发现,他把一个重要的代码文件printf.c删除掉了。苦恼的小明向你求助,你该怎样帮他把代码文件恢复呢? 98 | 99 | - 正在小明苦恼的时候,小红主动请缨帮小明解决问题。小红很爽快地在键盘上敲下了git rm printf.c,这下事情更复杂了,现在你又该如何处理才能弥补小红的过错呢? 100 | 101 | - 处理完代码文件,你正打算去找小明说他的文件已经恢复了,但突然发现小明的仓库里有一个叫Tucao.txt,你好奇地打开一看,发现是吐槽操作系统实验的,且该文件已经被添加到暂存区了,面对这样的情况,你该如何设置才能使Tucao.txt在不从工作区删除的情况下不会被git commit指令提交到版本库? 102 | ``` 103 | 104 | 1. `git checkout -- printf.c` 105 | 2. 先执行`git reset HEAD printf.c` 然后执行`git checkout -- printf.c` 106 | 3. `git rm --cached Tucao.txt` 107 | 108 | *** 109 | 110 | ##### 思考题 0.5 111 | 112 | ``` 113 | 思考下面四个描述,你觉得哪些正确,哪些错误,请给出你参考的资料或实验证据。 114 | 115 | 1. 克隆时所有分支均被克隆,但只有HEAD指向的分支被检出。 116 | 2. 克隆出的工作区中执行 git log、git status、git checkout、git commit等操作不会去访问远程版本库。 117 | 3. 克隆时只有远程版本库HEAD指向的分支被克隆。 118 | 4. 克隆后工作区的默认分支处于master分支。 119 | ``` 120 | 121 | 1. 正确 122 | 123 | 2. 正确 124 | 125 | 3. 错误,所有分支均被克隆 126 | 127 | 4. 错误,默认处于HEAD指向的分支,不一定是master 128 | 129 | ###### 依据 130 | 131 | *Manual page git-clone(1)*中指出 132 | 133 | ``` 134 | Clones a repository into a newly created directory, creates remote-tracking branches for each branch in the cloned repository (visible using git branch --remotes), and creates and checks out an initial branch that is forked from the cloned repository’scurrently active branch. 135 | ``` 136 | 137 | 且在试验机上测试,checkout非master时并未从远程版本库下载 138 | 139 | 此外,下面的文章介绍了clone后默认分支的情况以及如何修改HEAD(需要服务器访问权限) 140 | 141 | https://stackoverflow.com/questions/1485578/change-a-git-remote-head-to-point-to-something-besides-master 142 | 143 | https://stackoverflow.com/questions/3301956/git-correct-way-to-change-active-branch-in-a-bare-repository/3302018#3302018 144 | 145 | ``` 146 | So to wrap that up, you have repo A and clone it: 147 | 148 | HEAD references refs/heads/master and that exists 149 | -> you get a local branch called master, starting from origin/master 150 | 151 | HEAD references refs/heads/anotherBranch and that exists 152 | -> you get a local branch called anotherBranch, starting from origin/anotherBranch 153 | 154 | HEAD references refs/heads/master and that doesn't exist 155 | -> "git clone" complains 156 | 157 | 可以使用git symbolic-ref HEAD refs/heads/修改HEAD 158 | ``` 159 | 160 | *** 161 | 162 | ### 二、实验难点图示 163 | 164 | ##### 1. Makefile的使用 165 | 166 | | 项目 | 语法 | 167 | | -------------------- | ----------------------------------------------- | 168 | | 变量定义 | var := val | 169 | | 变量使用 | $(var) | 170 | | 伪目标(必定执行) | .PHONY target | 171 | | 目标,依赖,构建行为 | target: dependence_1 dependence_2 [换行] action | 172 | 173 | ##### 2. 实验中git的使用 174 | 175 | git clone(克隆仓库) 176 | 177 | $\rightarrow$ 修改、书写 178 | 179 | $\rightarrow$ git add(添加至暂存区) 180 | 181 | $\rightarrow$ git commit(commit) 182 | 183 | $\rightarrow$ git push(提交到remote仓库) 184 | 185 | $\rightarrow$ git pull(拉取测试结果) 186 | 187 | *** 188 | 189 | ### 三、体会与感想 190 | 191 | - 难度:中等偏下,主要难度在于初次学习Makefile,以及sed、awk等工具,需要学习的内容较多,以及初次使用CLI Shell环境进行实验需要适应。 192 | - 花费时长:环境配置:4 h,Lab 0实验:2 h 193 | - 体会与感想:命令行下操作还是相当困难的,需要进一步学习。Makefile文件用处很大且有很多技巧,包括GCC等软件都有许多很难掌握但很有用的参数,需要多下功夫。 -------------------------------------------------------------------------------- /实验报告/Lab0 Report/Lab0 Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab0 Report/Lab0 Report.pdf -------------------------------------------------------------------------------- /实验报告/Lab0 Report/command: -------------------------------------------------------------------------------- 1 | echo 'echo Shell Start' >>test 2 | echo 'echo set a = 1' >>test 3 | echo 'a=1' >>test 4 | echo 'echo set b = 2' >>test 5 | echo 'b=2' >>test 6 | echo 'echo set c = a + b' >>test 7 | echo 'c=$[$a + $b]' >>test 8 | echo 'echo c = $c' >>test 9 | echo 'echo save c to ./file1' >>test 10 | echo 'echo $c >file1' >>test 11 | echo 'echo save b to ./file2' >>test 12 | echo 'echo $b>file2' >>test 13 | echo 'echo save a to ./file3' >>test 14 | echo 'echo $a>file3' >>test 15 | echo 'echo save file1 file2 file3 to file4' >>test 16 | echo 'cat file1>file4' >>test 17 | echo 'cat file2>>file4' >>test 18 | echo 'cat file3>>file4' >>test 19 | echo 'echo save file4 to ./result' >>test 20 | echo 'cat file4>>result' >>test 21 | -------------------------------------------------------------------------------- /实验报告/Lab0 Report/test: -------------------------------------------------------------------------------- 1 | echo Shell Start 2 | echo set a = 1 3 | a=1 4 | echo set b = 2 5 | b=2 6 | echo set c = a + b 7 | c=$[$a + $b] 8 | echo c = $c 9 | echo save c to ./file1 10 | echo $c >file1 11 | echo save b to ./file2 12 | echo $b>file2 13 | echo save a to ./file3 14 | echo $a>file3 15 | echo save file1 file2 file3 to file4 16 | cat file1>file4 17 | cat file2>>file4 18 | cat file3>>file4 19 | echo save file4 to ./result 20 | cat file4>>result 21 | -------------------------------------------------------------------------------- /实验报告/Lab0 Report/test1: -------------------------------------------------------------------------------- 1 | echo Shell Start 2 | echo set a = 1 3 | a=1 4 | echo set b = 2 5 | b=2 6 | echo set c = a + b 7 | c=$[$a + $b] 8 | echo c = $c 9 | echo save c to ./file1 10 | echo $c >file1 11 | echo save b to ./file2 12 | echo $b>file2 13 | echo save a to ./file3 14 | echo $a>file3 15 | echo save file1 file2 file3 to file4 16 | cat file1>file4 17 | cat file2>>file4 18 | cat file3>>file4 19 | echo save file4 to ./result 20 | cat file4>>result 21 | -------------------------------------------------------------------------------- /实验报告/Lab1 Report/Lab1 Report.md: -------------------------------------------------------------------------------- 1 | # Lab1 Report 2 | 3 | ### 一、实验思考题 4 | 5 | ##### Thinking 1.1 6 | 7 | *也许你会发现我们的readelf程序是不能解析之前生成的内核文件(内核文件是可执行文件)的,而我们之后将要介绍的工具readelf则可以解析,这是为什么呢?(提示:尝试使用readelf -h,观察不同)* 8 | 9 | 因为我们自己写的readelf运行的机器和testELF文件都是在小端(little endian)环境下运行,而vmlinux是大端(big endian)格式存储,在大端环境下运行的。而linux默认的readelf有识别大小端并按相应方式处理的能力。 10 | 11 | ``` 12 | » readelf -h ./testELF 13 | ELF Header: 14 | ...... 15 | Data: 2's complement, little endian 16 | 17 | » readelf -h ../gxemul/vmlinux 18 | ELF Header: 19 | ...... 20 | Data: 2's complement, big endian 21 | ``` 22 | 23 | *** 24 | 25 | ##### Thinking 1.2 26 | 27 | *内核入口在什么地方?main 函数在什么地方?我们是怎么让内核进入到想要的 main 函数的呢?又是怎么进行跨文件调用函数的呢?* 28 | 29 | - 由于编译脚本中`ENTRY(_start)`指令的作用,内核入口位于`start.S`中的`_start`函数(标号使用`LEAF`宏定义,该宏完成初始化堆栈等工作,以辅助函数调用) 30 | 31 | - `main`函数在`init/main.c`中 32 | 33 | - 我们通过一条jal指令调用`main` 34 | 35 | - 跨文件调用时,跳转地址为一个标签,由链接器在连接过程中重定位至函数的真正地址 36 | 37 | *** 38 | 39 | ### 二、实验难点图示 40 | 41 | ##### 1. 实验用系统系统启动流程 42 | 43 | ![boot](proc0.png) 44 | 45 | ##### 2. ELF文件结构 46 | 47 | elf 48 | 49 | *** 50 | 51 | ### 三、体会与感想 52 | 53 | - 实验难度:中等偏下,主要困难在于要阅读大量源码,搞清楚工作原理。 54 | - 实验耗时:8h- 55 | - 感想:操作系统的启动是一个复杂的过程,在gxemul上进行的实验只是它经过很多简化过后的版本,真实生活中甚至会更麻烦,需要我们在以后的学习工作中仔细分析。 56 | -------------------------------------------------------------------------------- /实验报告/Lab1 Report/Lab1 Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab1 Report/Lab1 Report.pdf -------------------------------------------------------------------------------- /实验报告/Lab1 Report/elf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab1 Report/elf.png -------------------------------------------------------------------------------- /实验报告/Lab1 Report/proc0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab1 Report/proc0.png -------------------------------------------------------------------------------- /实验报告/Lab2 Report/Addr-Translation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab2 Report/Addr-Translation.png -------------------------------------------------------------------------------- /实验报告/Lab2 Report/Lab2 Report.md: -------------------------------------------------------------------------------- 1 | # OS Lab-2 Report 2 | 3 | ## 一、实验思考题 4 | 5 | ### Thinking 2.1 6 | 7 | **请思考cache用虚拟地址来查询的可能性,并且给出这种方式对访存带来的好处和坏处。另外,你能否能根据前一个问题的解答来得出用物理地址来查询的优势?** 8 | 9 | Cache可以用虚拟地址查询 10 | 11 | 虚拟地址查询对访存带来的好处: 12 | - 如果数据在Cache中,可以节省访问TLB/页表的时间 13 | - 由于使用了虚拟地址,可以更好地利用程序的空间局部性 14 | 15 | 虚拟地址查询对访存带来的坏处: 16 | - 不同虚拟地址可能对应同一个物理地址,导致高速缓存重影这一严重的问题,甚至可能损坏硬件,需要额外检查。 17 | - 不同进程的虚拟地址空间不同,要防止访问到错误的数据,还应引入校验位等其他机制,或者在进程切换时清空Cache,不利于进程间通信。 18 | 19 | 物理地址查询的优势: 20 | - Cache设计更简单,重影问题很难发生 21 | - 进程间切换时无开销,在共用同一个物理页面的程序之间(进程间通信)切换时效率更高。 22 | 23 | *** 24 | 25 | ### Thinking 2.2 26 | 27 | **请查阅相关资料,针对我们提出的疑问,给出一个上述流程的优化版本,新的版本需要有更快的访存效率。(提示:考虑并行执行某些步骤)** 28 | 29 | 第3步中,没有命中时,重新装填和返回给CPU数据并行进行。 30 | 31 | *** 32 | 33 | ### Thinking 2.3 34 | 35 | **在我们的实验中,有许多对虚拟地址或者物理地址操作的宏函数(详见include/mmu.h ),那么我们在调用这些宏的时候需要弄清楚需要操作的地址是物理地址还是虚拟地址,阅读下面的代码,指出x是一个物理地址还是虚拟地址** 36 | 37 | x是一个虚拟地址,在C语言(包括汇编语言)中,能直接使用(存、取值)的地址都是虚拟地址。 38 | 39 | *** 40 | 41 | ### Thinking 2.4 42 | **我们在 include/queue.h 中定义了一系列的宏函数来简化对链表的操作。实际上,我们在 include/queue.h 文件中定义的链表和 glibc 相关源码较为相似,这一链表设计也应用于 Linux 系统中 (sys/queue.h 文件)。请阅读这些宏函数的代码,说说它们的原理和巧妙之处。** 43 | 44 | 这一链表设计并不提供提供一个将其他结构体包起来的结构体,而是提供了一种可与其他结构体组合的小结构体。任何一个结构体,只要在自身加入这样一个链表结构体,就能应用链表的各种操作。 45 | 46 | 巧妙之处: 47 | - 小结构体添加方便,基本不影响对原结构体的访问 48 | - 小结构体中le_prev是一个指针的指针,因此在某个位置前后插入/删除节点时都不需要进行额外的遍历 49 | 50 | *** 51 | 52 | ### Thinking 2.5 53 | 54 | **我们注意到我们把宏函数的函数体写成了 do { /\* ... \*/ } while(0)的形式,而不是仅仅写成形如 { /\* ... \*/ } 的语句块,这样的写法好处是什么?** 55 | 56 | 好处: 57 | - 后面必须加分号,符合C语法习惯 58 | - 在省略括号的if...else...结构中不会出错 59 | ```C 60 | #define FUNC(x) do{x = x + 1}while(0) 61 | if (var == 0) 62 | FUNC(x); 63 | else 64 | FUNC(y); 65 | // No Problem 66 | ``` 67 | ```C 68 | #define FUNC(x) {x = x + 1} 69 | if (var == 0) 70 | FUNC(x); 71 | else 72 | FUNC(y); 73 | // Compile Error! 74 | ``` 75 | 76 | *** 77 | 78 | ### Thinking 2.6 79 | 80 | **注意,我们定义的 Page 结构体只是一个信息的载体,它只代表了相应物理内存页的信息,它本身并不是物理内存页。 那我们的物理内存页究竟在哪呢?Page 结构体又是通过怎样的方式找到它代表的物理内存页的地址呢? 请你阅读 include/pmap.h 与 mm/pmap.c 中相关代码,给出你的想法。** 81 | 82 | 物理内存页就在物理内存里,并没有某个特别的结构占用一整个物理内存页。 83 | 84 | 系统启动时根据内存大小申请了一个Page结构体数组,通过Page的虚拟地址和Page结构体数组的头指针之间的差值,可以得到Page结构体对应的物理页框号。物理页框号乘以每页的大小就得到了物理页面的起始地址。 85 | 86 | *** 87 | 88 | ### Thinking 2.7 89 | **请阅读 include/queue.h 以及 include/pmap.h, 将Page\_list的结构梳理清楚,选择正确的展开结构(请注意指针)。** 90 | 91 | A: 92 | ```c 93 | struct Page_list{ 94 | struct { 95 | struct { 96 | struct Page *le_next; 97 | struct Page **le_prev; 98 | }* pp_link; 99 | u_short pp_ref; 100 | }* lh_first; 101 | } 102 | ``` 103 | 104 | B: 105 | ```c 106 | struct Page_list{ 107 | struct { 108 | struct { 109 | struct Page *le_next; 110 | struct Page **le_prev; 111 | } pp_link; 112 | u_short pp_ref; 113 | } lh_first; 114 | } 115 | ``` 116 | 117 | C: 118 | ```c 119 | struct Page_list{ 120 | struct { 121 | struct { 122 | struct Page *le_next; 123 | struct Page **le_prev; 124 | } pp_link; 125 | u_short pp_ref; 126 | }* lh_first; 127 | } 128 | ``` 129 | 130 | 正确的展开结构为C,Page\_list是由LIST_HEAD()宏产生的结构体,内部只包含一个指向链表节点的指针lh\_first。 131 | 132 | *** 133 | 134 | ### Thinking 2.8 135 | 136 | **在 mmu.h 中定义了 bzero(void *b, size_t) 这样一个函数,请你思考,此处的b指针是一个物理地址, 还是一个虚拟地址呢?** 137 | 138 | b是一个虚拟地址,在C语言(包括汇编语言)中,能直接使用(存、取值)的地址都是虚拟地址。 139 | 140 | *** 141 | 142 | ### Thinking 2.9 143 | **了解了二级页表页目录自映射的原理之后,我们知道,Win2k内核的虚存管理也是采用了二级页表的形式,其页表所占的 4M 空间对应的虚存起始地址为 0xC0000000,那么,它的页目录的起始地址是多少呢?** 144 | 145 | 页目录地址为:0xC0000000 + (0xC0000000 >> 12) * 4 = 0xC0300000 146 | 147 | *** 148 | 149 | ### Thinking 2.10 150 | 151 | **注意到页表在进程地址空间中连续存放,并线性映射到整个地址空间,思考:是否可以由虚拟地址直接得到对应页表项的虚拟地址?上一节末尾所述转换过程中,第一步查页目录有必要吗,为什么?** 152 | 153 | 可以由虚拟地址直接得到对应页表项的虚拟地址。但转换过程中,第一步查页目录仍有必要,因为我们并不知道页表项虚拟地址所对应的物理地址。 154 | 155 | *** 156 | 157 | ### Thinking 2.11 158 | 159 | **观察给出的代码可以发现,page_insert 会默认为页面设置 PTE_V的权限。请问,你认为是否应该将 PTE_R 也作为默认权限?并说明理由。** 160 | 161 | 不应当,PTE_R会提供写权限,而我们的分配的页面可能是用来存放代码、常量等只读数据的。 162 | 163 | *** 164 | 165 | ### Thinking 2.12 166 | 167 | **思考一下tlb_out 汇编函数,结合代码阐述一下跳转到NOFOUND的流程?从MIPS手册中查找tlbp和tlbwi指令,明确其用途,并解释为何第10行处指令后有4条nop指令。** 168 | 169 | - tlbp:查询TLB内容。 170 | - tlbwi:向TLB指定索引处写入值。 171 | - 流程:先保存CP0_ENTRYHI,从CP0协处理器中查询TLB项,如果没有结果,证明查询的内容不在TLB中,跳转至NOFOUND,还原CP0_ENTRYHI;如果有结果,则向原表项处存入0,即使其无效,再跳转至NOFOUND,还原CP0_ENTRYHI 172 | - 4条nop指令是为了清空流水线,防止CP0遇险 173 | 174 | *** 175 | 176 | ### Thinking 2.13 177 | 178 | **显然,运行后结果与我们预期的不符,va值为0x88888,相应的pa中的值为0。这说明我们的代码中存在问题,请你仔细思考我们的访存模型,指出问题所在。** 179 | 180 | page_insert函数只是将va所在的虚拟页号和一个物理页面对应起来,而va2pa函数返回的地址是va所在页的开头,因此va对应的物理地址并不是pa,而应该是pa+页内偏移(0x450) 181 | 182 | *** 183 | 184 | ### Thinking 2.13 185 | 186 | **在X86体系结构下的操作系统,有一个特殊的寄存器CR4,在其中有一个PSE位,当该位设为1时将开启4MB大物理页面模式,请查阅相关资料,说明当PSE开启时的页表组织形式与我们当前的页表组织形式的区别。** 187 | 188 | PSE开启时,支持4MB的页面大小。每条页目录项第7位(Page Size)的值为1时,页目录项指向的是一个4MB(4MB对齐)的物理页面而不是普通的页表(PS位为0是指向的仍然是二级页表)。 189 | 190 | ## 二、实验难点图示 191 | 192 | 1. List链表结构 193 | ![List](List.png) 194 | 2. pages数组结构 195 | ![pages](pages.png) 196 | 3. 虚拟地址-物理地址转换 197 | ![addr](Addr-Translation.png) 198 | 199 | ## 三、体会与感想 200 | 201 | 本次实验难度为中高,总共花费20小时左右,主要难度在于对代码的阅读,链表结构的理解,特别是pages数组怎么转换到物理页面的这一点。 -------------------------------------------------------------------------------- /实验报告/Lab2 Report/Lab2 Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab2 Report/Lab2 Report.pdf -------------------------------------------------------------------------------- /实验报告/Lab2 Report/List.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab2 Report/List.png -------------------------------------------------------------------------------- /实验报告/Lab2 Report/pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab2 Report/pages.png -------------------------------------------------------------------------------- /实验报告/Lab3 Report/Lab3 Report.md: -------------------------------------------------------------------------------- 1 | # OS Lab-3 Report 2 | 3 | ## 一、实验思考题 4 | 5 | ### Thinking 3.1 6 | 7 | **为什么我们在构造空闲进程链表时必须使用特定的插入的顺序?(顺序或者逆序)** 8 | 9 | 10 | 按逆序插入,因为后期我们取用时使用`LIST_FIRST()`,第一个被取用的一定是`env[0]`,这样,我们第一个进程的envid值就是确定的,便于后期建立所有进程的进程树关系。 11 | 12 | *** 13 | 14 | ### Thinking 3.2 15 | 16 | **思考env.c/mkenvid 函数和envid2env 函数:** 17 | 18 | **• 请你谈谈对mkenvid 函数中生成id 的运算的理解,为什么这么做?** 19 | 20 | **• 为什么envid2env 中需要判断e->env_id != envid 的情况?如果没有这步判断会发生什么情况?** 21 | 22 | - 生成id:envid由两部分组成,低10位为env块在env数组中的编号;高22位为该进程是到目前为止运行的第几个进程。这样既能快速找到一个envid对应的env块,又能确定该进程不会与历史上已经结束的进程混淆。 23 | - 需要确定找到的就是envid指向的进程,且该进程仍然存活,而不是某个正好占据相同位置的新进程。如果不做此步检查,当一个老进程结束后一个新进程正好占据老进程的位置,此时再用老进程的envid就会找到新进程的env块,引发问题。 24 | 25 | *** 26 | 27 | ### Thinking 3.3 28 | 29 | **结合include/mmu.h 中的地址空间布局,思考env_setup_vm 函数:** 30 | 31 | **• 我们在初始化新进程的地址空间时为什么不把整个地址空间的pgdir 都清零,而是复制内核的boot_pgdir作为一部分模板?(提示:mips 虚拟空间布局)** 32 | 33 | **• UTOP 和ULIM 的含义分别是什么,在UTOP 到ULIM 的区域与其他用户区相比有什么最大的区别?** 34 | 35 | **• 在env_setup_vm 函数的最后,我们为什么要让pgdir[PDX(UVPT)]=env_cr3?(提示: 结合系统自映射机制)** 36 | 37 | **• 谈谈自己对进程中物理地址和虚拟地址的理解** 38 | 39 | - 我们的操作系统采用的是2G/2G模式,内核态的地址空间对所有的进程都是一样的,以boot_pgdir作为一部份模板,可以使内核所在空间对用户进程可见,便于用户进程和内核的交互。 40 | - UTOP是用户进程可以自由使用的地址空间的最高点,ULIM是kuseg和kseg0的分界。用户进程对在UTOP到ULIM之间区域的内存一般没有写权限 41 | - 这样,用户进程可以直接使用UVPT这一虚拟地址访问页表,而UVPT是一个满足自映射条件的地址,在此处放置页表可以利用自映射性质省下一页的内存占用。 42 | - 进程中,直接用来访问内存的地址都是虚拟地址。真实地址只有在TLB重填时才会被用到。可以说,对用户态进程来说,物理地址是透明的,用户进程无需关心数据的物理地址,只要有虚拟地址即可正常访问。 43 | 44 | *** 45 | 46 | ### Thinking 3.4 47 | 48 | **思考user_data 这个参数的作用。没有这个参数可不可以?为什么?(如果你能说明哪些应用场景中可能会应用这种设计就更好了。可以举一个实际的库中的例子)** 49 | 50 | user_data这个参数允许我们更好的定制load_elf的行为,没有这个参数会影响系统的灵活性。我们在load时,可能会使用多种不同的mapper,这些mapper可能会需要不同的额外数据来辅助进行映射,void *类型的user_data是一个最好的传递额外数据的方式,因为向void *型指针强制转换可以自动完成,同时void *可读性也更好。在真实库中,如果某个函数需要使用到用户提供的函数,且希望具有类似泛型的,可处理多种数据的能力,就会用到这种设计。例如,C标准库中在stdlib.h里定义的qsort函数接受的cmp函数指针,就有两个void *型的参数。 51 | 52 | *** 53 | 54 | ### Thinking 3.5 55 | 56 | **结合load_icode_mapper 的参数以及二进制镜像的大小,考虑该函数可能会面临哪几种复制的情况?你是否都考虑到了? (提示:1、页面大小是多少;2、回顾lab1中的ELF文件解析,什么时候需要自动填充.bss段)** 57 | 58 | - va 59 | - va与页面大小对齐 60 | - va与页面大小不对齐 61 | 62 | - bin_size 63 | - bin_size <= 1页 64 | - bin_size > 1页 65 | 66 | - va + bin_size 67 | - va + bin_size后还在va所在页内 68 | - va + bin_size后超出va所在页内 69 | - va + bin_size与页面大小对齐 70 | - va + bin_size与页面大小不对齐 71 | 72 | - sgsize 73 | - sgsize > bin_size:需要填充 74 | - sgsize = bin_size:不需要填充 75 | 76 | *** 77 | 78 | ### Thinking 3.6 79 | 80 | **思考上面这一段话,并根据自己在lab2 中的理解,回答:** 81 | 82 | **• 我们这里出现的” 指令位置” 的概念,你认为该概念是针对虚拟空间,还是物理内存所定义的呢?** 83 | 84 | **• 你觉得entry_point其值对于每个进程是否一样?该如何理解这种统一或不同?** 85 | 86 | - “指令位置”针对的是虚拟空间,因为我们取指时用的地址是虚拟地址。 87 | - 不一定一样。大部分ELF格式可执行文件的entry_point都是相同的,但是ELF格式文件中也允许设定程序的entry_point,这正体现了虚拟内存的优势:进程可以自主的决定自己的布局。此外,操作系统可以支持多种类型的可执行文件,这些可执行文件的entry_point也不一定相同。 88 | 89 | *** 90 | 91 | ### Thinking 3.7 92 | 93 | **思考一下,要保存的进程上下文中的env_tf.pc的值应该设置为多少?为什么要这样设置** 94 | 95 | 应设为`env_tf.cp0_epc`。在我们的OS里,如果要进行进程切换,一定是因中断发生后的处理过程中,进入env_run时如果当前curenv不是null,则当前进程进入中断时的寄存器状态必定在TIMESTACK处存放(中断处理时会先调用`.\include\stackframe.h`中的saveall,而saveall依赖的sp指针值在时钟中断(目前唯一的中断)时正是TIMESTACK)。由于是通过中断进入的,EPC指向的值就是受害指令,如果我们以后要恢复这个进程的运行,当然是从受害指令开始重新执行,因此应设为`env_tf.cp0_epc`。 96 | 97 | *** 98 | 99 | ### Thinking 3.8 100 | 101 | **思考TIMESTACK 的含义,并找出相关语句与证明来回答以下关于TIMESTACK 的问题:** 102 | 103 | **• 请给出一个你认为合适的TIMESTACK 的定义** 104 | 105 | **• 请为你的定义在实验中找出合适的代码段作为证据(请对代码段进行分析)** 106 | 107 | **• 思考TIMESTACK 和第18 行的KERNEL_SP 的含义有何不同** 108 | 109 | TIMESTACK就是时钟中断发生时系统存放相关数据(上下文、现场)时使用的栈空间,地址是0x82000000。证据如下: 110 | 111 | 从.\lib\traps.c中我们知道,所有异常的处理都是由异常处理句柄完成的,而一部分具体的异常处理句柄可以在.\lib\genex.S中找到。 112 | 113 | 异常处理句柄其实是由宏构建起来的,如下 114 | ```asm 115 | .macro BUILD_HANDLER exception handler clear 116 | .align 5 117 | NESTED(handle_\exception, TF_SIZE, sp) 118 | .set noat 119 | nop 120 | SAVE_ALL 121 | __build_clear_\clear 122 | .set at 123 | move a0, sp 124 | jal \handler 125 | nop 126 | j ret_from_exception 127 | nop 128 | END(handle_\exception) 129 | .endm 130 | ``` 131 | 这个宏表现出了异常处理句柄的一般形式:先保存上下文(SAVE_ALL),在跳转至特定的异常处理函数,最后从异常返回。其中,保存上下文(SAVE_ALL)这个过程定义在.\include\stackframe.h里 132 | 133 | SAVE_ALL先调用get_sp来获得栈指针,并把寄存器等上下文信息存入栈中,get_sp内容如下: 134 | 135 | ```asm 136 | .macro get_sp 137 | mfc0 k1, CP0_CAUSE 138 | andi k1, 0x107C 139 | xori k1, 0x1000 140 | bnez k1, 1f 141 | nop 142 | li sp, 0x82000000 143 | j 2f 144 | nop 145 | 1: 146 | bltz sp, 2f 147 | nop 148 | lw sp, KERNEL_SP 149 | nop 150 | 2: nop 151 | .endm 152 | ``` 153 | 154 | get_sp所做的事情其实是:如果CP0_CAUSE中,exccode的值为0且IRQ4值为1,则使用0x82000000作为栈。否则,如果sp > 0x80000000,则直接使用sp,否则使用KERNEL_SP作为栈地址。而0x82000000就是我们的TIME_STACK,同时,IRQ4正是时钟中断的中断请求。 155 | 156 | KERNEL_SP是内核处理各种异常中断时的通用的栈,而TIME_STACK专用于处理时钟中断和与之紧密联系的进程切换等任务。 157 | 158 | *** 159 | 160 | ### Thinking 3.9 161 | 162 | **阅读 kclock_asm.S 文件并说出每行汇编代码的作用** 163 | 164 | 除了引用之外,kclock_asm.S可以分为两部分,第一部分是一个宏,负责对CP0的Status寄存器进行设置,将set中为1的位置为1,将clr中为1的位置为0 165 | 166 | ```asm 167 | .macro setup_c0_status set clr 168 | .set push 169 | mfc0 t0, CP0_STATUS 170 | or t0, \set|\clr 171 | xor t0, \clr 172 | mtc0 t0, CP0_STATUS 173 | .set pop 174 | .endm 175 | ``` 176 | 177 | 第二部分是一个函数set_timer,负责开启时钟中断 178 | 179 | ```asm 180 | .text 181 | LEAF(set_timer) 182 | li t0, 0x01 183 | sb t0, 0xb5000100 184 | sw sp, KERNEL_SP 185 | setup_c0_status STATUS_CU0|0x1001 0 186 | jr ra 187 | nop 188 | END(set_timer) 189 | ``` 190 | 第1,2行 191 | ``` 192 | li t0, 0x01 193 | sb t0, 0xb5000100 194 | ``` 195 | 的作用是,让时钟以1s一次的频率产生中断。0xb5000100为时钟的控制地址 196 | 197 | 接下来`sw sp, KERNEL_SP`设置KERNEl_SP,内核异常处理栈的值 198 | 199 | 然后`setup_c0_status STATUS_CU0|0x1001 0`设置CP0的Status寄存器,STATUS_CU0为仅开启CU0,表示CP0存在的状态,0x1001,最低位1开启终端,第13位1使能IRQ4即时钟中断 200 | 201 | 最后`jr ra`返回主调函数 202 | 203 | `LEAF(set_timer)`和`END(set_timer)`都是设置一些调试/链接用到的符号信息,在此不深入解释 204 | 205 | *** 206 | 207 | ### Thinking 3.10 208 | 209 | **阅读相关代码,思考操作系统是怎么根据时钟周期切换进程的。** 210 | 211 | 时钟中断发生时,系统在保存上下文之后跳转到sched_yield函数,进行进程的调度。 212 | 213 | sched_yield函数首先判断当前进程时间片是否用完,若未用完继续执行当前进程,否则根据调度算法选择一个新进程继续执行,原进程上下文被保存并再次进入就绪队列。 214 | 215 | 最终,新的进程通过调用env_run函数被执行。 216 | 217 | *** 218 | 219 | ## 二、实验难点图示 220 | 221 | - 中断处理流程 222 | ![int](int.png) 223 | 224 | - tlb重填流程 225 | ![tlb](tlb.png) 226 | 227 | ## 三、体会与感想 228 | 229 | - 耗时:15h+ 230 | - 难度评价:难 231 | - 本次Lab明显比前几次明显增大,部分原因在于进程管理本身就是个难点,而且还与Lab2结合,很可能一些早先没发现的bug就在这次Lab中出现问题了。这次的难度在于理解代码,再仅仅依靠注释无脑补全已经不现实了。需要把代码(包括且特别是汇编代码,genex.S)仔细阅读并理解。同时在处理调度算法和映射这部分内容时,一定要考虑好各种不同的情况。 232 | 233 | ## 四、【可选】指导书反馈 234 | 235 | 建议在指导书中加入更多汇编代码相关的介绍,这部分内容对我们对操作系统的理解一样很重要。比方说genex.S就非常重要。此外,希望可以改一下,注明bcopy和bzero必须在对齐地址上使用(或者直接改一下这两个函数?)。 236 | 237 | ## 五、【可选】残留难点 238 | 239 | - 为什么env_destory的时候要把KERNEL_SP里的TRapframe拷贝到TIMESTACK里?在env_destory的时候已经把curenv设置为null了,理论上来说在之后运行sched_yield的时候,curenv是null,env_run也应该不需要保存TIMESTACK里的信息了 240 | - 为什么专门要用env_sched_link连接调度env块?创建新进程插入的时候就应该已经把这个和freelist全部链接断开了,再用env_link应该也没问题吧。 -------------------------------------------------------------------------------- /实验报告/Lab3 Report/Lab3 Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab3 Report/Lab3 Report.pdf -------------------------------------------------------------------------------- /实验报告/Lab3 Report/int.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab3 Report/int.png -------------------------------------------------------------------------------- /实验报告/Lab3 Report/tlb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab3 Report/tlb.png -------------------------------------------------------------------------------- /实验报告/Lab4 Report/COW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab4 Report/COW.png -------------------------------------------------------------------------------- /实验报告/Lab4 Report/Lab4 Report.md: -------------------------------------------------------------------------------- 1 | # OS Lab-4 Report 2 | 3 | ## 一、实验思考题 4 | 5 | ### Thinking 4.1 思考并回答下面的问题: 6 | 7 | - *内核在保存现场的时候是如何避免破坏通用寄存器的?* 8 | - *系统陷入内核调用后可以直接从当时的\$a0-\$a3 参数寄存器中得到用户调用msyscall 留下的信息吗?* 9 | - *我们是怎么做到让sys 开头的函数“认为”我们提供了和用户调用msyscall 时同样的参数的?* 10 | - *内核处理系统调用的过程对Trapframe 做了哪些更改?这种修改对应的用户态的变化是?* 11 | 12 | 1. 内核在保存现场时,首先利用\$k0, \$k1这两个不需要保护的寄存器,获取应该使用的\$sp值,然后首先将\$sp存入栈空间,再利用\$sp将所有寄存器的值入栈。这样在退出异常时可以进行恢复。 13 | 2. 可以(在本实验环境给定的代码中),因为本实验中从用户调用msyscall到跳转进入真正的handler的过程中没有用到\$a0-\$a3这些寄存器。但更推荐从栈中获取,这样可以防止其他程序使用这四个寄存器带来的问题。 14 | 3. 我们在syscall.S中将参数拷贝到内核的栈中和对应的寄存器中,将栈和寄存器“伪装成”正常调用sys开头的函数时的状态。 15 | 4. 设置epc的值为epc + 4,对应用户态pc + 4,返回时从syscall的后一条指令开始执行。系统调用的返回值放在v0中,用户态得到syscall_*的返回值 16 | 17 | *** 18 | 19 | ### Thinking 4.2 思考下面的问题,并对这两个问题谈谈你的理解: 20 | 21 | - *子进程完全按照fork() 之后父进程的代码执行,说明了什么?* 22 | - *但是子进程却没有执行fork() 之前父进程的代码,又说明了什么?* 23 | 24 | 1. fork()刚执行完时,父子进程的代码段以及大部分数据(除了fork的返回值外)内容完全相同。 25 | 2. 子进程是的pc值是fork时从父进程的pc复制来的。 26 | 27 | *** 28 | 29 | ### Thinking 4.3 关于fork 函数的两个返回值,下面说法正确的是: 30 | 31 | ``` 32 | A. fork 在父进程中被调用两次,产生两个返回值 33 | B. fork 在两个进程中分别被调用一次,产生两个不同的返回值 34 | C. fork 只在父进程中被调用了一次,在两个进程中各产生一个返回值 35 | D. fork 只在子进程中被调用了一次,在两个进程中各产生一个返回值 36 | ``` 37 | 38 | 正确答案是C,fork前子进程并不存在,子进程在父进程调用fork时被创建,并赋予不同的返回值。 39 | 40 | *** 41 | 42 | ### Thinking 4.4 43 | 44 | *如果仔细阅读上述这一段话, 你应该可以发现, 我们并不是对所有的用户空间页都使用duppage 进行了保护。那么究竟哪些用户空间页可以保护,哪些不可以呢,请结合include/mmu.h 里的内存布局图谈谈你的看法。* 45 | 46 | 不可写(PTE_R未设置)或父子进程共享(设置PTE_LIBRARY)的页,我们不应用PTE_COW予以保护。除此以外USTACKTOP以下可写的段都可以保护,USTACKTOP到UTOP之间的位置不能被保护。在\[USTACKTOP, UXSTACKTOP - BY2PG\)位置存放的是无效内存,而\[UXSTACKTOP - BY2PG, UXSTACKTOP)位置是用户的缺页异常处理栈,父子进程不能共用,而且处理page fault需要借助以上两片空间,因此我们不能用PTE_COW保护。User VPT对应的从UVPT开始的4M空间是进程的页表我们显然不能从父进程复制,因此也就没有保护的必要。UPAGES和UENVS对应的4M空间都是所有进程共享因此不需要保护。再向上是内核空间我们同样不应用PTE_COW权限位。 47 | 48 | *** 49 | 50 | ### Thinking 4.5 51 | 52 | *在遍历地址空间存取页表项时你需要使用到vpd 和vpt 这两个“指针的指针”,请思考并回答这几个问题:* 53 | 54 | - *vpt 和vpd 的作用是什么?怎样使用它们?* 55 | - *从实现的角度谈一下为什么能够通过这种方式来存取进程自身页表?* 56 | - *它们是如何体现自映射设计的?* 57 | - *进程能够通过这种存取的方式来修改自己的页表项吗?* 58 | 59 | 1. vpt是指向虚拟页表的指针**的指针**,vpd是指向虚拟页目录的指针**的指针**,\*vpt = 7fc00000 = UVPT,而 \*vpd = 7fdff000 = (UVPT+(UVPT>>12)*4)。我们通过((Pte \*) (\*vpt))\[pgtblIndex\]来获取pgtblIndex对应的页表项,通过((Pde \*) (\*vpd))\[pgdirIndex\]来获取pgdirIndex对应的页表项。 60 | 2. vpt和vpd在entry.S汇编中定义,指向UVPT和UVPT中页目录对应的虚拟地址。而具体能访问到页表是因为我们在env_setup_vm中,将UVPT ~ ULIM所在的4M空间对应的一张二级页表的物理地址设为页目录自身。这样当我们利用((Pde \*) (\*vpd))\[pgdirIndex\]访问时,根据自映射访问的就是页目录所在的一页的第pgdirIndex条记录。 61 | 62 | 而当我们向某个还没有二级页表的地址添加一条页面映射记录时,会将新增加的这张二级页表加入页目录,又因为页目录同时也是UVPT ~ ULIM所在的4M空间对应的一张二级页表,因此这张新的二级页表会被自然而然地映射到虚拟地址UVPT ~ ULIM中的一页;而且这一映射和物理地址是等比例的,故((Pte \*) (\*vpt))\[pgtblIndex\]所在的地址就是pgtblIndex号页表项的虚拟地址,其本身pgtblIndex对应的页表项内容。 63 | 3. 正是因为自映射,只需要在页目录项中映射一个表项,就自然而然将整个页表(包括新增的)映射到UVPT ~ ULIM所在的4M空间中(具体参见2)。 64 | 4. 不能,这些页表/页目录在映射时权限是只读的。 65 | 66 | ### Thinking 4.6 67 | 68 | *page_fault_handler 函数中,你可能注意到了一个向异常处理栈复制Trapframe 运行现场的过程,请思考并回答这几个问题:* 69 | 70 | - *这里实现了一个支持类似于“中断重入”的机制,而在什么时候会出现这种“中断重入”?* 71 | - *内核为什么需要将异常的现场Trapframe 复制到用户空间?* 72 | 73 | 1. 在page fault的处理过程中,如果触发了新的page fault,就会出现这种中断重入。 74 | 2. 我们采用微内核架构,对缺页错误的处理由用户进程完成,处理结束后也由用户进程恢复原来缺页异常发生时的现场。因此需要将异常的现场Trapframe 复制到用户空间。 75 | 76 | ### Thinking 4.7 77 | 78 | *到这里我们大概知道了这是一个由用户程序处理并由用户程序自身来恢复运行现场的过程,请思考并回答以下几个问题:* 79 | 80 | - *用户处理相比于在内核处理写时复制的缺页中断有什么优势?* 81 | - *从通用寄存器的用途角度讨论用户空间下进行现场的恢复是如何做到不破坏通用寄存器的?* 82 | 83 | 1. 采用微内核架构,减小内核体积;减少关中断时间,提高中断处理效率 84 | 2. 首先利用lw命令依次还原除sp外所有寄存器,最后短暂利用k0跳转到中断发生前执行的指令,在跳转的延迟槽中恢复sp寄存器。 85 | 86 | *** 87 | 88 | ### Thinking 4.8 89 | 90 | *请思考并回答以下几个问题:* 91 | 92 | - *为什么需要将set_pgfault_handler 的调用放置在syscall_env_alloc 之前?* 93 | - *如果放置在写时复制保护机制完成之后会有怎样的效果?* 94 | - *子进程需不需要对在entry.S 定义的字__pgfault_handler 赋值?* 95 | 96 | 1. 如果放在syscall_env_alloc之后,子进程开始运行后会再次执行set_pgfault_handler(),为__pgfault_handler赋值,同时设置pgfault_handler和分配相应的空间。但由于我们在子进程开始运行前就完成了第二、三步的内容,这样就产生了不必要的的开销。 97 | 2. 执行set_pgfault_handler之前就会发生pgfault,此时还没有相应的handler,系统就会出现问题。 98 | 3. 不需要,et_pgfault_handler的调用在syscall_env_alloc之前,父进程设置过__pgfault_handler的值,因此子进程不需要再次设置。 99 | 100 | ## 二、实验难点图示 101 | 102 | 1. 系统调用 103 | 104 | ``` 105 | 用户调用库函数 -> 106 | syscall_* -> 107 | msyscall(sysno, 参数) -> 108 | 直接syscall产生异常,被分发到handler -> 109 | syscall.S把参数从引发异常的进程的Trapframe转移到内核栈空间 -> 110 | sys_*开始运行 -> 111 | syscall.S把返回值放到trapframe寄存器里 -> 112 | 用户返回得到结果 113 | ``` 114 | 115 | 2. PTE_COW页面实现机制 116 | 117 | ![COW](COW.png) 118 | 119 | ## 三、体会与感想 120 | 121 | 本次Lab 4 任务较为困难,内容量大,要求了解的程度也深。需要我们了解清晰的调用过程,了解MIPS的调用ABI,汇编语言与C语言的结合,以及COW页面的实现机理等等。总的来说,需要将Lab 2和Lab 3的内容完全理解的基础上才能进行(比如好多人就被Lab 2/Lab 3大鞭尸)。 122 | 123 | ## 四、【可选】指导书反馈 124 | 125 | - 增加汇编语言与C语言的结合、ABI相关的内容,特别是堆栈的组织相关(包括返回值,返回一个结构体的话怎么办?)。那些莫名其妙搜都搜不出来的汇编器指令最好解释一下。还有,GAS的语法和我们在Mars中学习的也稍有不同(跳转时可以用的数字标号等等)这些应该也提供一些资料。 126 | - 把对PTE_LIBRARY等各种位的解释提前,`写时复制机制`这一节里出了个思考题涉及PTE_LIBRARY,但是在`父子各自的旅途`这一节才第一次提到PTE_LIBRARY的意义。 127 | - 增加对MIPS R3000 TLB具体内容的解释,计算机组成课上学的是一般的TLB,具体到咱们操作系统中,EntryHi、EntryLo以及ASID相关的东西都没怎么讲(当然也不能排除我忘记了),这些东西总不能让我们猜吧。 128 | - 如果一个操作需要的权限限于一些(评测)原因必须以一种不符合真实系统要求的形式出现的话(没错我说的就是sys_mem_开头的某些函数),最好在注释里写一下吧。(把陈年老注释改一改吧 129 | - 秋梨膏思考题讲一讲吧,至少给一个反馈思考的是对是错啊,重要内容都在思考题自己思考可以理解,但理解错了也得纠正一下吧。 -------------------------------------------------------------------------------- /实验报告/Lab4 Report/Lab4 Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab4 Report/Lab4 Report.pdf -------------------------------------------------------------------------------- /实验报告/Lab5 Report/Lab5 Report.md: -------------------------------------------------------------------------------- 1 | # OS Lab-5 Report 2 | 3 | ## 一、实验思考题 4 | 5 | ### Thinking 5.1 6 | 7 | *查阅资料,了解 Linux/Unix 的 /proc 文件系统是什么?有什么作用? Windows 操作系统又是如何实现这些功能的?proc 文件系统这样的设计有什么好 处和可以改进的地方?* 8 | 9 | /proc 文件系统是一个虚拟文件系统,通过对这些虚拟文件的读写可以与内核中实体进行通信。具体能实现的功能包括读取系统数据、进程信息甚至修改系统参数等。Windows系统一般通过Windows API来实现类似的功能。这样的设计简化用户程序和内核空间的交互过程,更加方便快捷。 10 | 11 | *** 12 | 13 | ### Thinking 5.2 14 | 15 | *如果我们通过 kseg0 读写设备,我们对于设备的写入会缓存到 Cache 中。通过 kseg0 访问设备是一种错误的行为,在实际编写代码的时候这么做会引发不可预知的问题。请你思考:这么做这会引起什么问题?对于不同种类的设备(如 我们提到的串口设备和 IDE 磁盘)的操作会有差异吗?可以从缓存的性质和缓存刷新的策略来考虑。* 16 | 17 | 对于写入操作: 18 | 19 | 在采用Write-back刷新策略时,写入数据只有在cache被换出时才会进行写回,导致后面的操作覆盖了前面操作,只进行最后一次操作。对串口设备,只有Cache刷新后才能看到输出,且只能看到最后一个字符。类似的,IDE磁盘可能只会写入最后一个扇区。 20 | 21 | 但如果采用Write-through策略进行刷新,CPU向Cache写入数据时,也会向内存相同地址也写一份。这样就避免了上面所说的问题,可以正常工作。 22 | 23 | 如果是读取操作:问题更大,任何一种策略都可能会读取到旧的、过时的数据,因此产生错误。 24 | 25 | *** 26 | 27 | ### Thinking 5.3 28 | 29 | *一个磁盘块最多存储 1024 个指向其他磁盘块的指针,试计算,我们 的文件系统支持的单个文件的最大大小为多大?* 30 | 31 | 单个文件最多有1024个指针,单个文件最大大小为 1024 * 4 KB = 4096 KB = 4 MB 32 | 33 | *** 34 | 35 | ### Thinking 5.4 36 | 37 | *查找代码中的相关定义,试回答一个磁盘块中最多能存储多少个文件 控制块?一个目录下最多能有多少个文件?* 38 | 39 | 一个磁盘块最多存储16个文件控制块;单个文件最多有1024个指针,指向1024个磁盘块,所以一个目录下最多16384个文件。 40 | 41 | *** 42 | 43 | ### Thinking 5.5 44 | 45 | *请思考,在满足磁盘块缓存的设计的前提下,我们实验使用的内核支持的最大磁盘大小是多少?* 46 | 47 | 根据我们小操作系统的代码,我们磁盘最大的大小不能超过DISKMAX,0x40000000字节,也就是1GB。 48 | 49 | 但是,如果但从系统架构所决定的最大可支持的磁盘大小角度考虑,0x10000000之下要存储一页ipc用的buffer,所以必须从DISKMAP,0x10000000开始映射缓存的硬盘块。缓存的硬盘块是在serv.c这个用户内存空间里的,而serv.c进程会从FILEVA,0x60000000开始,为Open结构分配空间。 50 | 51 | 一个正常的用户进程中,FDTABLE,也就是(FILEBASE-PDMAP)的位置是放置fd的。但是考虑到我们serv.c本身就是文件系统服务,不会也无法使用用户态提供的fd系列操作,且我们的Open结构的空间也覆盖了fd的Data区域,所以其实我们不需要考虑这个问题。 52 | 53 | 根据上面的结论,可以得出,serv.c可以被用来缓存磁盘块的大小的空间是0x10000000-0x60000000共0x50000000,1.25GB空间 54 | 55 | (当然如果考虑到我们的物理内存大小只有64MB的话(就算采用内存置换也因为磁盘是固定块缓存的,等于你存进去还是在内存里约等于没存)咱们这个文件系统能支持的大小就只有空闲内存容量了吧) 56 | 57 | *** 58 | 59 | ### Thinking 5.6 60 | 61 | *如果将 DISKMAX 改成 0xC0000000, 超过用户空间,我们的文件系统还能正常工作吗?为什么?* 62 | 63 | 不能,根据上面的分析,在大于0x50000000之后,就会覆盖掉Open结构进而可能出现潜在问题,达到0xC0000000, 超过用户空间之后更是会试图访问内核数据,会引发异常并panic 64 | 65 | *** 66 | 67 | ### Thinking 5.7 68 | 69 | *阅读 user/file.c ,你会发现很多函数中都会将一个 struct Fd * 型的 指针转换为 struct Filefd * 型的指针,请解释为什么这样的转换可行。* 70 | 71 | user/file.c里的struct Fd *指针都是open之后的,而open的过程中调用了fsipc_open函数,并将一个struct Fd型指针的值发送给serv。serv会用ipc将fd指针的所在页映射上一个struct Filefd。而Filefd的第一个元素就是一个Fd,因此转换之后不会出现问题。 72 | 73 | *** 74 | 75 | ### Thinking 5.8 76 | 77 | *请解释 Fd, Filefd, Open 结构体及其各个域的作用。比如各个结构体 会在哪些过程中被使用,是否对应磁盘上的物理实体还是单纯的内存数据等。说明 形式自定,要求简洁明了,可大致勾勒出文件系统数据结构与物理实体的对应关系 与设计框架。* 78 | 79 | 1. struct Fd定义在user/fd.h,是一个文件描述符结构,是库函数保存用户进程已打开文件使用的。 80 | - fd_dev_id:打开文件的id,也就是该文件描述符对应的抽象文件的实际类型 81 | - fd_offset:当前读/写的偏移值,也就是下一次操作从文件的哪个地方开始 82 | - fd_omode:当前文件打开的模式,只读/只写/读写等,可在判定操作是否合法时用。 83 | 2. struct Filefd定义在user/fd.h,是文件描述符+文件id+文件控制块的结构 84 | - f_fd:一个文件描述符。 85 | - f_fileid:对应于一个全局的文件编号,用来向文件系统请求服务。 86 | - f_file:对应文件的文件控制块。 87 | 3. struct Open定义在fs/serv.c,是文件系统服务用来保存整个系统的已打开文件的结构。 88 | - o_file:真实的,指向对应文件在硬盘块缓存上文件控制块的地址,用来对文件进行属性进行更改。 89 | - o_fileid:全局唯一的文件编号,和struct Filefd里的f_fileid对应。 90 | - o_mode:文件打开的模式,和struct Fd的fd_omode对应 91 | 92 | *** 93 | 94 | ## 二、实验难点图示 95 | 96 | 文件系统调用过程 97 | 98 | ![fs调用](fs调用.png) 99 | 100 | 注意open函数比较特殊,直接由用户调用位于file.c中的内容而不需要经过fd.c的抽象 101 | 102 | ## 三、体会与感想 103 | 104 | Lab-5的难度明显比之前有所增加,整个函数的调用关系非常复杂,要填写的代码内容虽然不多,但是要读的代码却很多。 105 | 106 | Lab-5代码的理解就是把握住文件系统里的层层抽象,ide.c将将磁盘块的操作抽象出来;serv.c将磁盘抽象为一个目录树,利用ipc提供文件系统服务;file.c利用fsipc.c封装对于文件的种种操作;fd.c更进一层提供了对所有“虚拟文件类型”的访问操作。 107 | 108 | 只有深入的理解代码,才能真正学好OS实验,Debug才能如鱼得水(虽说按理来说真正读懂应该就不会有啥大Bug来着)。 -------------------------------------------------------------------------------- /实验报告/Lab5 Report/Lab5 Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab5 Report/Lab5 Report.pdf -------------------------------------------------------------------------------- /实验报告/Lab5 Report/fs调用.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab5 Report/fs调用.png -------------------------------------------------------------------------------- /实验报告/Lab6 Report/Lab6 Report.md: -------------------------------------------------------------------------------- 1 | # OS Lab-6 Report 2 | 3 | ## 一、实验思考题 4 | 5 | ### Thinking 6.1 6 | 7 | *示例代码中,父进程操作管道的写端,子进程操作管道的读端。如果现在想让父进程作为“读者”,代码应当如何修改?* 8 | 9 | 将`switch`中`case 0:`和`default:`部分的语句块互换 10 | 11 | *** 12 | 13 | ### Thinking 6.2 14 | 15 | *上面这种不同步修改 pp_ref 而导致的进程竞争问题在 user/fd.c 中的dup 函数中也存在。请结合代码模仿上述情景,分析一下我们的 dup 函数中为什么会出现预想之外的情况?* 16 | 17 | 假设父子进程,有一对管道`p[2]`,其中父进程关闭`p[0]`完毕,准备测试`ispipeclosed(p[1])`。子进程`dup(p[1])`刚dup完毕fd,还没开始dup Pipe结构体。 18 | 19 | 此时`p[1]`引用数为3,`p[0]`引用数为1,Pipe结构体所在页因为被map到父进程的`p[0]`和子进程的`p[0]`、`p[1]`的fdData处,引用数也为3,此时pageref(wfd) = pageref(pipe)父进程的`ispipeclosed(p[1])`就会被误判为true。 20 | 21 | *** 22 | 23 | ### Thinking 6.3 24 | 25 | *阅读上述材料并思考:为什么系统调用一定是原子操作呢?如果你觉得不是所有的系统调用都是原子操作,请给出反例。希望能结合相关代码进行分析。* 26 | 27 | 所有的系统调用都是原子操作。用户进程执行syscall后到操作系统完成操作返回的过程中,不会有其他程序执行。系统调用开始时,操作系统就会关闭中断(syscall.S中的CLI指令)。因此系统调用不会被打断。对于sys_ipc_recv,应理解为设置进程进入recv状态,这个设置过程不会被打断,因而也是原子操作。 28 | 29 | *** 30 | 31 | ### Thinking 6.4 32 | 33 | *仔细阅读上面这段话,并思考下列问题* 34 | 35 | - *按照上述说法控制 pipeclose 中 fd 和 pipe unmap 的顺序,是否可以解决上述场景的进程竞争问题?给出你的分析过程。* 36 | 37 | - *我们只分析了 close 时的情形,那么对于 dup 中出现的情况又该如何解决?请模仿上述材料写写你的理解。* 38 | 39 | 可以,因为原情况出现的原因是:a, b二值, a > b当先减少a再减少b时,就可能会出现a == b的中间态。改变顺序后b先减少 a > b > b\* 不会出现这种状态。 40 | 41 | dup是类似的,只不过情况变成了先增加b再增加a,改变顺序之后先增加a再增加b,也就不会有这种情况发生。 42 | 43 | *** 44 | 45 | ### Thinking 6.5 46 | 47 | *bss 在 ELF 中并不占空间,但 ELF 加载进内存后,bss 段的数据占据了空间,并且初始值都是 0。请回答你设计的函数是如何实现上面这点的?* 48 | 49 | Load二进制文件时,根据bss段数据的memsz属性分配对应的内存空间并清零。 50 | 51 | *** 52 | 53 | ### Thinking 6.6 54 | 55 | *为什么我们的 *.b 的 text 段偏移值都是一样的,为固定值?* 56 | 57 | user.lds中有如下内容,规定了.text段在链接中第一个被链接,因此开始位置相同。 58 | 59 | ```c 60 | . = 0x00400000; 61 | 62 | _text = .; /* Text and read-only data */ 63 | .text : { 64 | *(.text) 65 | *(.fixup) 66 | *(.gnu.warning) 67 | } 68 | ``` 69 | 70 | *** 71 | 72 | ### Thinking 6.7 73 | 74 | *在哪步,0 和 1 被” 安排” 为标准输入和标准输出?请分析代码执行流程,给出答案。* 75 | 76 | user/init.c中。 77 | 78 | ```c 79 | if ((r = opencons()) < 0) 80 | user_panic("opencons: %e", r); 81 | if (r != 0) 82 | user_panic("first opencons used fd %d", r); 83 | if ((r = dup(0, 1)) < 0) 84 | user_panic("dup: %d", r); 85 | ``` 86 | 87 | ## 二、实验难点图示 88 | 89 | - spawn过程 90 | 91 | ![spawn](spawn.png) 92 | 93 | 这次我的spawn采用系统调用来加载程序,有效复用了Lab-3的代码. 94 | 95 | ## 三、体会与感想 96 | 97 | 本次实验难度一般,难点在于pipe在多进程并发环境下可能出现的一些问题和spawn函数的设计,其中后者我利用系统调用巧妙地避开了。 98 | 99 | 实验内容方面,真正测试shell才发现我们的小系统还是有太多这样那样的不完善之处,但是这样能让一个小系统跑起来,也是一个很大的收获。 100 | 101 | ## 四、【可选】指导书反馈 102 | 103 | lab6-extra的测试真的是,绝了。大多数测试点的不过都是因为调度或者fork导致的时序问题,在PV操作的语义上都是正确的。这点我觉得不太合适。测试程序应当利用信号量(Barrier)以及check_val函数将多进程并发的不确定性缩小在很少的几条语句上,然后再进行大量循环来尽可能使另一个程序先执行。而事实上fork的时间长短都会影响测试的结果,说明测试只是用一定的循环(循环量还不是很大)来得到一个“大概率(并不大,看看多少11/13)正确”的东西,这样对实验很不友好。(交了30多次最后发现是时间跟评测机对不上啊喂) -------------------------------------------------------------------------------- /实验报告/Lab6 Report/Lab6 Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab6 Report/Lab6 Report.pdf -------------------------------------------------------------------------------- /实验报告/Lab6 Report/spawn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/实验报告/Lab6 Report/spawn.png -------------------------------------------------------------------------------- /有关环境/README.md: -------------------------------------------------------------------------------- 1 | # 环境说明 2 | 3 | - mips-2007-01-21.iso 下载链接:https://ftp.denx.de/pub/eldk/4.1/mips-linux-x86/iso/ 4 | - Gxemul 0.46版本,直接在仓库中可用 5 | - lib32-sysroot.tar.gz是课程中Lab5编译时要用到的依赖 6 | 7 | -------------------------------------------------------------------------------- /有关环境/gxemul-0.4.6.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/有关环境/gxemul-0.4.6.tar.gz -------------------------------------------------------------------------------- /有关环境/lab5-compile_env.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/有关环境/lab5-compile_env.pdf -------------------------------------------------------------------------------- /有关环境/lib32-sysroot.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOIDMalkuth/BUAA_OS_2019_Code/88792b4ae71ca0fc339f821bfad7b416edeafc60/有关环境/lib32-sysroot.tar.gz --------------------------------------------------------------------------------