2021年链接 https://pdos.csail.mit.edu/6.S081/2021/schedule.html
LEC 1
fork后子进程将复制父进程的数据段、BSS段、代码段、堆空间、栈空间和文件描述符。
使用exec执行新的程序时,仍然可以使用原来的文件。
xv6 使用第一种方法。也就是子shell先关闭某个fd再打开,新fd取低值。
所以分开fork和exec的好处之一是可以重定向子进程(子shell的)IO然后再exec而不会扰乱主进程的文件描述符表。
fork以后的同一个文件描述符或者dup系统调用复制出来的文件描述符,他们对应的深层(underlying)IO对象都是一个,共享offset。
shell中有种常见的重定向2>&1
,就是说fd 2(stderr)是fd 1(stdout)的副本,也就是错误和正常输出都到一块。
一个值得研读的重定向+pipe的例子:
关于xv6:ctrl+p
打印进程信息,ctrl+a x
退出qemu。
Lab Util
实现user/sleep.c,user/pingpong.c,user/primes.c,user/find.c,user/xargs.c,主要是完成这些工具。
LEC 3
操作系统三大需求:多路复用、隔离、交互。
宏内核monolithic kernel
RISC-V有三种模式,机器模式machine mode,监督模式supervisor mode,用户模式user mode
机器起于m mode,执行几条指令以后进入s mode。在s mode里面,cpu可以执行特权指令。如果u mode 的程序企图执行特权指令,那么不会执行,而是转为s mode并且停止程序。
u mode的程序运行于用户空间user space,s mode的程序运行于内核空间 kernel space,也称为内核kernel。
XV6为每个进程都维护了一个页表
每个进程都有用户栈和内核栈。执行用户指令时只用user stack,内核栈空。
系统调用使用ecall
指令。
Lab Syscall
xv6进行系统调用
比方说我们在user/xxx.c里头写用户程序,调用了read()系统调用。这些系统调用的声明都在user/user.h中。
usys.pl是一个perl脚本,生成到usys.S里头形如
1 | .global read |
回想一下汇编语言,参数存在寄存器a0/a1……里头,a7加载上kernel/syscall.h里头定义好的标识数,ecall触发中断。
中断描述符到中断处理函数这里还没讲,反正就到了kernel/trap.c usertrap(),调用了kernel/syscall.c syscall(),处理了系统调用。
根据标识数和一个预先定义好的函数指针数组,来到了对应的系统调用处理函数kernel/sysfile.c sys_read()
这个函数没有入参,都是自己从寄存器里面捞出来的。它里面可能调用kernel/里头的其他函数。user/xxx.c并不能调用这些函数。
如果要写什么东西到用户内存,要使用copyout()。返回值的话,sys_read()的返回值存到a0里头(等同于eax)。
LEC 4
学到这里的时候和华科的os课设混着做了,感觉知识++
硬件三级页表:
虚拟空间$2^39$,物理空间$2^56$
快表TLB可以解决三级页表访存次数多的问题,但是记得切换进程的时候要清空tlb。
PTE(page table entry)有一些标记,PTE_V for valid, PTE_R/W之类的。
如果查的时候发现有不在的PTE,就发起缺页中断。
每个进程(CPU)都有自己的satp寄存器,存放根页表的地址。他们各自有各自的页表。并且,内核自己也有一个页表。
内核的虚拟空间
内核空间映射:
QEMU模拟的ram从kernbase到phystop。内核虚拟空间里,ram和底下的设备都是直接映射。
也有不是直接映射的:trampoline page(在虚拟空间的最高处,和用户页表一样,不过我不知道这是啥),以及内核栈页。每个进程都有一个内核栈。这些内核栈页直接都有guard page。
用户虚拟空间
代码片段
一个PTE是一个uint64,那么一个pagetable_t就是一个uint64的指针,,,
walk是走一遍三级页表,返回va对应的pte。期间可以指定是否分配不存在的页面。
空闲物理内存管理就是维护一个空闲页链表。一个页的下一个空闲页存放在这个页的最开头,用强制类型转换做。
LEC 5
trap有三种可能:
- ecall
- exception,例如除零
- interrupt,例如io完毕