MIT6.S081
  • 简介
  • Lec01 Introduction and Examples (Robert)
    • 1.1 课程内容简介
    • 1.2 操作系统结构
    • 1.3 Why Hard and Interesting
    • 1.4 课程结构和资源
    • 1.5 read, write, exit系统调用
    • 1.6 open系统调用
    • 1.7 Shell
    • 1.8 fork系统调用
    • 1.9 exec, wait系统调用
    • 1.10 I/O Redirect
  • Lec03 OS Organization and System Calls (Frans)
    • 3.1 上一节课回顾
    • 3.2 操作系统隔离性(isolation)
    • 3.3 操作系统防御性(Defensive)
    • 3.4 硬件对于强隔离的支持
    • 3.5 User/Kernel mode切换
    • 3.6 宏内核 vs 微内核 (Monolithic Kernel vs Micro Kernel)
    • 3.7 编译运行kernel
    • 3.8 QEMU
    • 3.9 XV6 启动过程
  • Lec04 Page tables (Frans)
    • 4.1 课程内容简介
    • 4.2 地址空间(Address Spaces)
    • 4.3 页表(Page Table)
    • 4.4 页表缓存(Translation Lookaside Buffer)
    • 4.5 Kernel Page Table
    • 4.6 kvminit 函数
    • 4.7 kvminithart 函数
    • 4.8 walk 函数
  • Lec05 Calling conventions and stack frames RISC-V (TA)
    • 5.1 C程序到汇编程序的转换
    • 5.2 RISC-V vs x86
    • 5.3 gdb和汇编代码执行
    • 5.4 RISC-V寄存器
    • 5.5 Stack
    • 5.6 Struct
  • Lec06 Isolation & system call entry/exit (Robert)
    • 6.1 Trap机制
    • 6.2 Trap代码执行流程
    • 6.3 ECALL指令之前的状态
    • 6.4 ECALL指令之后的状态
    • 6.5 uservec函数
    • 6.6 usertrap函数
    • 6.7 usertrapret函数
    • 6.8 userret函数
  • Lec08 Page faults (Frans)
    • 8.1 Page Fault Basics
    • 8.2 Lazy page allocation
    • 8.3 Zero Fill On Demand
    • 8.4 Copy On Write Fork
    • 8.5 Demand Paging
    • 8.6 Memory Mapped Files
  • Lec09 Interrupts (Frans)
    • 9.1 真实操作系统内存使用情况
    • 9.2 Interrupt硬件部分
    • 9.3 设备驱动概述
    • 9.4 在XV6中设置中断
    • 9.5 UART驱动的top部分
    • 9.6 UART驱动的bottom部分
    • 9.7 Interrupt相关的并发
    • 9.8 UART读取键盘输入
    • 9.9 Interrupt的演进
  • Lec10 Multiprocessors and locking (Frans)
    • 10.1 为什么要使用锁?
    • 10.2 锁如何避免race condition?
    • 10.3 什么时候使用锁?
    • 10.4 锁的特性和死锁
    • 10.5 锁与性能
    • 10.6 XV6中UART模块对于锁的使用
    • 10.7 自旋锁(Spin lock)的实现(一)
    • 10.8 自旋锁(Spin lock)的实现(二)
  • Lec11 Thread switching (Robert)
    • 11.1 线程(Thread)概述
    • 11.2 XV6线程调度
    • 11.3 XV6线程切换(一)
    • 11.4 XV6线程切换(二)
    • 11.5 XV6进程切换示例程序
    • 11.6 XV6线程切换 --- yield/sched函数
    • 11.7 XV6线程切换 --- switch函数
    • 11.8 XV6线程切换 --- scheduler函数
    • 11.9 XV6线程第一次调用switch函数
  • Lec13 Sleep & Wake up (Robert)
    • 13.1 线程切换过程中锁的限制
    • 13.2 Sleep&Wakeup 接口
    • 13.3 Lost wakeup
    • 13.4 如何避免Lost wakeup
    • 13.5 Pipe中的sleep和wakeup
    • 13.6 exit系统调用
    • 13.7 wait系统调用
    • 13.8 kill系统调用
  • Lec14 File systems (Frans)
    • 14.1 Why Interesting
    • 14.2 File system实现概述
    • 14.3 How file system uses disk
    • 14.4 inode
    • 14.5 File system工作示例
    • 14.6 XV6创建inode代码展示
    • 14.7 Sleep Lock
  • Lec15 Crash recovery (Frans)
    • 15.1 File system crash概述
    • 15.2 File system crash示例
    • 15.3 File system logging
    • 15.4 log_write函数
    • 15.5 end_op函数
    • 15.6 File system recovering
    • 15.7 Log写磁盘流程
    • 15.8 File system challenges
  • Lec16 File system performance and fast crash recovery (Robert)
    • 16.1 Why logging
    • 16.2 XV6 File system logging回顾
    • 16.3 ext3 file system log format
    • 16.4 ext3如何提升性能
    • 16.5 ext3文件系统调用格式
    • 16.6 ext3 transaction commit步骤
    • 16.7 ext3 file system恢复过程
    • 16.8 为什么新transaction需要等前一个transaction中系统调用执行完成
    • 16.9 总结
  • Lec17 Virtual memory for applications (Frans)
    • 17.1 应用程序使用虚拟内存所需要的特性
    • 17.2 支持应用程序使用虚拟内存的系统调用
    • 17.3 虚拟内存系统如何支持用户应用程序
    • 17.4 构建大的缓存表
    • 17.5 Baker's Real-Time Copying Garbage Collector
    • 17.6 使用虚拟内存特性的GC
    • 17.7 使用虚拟内存特性的GC代码展示
  • Lec18 OS organization (Robert)
    • 18.1 Monolithic kernel
    • 18.2 Micro kernel
    • 18.3 Why micro kernel?
    • 18.4 L4 micro kernel
    • 18.5 Improving IPC by Kernel Design
    • 18.6 Run Linux on top of L4 micro kernel
    • 18.7 L4 Linux性能分析
  • Lec19 Virtual Machines (Robert)
    • 19.1 Why Virtual Machine?
    • 19.2 Trap-and-Emulate --- Trap
    • 19.3 Trap-and-Emulate --- Emulate
    • 19.4 Trap-and-Emulate --- Page Table
    • 19.5 Trap-and-Emulate --- Devices
    • 19.6 硬件对虚拟机的支持
    • 19.7 Dune: Safe User-level Access to Privileged CPU Features
  • Lec20 Kernels and HLL (Frans)
    • 20.1 C语言实现操作系统的优劣势
    • 20.2 高级编程语言实现操作系统的优劣势
    • 20.3 高级编程语言选择 --- Golang
    • 20.4 Biscuit
    • 20.5 Heap exhaustion
    • 20.6 Heap exhaustion solution
    • 20.7 Evaluation: HLL benefits
    • 20.8 Evaluation: HLL performance cost(1)
    • 20.9 Evaluation: HLL performance cost(2)
    • 20.10 Should one use HLL for a new kernel?
  • Lec21 Networking (Robert)
    • 21.1计算机网络概述
    • 21.2 二层网络 --- Ethernet
    • 21.3 二/三层地址转换 --- ARP
    • 21.4 三层网络 --- Internet
    • 21.5 四层网络 --- UDP
    • 21.6 网络协议栈(Network Stack)
    • 21.7 Ring Buffer
    • 21.8 Receive Livelock
    • 21.9 如何解决Livelock
  • Lec22 Meltdown (Robert)
    • 22.1 Meltdown发生的背景
    • 22.2 Speculative execution(1)
    • 22.3 Speculative execution(2)
    • 22.4 CPU caches
    • 22.5 Flush and Reload
    • 22.6 Meltdown Attack
    • 22.7 Meltdown Fix
  • Lec23 RCU (Robert)
    • 23.1 使用锁带来的问题
    • 23.2 读写锁 (Read-Write Lock)
    • 23.3 RCU实现(1) - 基本实现
    • 23.4 RCU实现(2) - Memory barrier
    • 23.5 RCU实现(3) - 读写规则
    • 23.6 RCU用例代码
    • 23.7 RCU总结
