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.4 L4 micro kernel

Previous18.3 Why micro kernel?Next18.5 Improving IPC by Kernel Design

Last updated 4 years ago

Was this helpful?

今天要讨论的,有许多有关L4微内核的内容。这是今天论文作者开发和使用的一种微内核。L4必然不是最早的微内核,但是从1980年代开始,它是最早一批可以工作的微内核之一,并且它非常能展现微内核是如何工作的。在许多年里面它一直都有活跃的开发和演进。如果你查看Wikipedia,L4有15-20个变种,有一些从1980年代开始开发的项目现在还存在。接下来我将从我的理解向你们解释L4在今天的论文发表的时候是如何工作的。

首先,L4是微内核,它只有7个系统调用,虽然其中有一些稍微有点复杂,但是它还是只有7个系统调用。然而现在的Linux,我上次数了下有大概350个系统调用。甚至XV6这个极其简单的内核,也有21个系统调用。从这个指标来看,L4更加简单。

其次,L4并不大,论文发表的时候,它只有13000行代码,这并不多。XV6的代码更少,我认为XV6内核只有6000-7000行代码,所以作为内核XV6非常的简单。L4也没有复杂太多,它只有Linux代码的几十分之一,所以它非常的小。

第三,它只包含几个非常基础的抽象。

它在内部有一个叫做Task或者地址空间的概念,这或多或少的对应了Uinx内的进程概念。Task包含了一些内存,地址从0开始,并且可以像进程一样执行指令。区别于XV6的是,每个Task可以有多个线程,L4会调度每个Task内的多个线程的执行。这样设计的原因是,可以非常方便地用线程来作为组织程序结构的工具。我不知道在论文发表的时候,L4是否支持了多处理器,或许它包含了在多个处理器上运行同一个程序的能力。所以L4内核知道Task,知道线程,也知道地址空间,这样你就可以告诉L4如何映射地址空间内的内存Page。

另一个L4知道的事情是IPC。每一个线程都有一个标识符,其中一个线程可以说,我想要向拥有这个标识符的另一个线程发送几个字节。

这里的Task,线程,地址空间,IPC是L4唯一有的抽象。

我不确定是否能列出所有的系统调用,这里涉及到的系统调用有:

  • Threadcreate系统调用,你提供一个地址空间ID并要求创建一个新的线程。如果地址空间或者Task不存在,系统调用会创建一个新的Task。所以这个系统调用即可以创建线程,又可以创建Task。

  • Send/Recv IPC系统调用。

  • Mapping系统调动可以映射内存Page到当前Task或者其他Task的地址空间中。你可以要求L4来改变当前Task的地址空间和Page Table,如果你有足够的权限,你也可以要求L4改变其他Task的地址空间。这实际上是通过IPC完成的,你会发送一个特殊的IPC消息到目标线程,内核可以识别这个IPC消息,并会修改目标线程的地址空间。如果你创建一个先的线程,新线程最开始没有任何内存。所以如果你想创建一个线程,你先调用Threadcreate系统调用来创建新的线程,新的Task和地址空间。然后你创建一个特殊 IPC,将你自己内存中的一部分,其中包含了指令和数据,映射到新的Task的地址空间中。之后你再发送一个特殊的Start IPC消息到这个新的Task,其中包含了你期望新的Task开始执行程序的程序计数器和Stack Pointer。之后新的Task会在你设置好的内存中,从你要求的程序计数器位置开始执行。

  • 虽然我不知道具体是怎么实现的,但是Privileged Task可以将硬件控制寄存器映射到自己的地址空间中。所以L4并不知道例如磁盘或者网卡的设备信息,但是实现了设备驱动的用户空间软件可以直接访问设备硬件。

  • 你可以设置L4将任何一个设备的中断转换成IPC消息。这样,运行设备驱动的Task不仅可以读写了设备,并且也可以设置L4将特定设备的中断通过IPC消息发送给自己。

  • 最后,一个Task可以设置L4内核通知自己有关另一个Task的Page Fault。所以如果一个Task发生了Page Fault,L4会将Page Fault转换成一个IPC消息,并发送给另一个指定的Pager Task。每一个Task都有个与之关联的Pager Task用来处理自己相关的Page Fault。这就是关联到Page Fault的方法,通过它可以实现类似copy-on-write fork或者lazy allocation。

