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. Lec18 OS organization (Robert)

18.6 Run Linux on top of L4 micro kernel

Previous18.5 Improving IPC by Kernel DesignNext18.7 L4 Linux性能分析

Last updated 4 years ago

Was this helpful?

前一节对于IPC的优化使得人们开始认真考虑使用微内核替代monolithic kernel。然而,这里仍然有个问题,即使IPC很快了,操作系统的剩余部分从哪里去获取?现在的微内核大概只有一个完整操作系统的百分之几,我们该怎么处理操作系统剩下的部分?这个问题通常会在一些有着相对较少资源的学校研究项目中被问到,我们需要从某个地方获取到所有这些用户空间服务。

实际上在一些特殊的应用场合,以上的问题并不是问题,比如说我们运行的一些设备的控制器,例如车里的点火控制器,只运行了几千行代码,它并且不需要一个文件系统,这样我们就只需要很少的用户空间内容,微内核也特别适合这种应用程序。但是微内核项目发起时,人们非常有雄心壮志,人们想的是完全替换操作系统,人们希望可以构建一些运行在工作站,服务器等各种地方的微内核操作系统,并取代大的monolithic kernel。对于这种场景,你需要一个传统操作系统所需要的所有内容。

一种可能是,重新以微内核的方式,以大量的进程实现所有的内容。实际上有项目在这么做,但是这涉及到大量的工作。具体的说,比如我想要使用笔记本电脑,我的电脑必须要有emacs和我最喜欢的C编译器,否则我肯定不会用你的操作系统。这意味着,微内核要想获得使用,它必须支持现有的应用程序,它必须兼容或者提供相同的系统调用或者更高层的服务接口,它必须能够完全兼容一些现有的操作系统,例如Unix,Linux,这样人们才愿意切换到微内核。所以这些微内核项目都面临一个具体的问题,它们怎么兼容一些为Linux,Windows写的应用程序?对于论文中提到的项目,也就是L4,对标的是Linux。与其写一些完全属于自己的新的用户空间服务,并模仿Linux,论文中决定采用一种容易的多的方法,其实许多项目也都采用了这种方法,也就是简单的将一个现有的monolithic kernel运行在微内核之上,而不是重新实现一些新的东西。这就是今天论文要介绍的内容。

在今天的讨论中,L4微内核位于底部,但是同时,一个完整的Linux作为一个巨大的服务运行在用户空间进程中。听起来有点奇怪,一般的kernel都是运行在硬件之上,而现在Linux kernel是一个用户空间进程。

实际上,如你在QEMU上运行XV6时所见,内核也是运行在用户空间。Linux kernel不过就是一个程序,对其做一些修改它就可以运行在用户空间,所以现在Linux需要被修改。论文中提到需要对Linux的底层做一些修改,例如Linux中期望能直接修改Page Table的内容,读写CPU寄存器。Linux中一部分需要被修改以将它们改成调用L4微内核的系统调用,或者发送IPC,而不是直接访问硬件。但是Linux的大部分内容都可以不做修改而直接运行。所以按照这种方式,作为Linux的一部分,现在得到了文件系统,网络支持,各种设备驱动等等,而不需要自己实现这些。

这里的实现方式是将Linux内核作为一个L4 Task运行,每一个Linux进程又作为一个独立的L4 Task运行。所以当你登录到Linux中时,你要它运行一个Shell或者terminal,它会在用户空间创建一个L4 Task来运行这个Linux程序。所以现在有一个Task运行Linux,以及N个Task来运行每一个你在Linux中启动的进程。

Linux不会直接修改进程的Page Table,而是会向L4发送正确的IPC让L4来修改进程的Page Table。

这里有很多小的改动,其中一个有意思的地方是,当VI想要执行一个系统调用时,VI并不知道它运行在L4之上,在上面的方案中,所有的程序都以为它们运行在Linux中。当VI要执行系统调用时,L4并不支持,因为VI要执行的是Linux系统调用而不是L4系统调用。所以对于Linux进程,会有一个小的库与之关联,这个库会将类似于fork,exec,pipe,read,write的系统调用,转换成发送到Linux kernel Task的IPC消息,并等待Linux kernel Task的返回,然后再返回到进程中。从VI的角度看起来好像就是从系统调用返回了。所以这些小的库会将系统调用转成发送到Linux kernel Task的IPC消息。这意味着,如果Linux kernel Task没有做其他事情的话,它会在一个recv系统调用中等待接收从任何一个进程发来的下一个系统调用请求IPC。

这导致了这里的Linux和普通的Linux明显不同的工作方式。在普通的Linux中,就像XV6一样,会有一个内核线程对应每一个用户空间进程。当用户空间进程调用系统调用时,内核会为这个系统调用运行一个内核线程。并且,在普通的Linux中,如果内核在内核线程之间切换,这基本上意味着从一个用户进程切换到另一个用户进程。所以这里Linux kernel的内核线程以及当Linux完成工作之后要运行的用户进程之间有一对一的关系。

在这里架构中,这种一对一的关系断了,这里的Linux kernel运行在一个L4线程中。然而,就像XV6一样,这个线程会使用与XV6中的context switching非常相似的技术,在与每个用户进程对应的内核线程之间切换。不过这些内核线程完全是在Linux中实现的,与L4线程毫无关系,唯一的L4线程就是运行了Linux kernel的控制线程。

但是哪个用户进程可以运行,是由L4决定的。所以在这里的设置中,Linux kernel或许在内核线程中执行来自VI的系统调用,同时,L4又使得Shell在用户空间运行了。这在XV6或者Linux极不可能发生,在这两个系统中,活跃的内核线程和用户进程有直接的对应关系,而L4会运行它喜欢的任何Task。因为Linux kernel中的内核线程都是私有的实现,Linux可以同时执行不同阶段的多个系统调用,或许一个进程在它的内核线程中在等待磁盘,这时Linux可以运行另一个进程的内核线程来处理另一个进程的系统调用。

你或许会想知道为什么不直接使用L4线程来实现Linux内的内核线程,或者说Linux为什么要实现自己内部的内核线程,而不是使用L4线程,答案是,

  • 在论文发表时,还没有用到多核CPU硬件,他们使用的是单核CPU硬件。所以在内核中同时运行多个内核线程并没有性能优势,因为只有一个CPU核,所以第二个线程不能执行,由于硬件的限制,一次只能执行一个线程。

  • 另一个或许是更强大的原因是,在论文发表时,他们使用的Linux版本并不支持将Linux kernel运行在多个CPU核上。所以他们使用的是旧版本的单核Linux,一次只能期望在内核中使用一个CPU,它并没有类似于XV6的spinlock,可以使得它能正确的在内核中使用多核。所以在Linux内核中使用多个L4线程并没有性能优势。如果一定要使用的话,在没有性能优势的前提下,又需要加入spinlock和其他的内容来支持并发。所以论文中没有在Linux内核使用L4线程。

这种架构的一个缺点是,在普通原生的Linux中,存在大量复杂的线程调度机制,例如在不同进程上增加优先级,确保调度公平性等等。Linux可以在你的笔记本上运行这些机制,因为Linux控制了哪些进程可以运行在哪些CPU核上。但是在这里的架构中,Linux完全控制不了哪些进程可以运行,因为现在是L4而不是Linux在完成调度,这些进程都是被L4所调度。所以这里的架构失去了Linux的调度能力,这是这种架构的缺点,我相信L4的后续版本有一些方法能够让Linux通知L4调度器,来给某个进程更高优先级等等。

论文