# 18.4 L4 micro kernel

今天要讨论的[论文](https://pdos.csail.mit.edu/6.828/2020/readings/microkernel.pdf)，有许多有关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代码的几十分之一，所以它非常的小。

![](/files/-MXaSPF2zUj4-VZDA24n)

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

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

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

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

![](/files/-MXaSY4qx2aglEPoTV2r)

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

* 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。

![](/files/-MXabbn503do5ixrzCNt)

以上就是内核的内容，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系统需要非常快。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/lec18-os-organization-robert/18.4-l4-micro-kernel.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