以上就是内核的内容,L4里面不包含其他的功能,没有文件系统,没有fork/exec系统调用,除了这里非常简单的IPC之外,没有其他例如pipe的通信机制,没有设备驱动,没有网络的支持等等。任何其他你想要的功能,你需要以用户空间进程的方式提供。

L4能提供的一件事情是完成线程间切换。L4会完成线程调度和context switch,来让多个线程共用一个CPU。它实现的方式你会觉得非常熟悉,L4会为每个Task保存寄存器,当它执行一个线程时,它会跳到用户空间,切换到那个线程对应Task的Page Table,之后那个线程会在用户空间执行一会。之后或许会有一个定时器中断,定时器是L4知道的一个设备,定时器中断会使代码执行返回到L4内核,L4会保存线程的用户寄存器,然后在一个类似于XV6的线程调度循环中,选择一个Task来运行。通过将这个Task之前保存的寄存器恢复出来,切换Page Table,就可以跳转到Task中再运行一会,直到再发生另一个定时中断,或者当前Task出让了CPU。所以我认为L4或许还有一个yield系统调用。在这种情况下Task可以等待接收一个IPC消息,这时代码会跳转回L4内核,L4内核会保存寄存器,并切换到一个新的Task。所以L4中有关线程切换的部分你们会非常熟悉。

我之前提到过这个概念,Pager。如果一个进程触发了Page Fault,通过trap走到了内核,内核会将Page Fault转换成IPC消息并发送到指定的Pager Task,并告诉Pager Task是哪个线程的哪个地址触发了Page Fault。在Pager Task中,如果它实现了lazy allocation,那么它会负责从L4分配一些内存,向触发Page Fault的Task发送一个特殊的IPC,来恢复程序的运行。所以Pager Task实现了XV6或者Linux在Page Fault Handler中实现的所有功能。如果你想的话,你可以在Pager Task中实现copy-on-write fork或者memory mapped files,Pager Task可以实现基于Page Fault的各种技巧。

这是类似L4的微内核相比传统的内核,对于用户程序要灵活的多的众多例子之一。如果Linux并没有copy-on-write fork,并且你想要有这个功能,你不可能在不修改内核的前提下完成这个功能。Linux中没有办法写一些可移植的用户空间代码来实现copy-on-write fork。这样描述可能并不完全正确,但是一定要这么做的话会很复杂。然而,在L4里面,这就相对简单了。L4就好像是完全设计成让你去写用户空间代码来获取Page Fault,并实现copy-on-write fork。所有这些都可以在用户空间完成,而不用弄乱内核。

学生提问:能说明一下Task和线程之间的区别吗?

Robert教授:可以。一个Task就像XV6的一个进程一样,它有一些内存,一个地址空间,你可以在其中运行用户代码。如果你在XV6中有一个进程,它只能包含一个线程。但是在现代的操作系统和L4中,在一个进程,一个地址空间中,可以有多个线程。如果你有多个CPU核,那么多个CPU核可以同时运行一个Task。每个线程在Task的地址空间中都有一个设置好的Stack,这意味着你可以写一个程序,并通过并行运行在多个CPU核上得到性能的提升,其中的每个线程都运行在不同的CPU核上。

所以你们可以看到,这里的设计非常依赖IPC,因为如果你想与你的文件系统交互,文件系统想要与设备驱动交互,你都需要来回发送IPC消息。对于每个系统调用,每个Page Fault,每个设备中断,都会有反复的IPC消息。所以IPC系统需要非常快。

论文