Powered by GitBook
On this page

Was this helpful?

  1. Lec19 Virtual Machines (Robert)

19.3 Trap-and-Emulate --- Emulate

Previous19.2 Trap-and-Emulate --- TrapNext19.4 Trap-and-Emulate --- Page Table

Last updated 4 years ago

Was this helpful?

VMM会为每一个Guest维护一套虚拟状态信息。所以VMM里面会维护虚拟的STVEC寄存器,虚拟的SEPC寄存器以及其他所有的privileged寄存器。当Guest操作系统运行指令需要读取某个privileged寄存器时,首先会通过trap走到VMM,因为在用户空间读取privileged寄存器是非法的。之后VMM会检查这条指令并发现这是一个比如说读取SEPC寄存器的指令,之后VMM会模拟这条指令,并将自己维护的虚拟SEPC寄存器,拷贝到trapframe的用户寄存器中(注,有关trapframe详见Lec06,这里假设Guest操作系统通过类似“sread a0, sepc”的指令想要将spec读取到用户寄存器a0)。之后,VMM会将trapframe中保存的用户寄存器拷贝回真正的用户寄存器,通过sret指令,使得Guest从trap中返回。这时,用户寄存器a0里面保存的就是SEPC寄存器的值了,之后Guest操作系统会继续执行指令。最终,Guest读到了VMM替自己保管的虚拟SEPC寄存器。

