一个完全值得思考的问题是,一个操作系统内核本身应该具备什么功能?内核可以看做是一种程序员的开发平台
操作系统的传统实现方式以及应该具备的功能。
一个完成了各种事情的大的程序,它包含许多组件,例如fs;提供了许多接口,例如人以fd为代表的fs。
抽象
概念,实现了可移植的接口、隐蔽了复杂性、简化了应用程序、保护了硬件高度耦合
的,许多功能可以访问彼此的数据接口,简化了实现,但在崩溃时也会导致整个系统瘫痪Supervisor mode
,以最大的硬件权限在运行但他同样也存在一些缺点:
编程及其困难
,很容易就造出一个bug与安全漏洞
无法针对特定场景进行优化
。削弱抽象能力
,因为应用程序需要遵循设计考虑。例如以上就是人们认为的monolithic kernel的问题。这些问题使得人们去思考操作系统的其他架构。这里有很多不同的想法,其中一些想法非常的激进,我们今天只会讨论其中的一种,也是现在非常流行的一种,这就是微内核,Micro kernel。
微内核的核心就是实现了IPC(Inter-Process Communication)以及线程和任务的tiny kernel,它只提供了进程的抽象以及 IPC 进程间通信的方式
。
例如fs这样的功能只会通过一个用户进程实现。假设 vi 需要读取一个文件,他会通过 IPC 和 fs 通信,fs与磁盘驱动程序通信,并返回数据
内核中没有任何文件系统,没有任何设备驱动,没有网络协议栈,所有这些东西以普通用户进程在运行。所以这提供给你一种非常小的内核,以及相对少的代码去优化,你可以优化IPC,除此之外也没有别的东西了。
微内核中的用户进程通过IPC通信,这在很多操作系统都存在。例如我现在运行的macOS,它就是一个普通的monolithic kernel,它也很好的支持用户进程通过IPC进行通信。所以用户进程通过内核内的IPC相互通信,这是一个成功的思想并且被广泛采用。
安全
,能够验证os的正确性,例如seL4优化
快
灵活
模块化
内核功能,例如fs健壮
运行多个os
以上就是人们在微内核这条道路上期望得到的一些好处。当然,这里也有一些挑战。
系统调用的最小集
?开发用户空间实现os的其他部分功能
?IPC 足够快吗
?支撑得起大量进程间的通信吗?7
个系统调用,作为对比,linux有三百多个系统调用4
个非常基础的抽象:Task
或者 地址空间
:包含了一些内存,地址从0开始,并且可以像进程一样执行指令,每个Task可以有多个 线程
,L4会调度每个Task内的多个线程的执行IPC
:每一个线程都有一个标识符,其中一个线程可以说,我想要向拥有这个标识符的另一个线程发送几个字节Threadcreate
:提供一个地址空间ID并要求创建一个新的线程Send/Recv IPC
系统调用Mapping
:映射内存page到当前或其他Task的地址空间中Privileged Task
可以将硬件控制寄存器映射到自己的地址空间中。中断转换成IPC消息
通知
自己有关另一个Task的Page Fault
L4能提供的一件事情是完成线程间切换,进行线程调度和context switch
Pager Task可以实现基于Page Fault的各种技巧。
如果一个进程触发了Page Fault,通过trap走到了内核,内核会将Page Fault转换成IPC消息并发送到指定的Pager Task,并告诉Pager Task是哪个线程的哪个地址触发了Page Fault。
一种 IPC 设计基于Unix Pipe,非常简单但也很慢
在实际中,几乎很少情况你会只想要发送一个消息,你几乎总是想要能再得到一个回复。这是RPC的用武之地
举个例子:
这种方式被称为异步传输,也被称作是buffered system
在一个单核系统中,这样的设计为了让消息能够发送和回复,将要包含:
每一次用户空间和内核空间之间的切换和context switching都很费时,都需要切换PT、清空TLB。此外消息的拷贝、缓存的分配也很费时
P1的send会等待P2调用recv,当P1和P2都到达了内核中,这时才会发生一些事情。
优点
:内核可以直接将消息从用户空间P1拷贝到用户空间P2,而不用先拷贝到内核中,再从内核中拷出来。消息超级小
,比如说只有几十个字节,它可以在寄存器中传递,而不需要拷贝,你可以称之为Zero Copy
。非常长的消息
,L4可以在一个IPC消息中携带一个Page映射,共享部分地址空间RPC
:减少一半的内核用户空间切换从P2的角度来看,就像是从recv中返回一样,完全不需要context switching、保存寄存器等等缓慢的操作
这种方式也被称为unbuffered。它不需要buffer一部分原因是因为它是同步的。
——我们该怎么处理操作系统剩下的部分?
实际上在一些特殊的应用场合,以上的问题并不是问题,例如车里的点火控制器完全不需要fs。
一种可能是,重新以微内核的方式,以大量的进程实现所有的内容。
这涉及到大量的工作,需要提供相同的系统调用或者更高层的服务接口,完全兼容一些现有的操作系统
另一种方式是,将一个现有的 monolithic kernel 运行在微内核之上
,现在Linux kernel是一个用户空间进程。
需要对Linux的底层做一些修改,以将它们改成调用L4微内核的系统调用,或者发送IPC,而不是直接访问硬件。
例如Linux中期望能直接修改Page Table的内容,读写CPU寄存器。
Linux的大部分内容都可以不做修改而直接运行。所以按照这种方式,作为Linux的一部分,现在得到了文件系统,网络支持,各种设备驱动等等,而不需要自己实现这些。
所有的程序都以为它们运行在Linux中
这意味着,如果Linux kernel Task没有做其他事情的话,它会在一个recv系统调用中等待接收从任何一个进程发来的下一个系统调用请求IPC。这导致了这里的Linux和普通的Linux明显不同的工作方式,普通linux的内核线程与每个进程是一对一关系,这里的Linux kernel运行在一个L4线程中
但是哪个用户进程可以运行,是由L4决定的。所以在这里的设置中,Linux kernel或许在内核线程中执行来自VI的系统调用,同时,L4又使得Shell在用户空间运行了。这在XV6或者Linux极不可能发生,在这两个系统中,活跃的内核线程和用户进程有直接的对应关系,而L4会运行它喜欢的任何Task。
你或许会想知道为什么不直接使用L4线程来实现Linux内的内核线程,或者说Linux为什么要实现自己内部的内核线程,而不是使用L4线程,答案是,
在论文发表时,还没有用到多核CPU硬件,他们使用的是单核CPU硬件。所以在内核中同时运行多个内核线程并没有性能优势,因为只有一个CPU核,所以第二个线程不能执行,由于硬件的限制,一次只能执行一个线程。
另一个或许是更强大的原因是,在论文发表时,他们使用的Linux版本并不支持将Linux kernel运行在多个CPU核上。所以他们使用的是旧版本的单核Linux,一次只能期望在内核中使用一个CPU,它并没有类似于XV6的spinlock,可以使得它能正确的在内核中使用多核。所以在Linux内核中使用多个L4线程并没有性能优势。如果一定要使用的话,在没有性能优势的前提下,又需要加入spinlock和其他的内容来支持并发。所以论文中没有在Linux内核使用L4线程。
这种架构的一个缺点是,Linux完全控制不了哪些进程可以运行,因为现在是L4而不是Linux在完成调度,这些进程都是被L4所调度。
我相信L4的后续版本有一些方法能够让Linux通知L4调度器,来给某个进程更高优先级等等。
论文的表二做了性能对比,运行在硬件上的原生Linux执行一个简单的系统调用getpid花费1.7us,对于上一节的实现,需要发送一个IPC request,并获取一个IPC response,以实现getpid系统调用,这需要花费接近4us,这是原生Linux的两倍多。主要是因为这里有两倍的工作,这里涉及到两次用户空间到内核空间的切换,而不是一个简单的系统调用。这也说明L4已经将这种基于IPC的系统调用的代价降到了最低,也就是2倍于一个原生Linux的系统调用。因此,它可以做的大概与你期望的一样好。
当然这里的系统调用仍然只有原生Linux一半的速度。现在还不清楚这是否是一个灾难,还是说并没有问题。如果你执行大量的系统调用或许就是个问题;如果你执行了相对较少的系统调用,或者系统调用本身就有很多工作,或者你的系统调用比getpid要复杂的多,这又或许不是个问题。论文中通过使用AIM做的测试结果,给出了答案。测试结果在论文的图8。
AIM会执行各种系统调用,例如read/write文件,创建进程等等。从图8可以看出,在AIM设置的一个更完整的应用中,基于L4的Linux之比原生Linux慢几个百分点。因此,理想情况下你可以期望你想要运行在计算机上行的应用,如果在L4+Linux上运行可以与运行在原生操作系统上一样快。因为可以以近似原生Linux的速度运行,所以你们现在应该认真对待微内核。图8是一个非常不错的结果,有点超出预期。
让时间快进20年,如果之前所说,现在人们实际上在一些嵌入式系统中使用L4,尤其在智能手机里有很多L4实例在运行,它们与Unix并没有兼容性。在一些更通用的场景下,像是工作站和服务器, 微内核从来没有真正的流行过,并不是因为这里的设计有什么问题,只是为了能够吸引一些软件,微内核需要做的更好,这样人们才会有动力切换到微内核。
对于人们来说很难决定微内核是否足够好,这样才值得让他们经历从现在正在运行的Linux或者其他系统迁移到微内核的所需要的各种麻烦事。所以,微内核从来没有真正流行过,因为它们并没有明显的更好。
另一方面来看,微内核的很多思想都有持久的影响。
人们实现了更加灵活和有趣的方法来在微内核上使用虚拟内存。这些复杂的多的接口导致了mmap这样的系统调用合并到了例如Linux的主流操作系统中。
论文中将一个操作系统作为一个用户程序运行另一个操作系统之上,今天以另一种方式非常流行的存在:在Virtual Machine Monitor上运行虚拟机。这种方式在各种场景,例如云主机上,都有使用。
为了让内核能够具有一个用户空间服务一样的可扩展性,在Linux中演进成了可加载的内核模块,这使得你可以在线修改Linux的工作方式。
当然,这里基于IPC的Client-Server支持,也在macOS有所体现,macOS中也有好用的IPC。
学生提问:看起来一些任务更适合在内核中,但是内核的方案中,要么所有东西都在内核要么都不在。所以要么你有一个monolithic kernel可以完成所有的事情,要么有个micro kernel什么也不做。我认为虚拟内存、文件系统和一些其他的事情在内核中做会非常的有效。不能有些系统具备有一些功能,然后你又可以选择用不用这些功能吗?
Robert教授:所有你说的都有非常有道理。实际上有很多微内核相关的项目都构建了各种hybrid内核,MACH有一些不同的版本,其中一些就是hybrid内核,这些内核的核心是包括了IPC的微内核,同时在内核中又是一个完整的Unix系统,比如MACH 2.5就是这样一个hybrid内核,其中一些东西是按照微内核的方式构建,一些东西又是按照Unix方式构建。现代的macOS也以与你描述类似的方式构建,macOS本身是个完整的操作系统,包含了文件系统,同时也很好的支持了IPC和其他用来构建微内核的东西。Google Fuchsia也实现了一些这里的想法。