> For the complete documentation index, see [llms.txt](https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/lec18-os-organization-robert/18.6-run-linux-on-top-of-l4-micro-kernel.md).

# 18.6 Run Linux on top of L4 micro kernel

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

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

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

在今天[论文](https://pdos.csail.mit.edu/6.828/2020/readings/microkernel.pdf)的讨论中，L4微内核位于底部，但是同时，一个完整的Linux作为一个巨大的服务运行在用户空间进程中。听起来有点奇怪，一般的kernel都是运行在硬件之上，而现在Linux kernel是一个用户空间进程。

![](/files/-MXiyDOVIGyvW10YMQAM)

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

![](/files/-MXizrkQOGNsZ294kg78)

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

![](/files/-MXj0IDhMEfYfMmPR9G0)

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。

![](/files/-MXj2O_CR39FrwVfmWpR)

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

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

![](/files/-MXlhvJ_z7heB54CpKFo)

但是哪个用户进程可以运行，是由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调度器，来给某个进程更高优先级等等。