学生提问:VMM是怎么区分不同的Guest?

Robert教授:VMM会为每个Guest保存一份虚拟状态信息,然后它就像XV6知道是哪个进程一样,VMM也知道是哪个Guest通过trap走到VMM的。XV6有一个针对每个CPU的变量表明当前运行的是哪个进程,类似的VMM也有一个针对每个CPU的变量表明当前是哪个虚拟机在运行,进而查看对应的虚拟状态信息。

学生提问:VMM可以给一个Guest分配多个CPU核吗?

Robert教授:稍微复杂点的VMM都可以实现。

学生提问:在实际的硬件中会有对应寄存器,那么为什么我们不直接使用硬件中的寄存器,而是使用虚拟的寄存器?

Robert教授:这里的原因是,VMM需要使用真实的寄存器。举个例子,想象一下SCAUSE寄存器,当Guest操作系统尝试做任何privileged操作时(注,也就是读写privileged寄存器),会发生trap。硬件会将硬件中真实的SCAUSE寄存器设置成引起trap的原因,这里的原因是因为权限不够。但是假设Guest操作系统只是从Guest用户进程执行了一个系统调用,Guest操作系统需要看到SCAUSE的值是系统调用。也就是说Guest操作系统在自己的trap handler中处理来自Guest用户进程的系统调用时,需要SCAUSE的值表明是系统调用。

而实际的SCAUSE寄存器的值却表明是因为指令违反了privilege规则才走到的trap。通常情况下,VMM需要看到真实寄存器的值,而Guest操作系统需要能看到符合自己视角的寄存器的值。(注,在Guest操作系统中,可能有两种情况会触发trap,一种是Guest用户空间进程的系统调用,也就是正常操作系统中正常的trap流程,另一种是Guest内核空间读取privileged寄存器时,因为Guest内核空间实际上也是在宿主机的用户空间,导致这是个非法操作并触发trap。Robert这边举的例子的流程应该是这样,Guest用户进程执行系统调用,在这一个瞬间SCAUSE寄存器的值是ECALL,也就是8,详见6.6。但是稍后在Guest系统内核的trap handler中需要读取SCAUSE的值,以确定在Guest中引起trap的原因,但是这就触发了第二种trap,SCAUSE的值会变成Illegal Access。我们不能让Guest系统内核看到这个值,所以VMM这里将它变成ECALL并返回。)

在这种虚拟机的实现中,Guest整个运行在用户空间,任何时候它想要执行需要privilege权限的指令时,会通过trap走到VMM,VMM可以模拟这些指令。这种实现风格叫做Trap and Emulate。你可以完全通过软件实现这种VMM,也就是说你可以只通过修改软件就将XV6变成一个可以运行在RISC-V上的VMM,然后再在之上运行XV6虚拟机。当然,与常规的XV6一样,VMM需要运行在Supervisor mode。

所有以S开头的寄存器,也就是所有的Supervisor控制寄存器都必须保存在虚拟状态信息中。同时还有一些信息并不能直接通过这些控制寄存器体现,但是又必须保存在这个虚拟状态信息中。其中一个信息就是mode。VMM需要知道虚拟机是运行在Guest user mode还是Guest Supervisor mode。例如,Guest中的用户代码尝试执行privileged指令,比如读取SCAUSE寄存器,这也会导致trap并走到VMM。但是这种情况下VMM不应该模拟指令并返回,因为这并不是一个User mode中的合法指令。所以VMM需要跟踪Guest当前是运行在User mode还是Supervisor mode,所以在虚拟状态信息里面也会保存mode。

VMM怎么知道Guest当前的mode呢?当Guest从Supervisor mode返回到User mode时会执行sret指令,而sret指令又是一个privileged指令,所以会通过trap走到VMM,进而VMM可以看到Guest正在执行sret指令,并将自己维护的mode从Supervisor变到User。

虚拟状态信息中保存的另外一个信息是hartid,它代表了CPU核的编号。即使通过privileged指令,也不能直接获取这个信息,VMM需要跟踪当前模拟的是哪个CPU。

实际中,在不同类型的CPU上实现Trap and Emulate虚拟机会有不同的难度。不过RISC-V特别适合实现Trap and Emulate虚拟机,因为RISC-V的设计人员在设计指令集的时候就考虑了Trap and Emulate虚拟机的需求。举个例子,设计人员确保了每个在Supervisor mode下才能执行的privileged指令,如果在User mode执行都会触发trap。你可以通过这种机制来确保VMM针对Guest中的每个privileged指令,都能看到一个trap。

学生提问:Guest操作系统内核中会实际运行任何东西吗?还是说它总是会通过trap走到VMM?

Robert教授:如果你只是执行一个ADD指令,这条指令会直接在硬件上以硬件速度执行。如果你执行一个普通的函数调用,代码的执行也没有任何特殊的地方。所有User代码中合法的指令,以及内核代码中的non-priviledged指令,都是直接以全速在硬件上执行。

学生提问:在Guest操作系统中是不是也有类似的User mode和Kernel mode?

Robert教授:有的。Guest操作系统就是一个未被修改的普通操作系统,所以我们在Guest中运行的就是Linux内核或者XV6内核。而XV6内核知道自己运行在Supervisor mode,从代码的角度来说,内核代码会认为自己运行在Supervisor mode,并执行各种privileged指令,并期望这些指令能工作。当Guest操作系统执行sret指令时,它也知道自己将要进入到User空间。不过在宿主机上,Guest操作系统是运行在User mode,VMM也确保了这里能正常工作。但是从Guest角度来说,自己的内核看起来像是运行在Supervisor mode,自己的用户程序看起来像是运行在User mode。

所以,当Guest执行sret指令从Supervisor mode进入到User mode,因为sret是privileged指令,会通过trap进入到VMM。VMM会更新虚拟状态信息中的mode为User mode,尽管当前的真实mode还是Supervisor mode,因为我们还在执行VMM中的代码。在VMM从trap中返回之前,VMM会将真实的SEPC寄存器设置成自己保存在虚拟状态信息中的虚拟SEPC寄存器。因为当VMM使用自己的sret指令返回到Guest时,它需要将真实的程序计数器设置成Guest操作系统想要的程序计数器值(注,因为稍后Guest代码会在硬件上执行,因此依赖硬件上的程序计数器)。所以在一个非常短的时间内,真实的SEPC寄存器与虚拟的SEPC寄存器值是一样的。同时,当VMM返回到虚拟机时,还需要切换Page table,这个我们稍后会介绍。

Guest中的用户代码,如果是普通的指令,就直接在硬件上执行。当Guest中的用户代码需要执行系统调用时,会通过执行ECALL指令(注,详见6.3,6.4)触发trap,而这个trap会走到VMM中(注,因为ECALL也是个privileged指令)。VMM可以发现当前在虚拟状态信息中记录的mode是User mode,并且发现当前执行的指令是ECALL,之后VMM会更新虚拟状态信息以模拟一个真实的系统调用的trap状态。比如说,它将设置虚拟的SEPC为ECALL指令所在的程序地址(注,执行sret指令时,会将程序计数器的值设置为SEPC寄存器的值。这样,当Guest执行sret指令时,可以从虚拟的SEPC中读到正确的值);将虚拟的mode更新成Supervisor;将虚拟的SCAUSE设置为系统调用;将真实的SEPC设置成虚拟的STVEC寄存器(注,STVEC保存的是trap函数的地址,将真实的SEPC设置成STVEC这样当VMM执行sret指令返回到Guest时,可以返回到Guest的trap handler。Guest执行系统调用以为自己通过trap走到了Guest内核,但是实际上却走到了VMM,这时VMM需要做一些处理,让Guest以及之后Guest的所有privileged指令都看起来好像是Guest真的走到了Guest内核);之后调用sret指令跳转到Guest操作系统的trap handler,也就是STVEC指向的地址。