Click HERE for English version.
RME 是一个支持很多高级功能的通用操作系统。本系统的一些高级功能包括:
- 基于权能(capability)的可配置保护域(configurable protection domain);
- 高度的可扩展性(Scalability)和并行性(parallelism);
- 容错性(fault-tolerance)和抗攻击性(attack resilience);
- 用户态层次化调度(hierachical scheduling);
- 全虚拟化技术和基于容器(container)的准虚拟化(paravirtualization)技术;
- 非易失性内存(Non-volatile memory,NVM)技术应用;
- 网络功能虚拟化(Network function virtualization,NFV)技术应用;
- 实时(real-time)多核混合关键度(mixed-criticality)系统等等。
本系统的手册可以在 这里 找到。
如果想要参与开发,请阅读 参与 和 规范 两个指导文档。如果要提交拉取请求,请使用 拉取请求模板 。 本软件是EDI的官方作品,因此属于 公有领域 。 所有由EDI保留的版权在所有适用的法律条款下尽最大可能地授权给所有实体。
对于那些由微控制器厂商提供的硬件抽象层软件包,请到 M0A00_Library 软件仓库自行下载。
微内核的发明至少已有30年的历史,在这期间涌现了大量强调性能、并行性、容错性、安全性甚至形式正确性的优秀设计。然而,没有任何一个设计能够在所有计算设备上建立统一的抽象,这损害了软件的可移植性和生态系统的一致性。具体来说,(1) 很少有微内核支持高并行的云原生环境,(2) 支持资源稀缺的微控制器的更少,(3) 没有一个能同时支持这两种极端以及这两种极端之间的设备的单一内核设计。此外,现有系统的可配置性也受到限制,这是因为很少有系统能允许你自由选择需要的代码行。如果系统在设计阶段没有考虑到可配置性和重用性,那么即便在后续重新添加这些功能可能效果也不会太好。
RME的终极目标是实现超适应性。它为所有类型的硬件构建了一个总体抽象,无论是云服务器、边缘路由器、终端节点,甚至是电池供电的设备。在构建这样的抽象时,我们的目标是发现并表达不同硬件模型之间的真正共同点,而不是将一个硬件模型强行转化为另一个。换句话说,相同的抽象及其最终实现应当在微处理器和微控制器上都可以实际使用:当部署在前者时,它可以媲美最先进的云原生操作系统,同时还能提供更多的并行性;当部署在后者时,它可以媲美最先进的实时操作系统,同时还能提供更高的安全性和容错性。这揭示了以前从未意识到的研究机会,因为来自不同场景的关注点相互交织。对于任何特定场景,我们会思考一个针对该特定情况完全从头构建的系统(包括内核、驱动、中间件和应用程序)会是什么样子,并评估RME是否能够达到同样水平(这个过程中在某种程度上类似于证明一个贪心算法的最优性)。如果不能,那么就存在系统设计优化的机会。
RME的第二个目标是实现超复用性。包括驱动程序和中间件在内的所有系统组件都是完全解耦的,并以一种可以配置和部署为独立软件包的方式编写。这使得它们可以与其他操作系统一起使用,甚至在没有操作系统的情况下也能使用。这些组件以一种特殊的方式编写,使得当库中仅存在一个实例时,可以完全消除函数指针,从而减少冗余并最大限度地提高资源效率。理想情况下,如果组件配置正确,最终映像将不会包含任何一行垃圾代码。同时,剔除未使用的代码也有助于系统安全,因为未使用的垃圾代码中的漏洞将不会被带入生成的映像中。
RME的最后一个目标是实现超可部署性。许多微内核的存在仅仅是为了教育或研究目的,由于缺乏软件工程方面的考虑,它们无法在商业甚至业余项目中使用。RME的不同之处在于,它有意直面工程难度,并愿意投入资源支持尽可能多的生产环境。这尤其适用于微控制器,因为其编译工具链较为晦涩,调试功能也受到限制。为此,RVM 微控制器虚拟机管理程序处理了所有复杂的工作,并且提供了开箱即用的Keil或Makefile项目,只需一键即可使用。更多工具链如IAR也在计划支持中。
我们认为以下原则是不言而喻的:
- 理想比想法廉价,想法比代码廉价。
- 理论上,理论与实践没有任何区别;实际上,则有区别。
- 所有的性能问题都源于对权利和义务的模糊划分。
- 好的整体系统设计始终优于局部优化。
- 真正有用的适应性胜过虚假的示范可移植性。
- 如果你不得不强制某些底层硬件适应抽象模型,那么说明这个抽象是错误的。
我们探究这些需求背后的驱动力是什么,以及如何迎合这些驱动力。我们关注的是适合当前情况的方案,而不是手头已有的工具。
现代微内核系统采用了一种源自 EROS 的细粒度资源控制机制(该概念本身甚至更早),即权能(capabilities)。所有资源和机制都作为不可伪造的令牌暴露给用户态,以便用户可以使用这些机制并且细粒度的灵活编排策略。许多系统,如 seL4、Fiasco.OC、NOVA、Barrelfish 和 Composite,都是这样设计的,并且已被证明是成功的,我们也借鉴了类似的设计。权能可以被委派给进程或从进程中撤销,从而赋予或剥夺它们操作某些资源的特权。
然而,基于权能的设计并非没有问题。一般来说,简单的设计会存在三类问题:(1) 权能查找延迟,(2) 权能内存组织,以及(3) 权能运行效率。为了解决(1),我们大量借鉴了 Composite 的有限两级权能表构建方案,因此权能查找的最坏情况执行时间可以在无需抢占点的情况下得到限制。以 seL4 的术语来说,我们的 CSpace 深度限制为 2。为了解决(2),我们的权能统一大小并时刻保持8字节对齐,以最大程度的降低管理复杂性,尤其是在竞争环境激烈的多核环境中。为了解决(3),某些操作允许以事务的方式同时操作多个权能,从而最大限度的减少摊销开销。
需要注意的是,我们的保护域设计与 seL4 有显著不同:我们不会将权能空间附加到线程上,而是将其附加到进程,进程已经拥有地址空间(可以将其视为物理内存的权能)。RME 的进程是由权能表和页表组成的实体,权能表和页表将由在其内部运行的所有线程共享。除此之外,它与传统的 Unix 进程没有任何关系;这个名字只是为了表明某种遥远的熟悉感。
抢占决策和执行开销就是调度的核心内容。为了迎合用户态的调度策略,这两个方面都必须要暴露给用户层。随之而来的是效率问题,这是由于所有的时间敏感的抢占式调度都必须引起两次额外的上下文切换开销——系统需要先切换到调度线程,之后再切换到被调度器选中的线程。这对于时间敏感的系统(尤其是没有快速处理器的微控制器)来说并不适合。如果以这种方式实现,那么抢占延迟件很容易超过1000个时钟周期,这个过程至少需要几十毫秒。为了解决这个问题,大多数微内核系统设计者将整个调度器放在内核中,只有 Composite 是一个真正的例外。在 Composite 中,调度上下文 (Temporal Capabilities, TCaps) 完全独立于执行上下文(线程对象本身)。虽然为了减少上下文切换的延迟,抢占决策在内核中进行,但时间预算的转移则由用户态管理。内核中的抢占决策逻辑通过比较两个线程消耗时间的“质量”来决定谁能获得调度。当时间通过子系统向下授权时,这个“质量”由不同子系统被赋予的优先级决定:只有当所有子系统对优先级比较结果达成一致时,我们才可以说一个线程的质量优于另一个。这确保了抢占只有在所有子系统调度器同意的情况下才能发生,从而限制了它们之间的相互干扰。
但是这种设计也并非没有缺陷。首先,TCaps 的大小是可变的,这取决于子系统的数量,因为每个子系统都必须在 TCaps 内核对象中占据一个槽位来记录优先级。当我们在编译阶段无法确定需要多少子系统时,这种设计就会带来问题,并且当我们动态启动子系统的时候,情况会变得更糟糕,因为我们有可能耗尽 TCap 的槽位。其次,抢占决策是通过数组比较来完成的,这就导致其在超标量处理器上速度很快(因为比较操作没有相互依赖,因此可以在最大流水线宽度下实现),而在单发射微控制器上效率极低。这是一个很讽刺的事实,因为调度器在它试图解决的真正重要的场景中表现得很慢。最后,TCaps 和线程权能一样,都需要在线程调度时被指定,这也增加了权能有效性检查的额外开销。同样,这在超标量处理器上问题不大,因为两个检查之间不存在依赖关系,但在微控制器上会引发问题。结果就是一个在 x86 计算机上表现出色的内核,却在微控制器上表现非常慢。而且复杂的处理逻辑会导致代码体积膨胀,这在 x86 上无足轻重,却在微控制器上至关重要。此外,当多个加速器共存时,这种设计也不可扩展;只有当所有子系统上的加速器都同意时,我们才能做出抢占决策,这迫使我们不得不在内核中进行张量比较,并且为了尽可能快的完成这项操作,我们可能需要利用 AVX 扩展!
鉴于上述情况,我们设计了一种调度机制,该机制 (1) 将预算与线程关联起来,(2) 在内核中具有一个简单的 FPRR 抢占逻辑,但 (3) 将预算管理隔离在内核之外。这种机制看起来像一个奇怪的 FPRR 调度器,它只花费预算,但不会补充或撤销预算,后两者由用户态负责。在需要绝对延迟且时间敏感性与优先级一致的微控制器上,可以直接利用内核中的 FPRR 调度器(参见 RVM)来提供最小延迟和基本预算控制。在构建复杂 MCS 的 x86 系统上,可以通过安排线程的优先级来规避内核中的 FPRR 机制,并且可以使用任意复杂的用户态调度策略,包括类似于 TCaps 的机制。换句话说,我们在用户态实现了整个 MCS 策略(即带宽服务器),此外我们容忍基于 x86 的系统上调度程序之间的额外上下文切换。我们认为这只会造成轻微的性能损失,因为 x86 的超标量管道将弥补这些开销。此外,当我们考虑 MCS 时,有界干扰比绝对的最小延迟更为重要。
内存由用户内存和内核内存组成,所有的物理内存都要划分为其中之一。在用户态管理用户级内存并不困难,早在 L4 等操作系统中采用弹性页(Flexpages)的方式就已经实现了该功能。弹性页能够暴露的内存组成太少,而且隐含了物理页面的使用策略,因此我们采用 exokernel,通过暴露页面表的实际构造来进行管理。在用户态管理内核内存才是真正的挑战,因为简单的机制设计很容易导致机密性、完整性、可用性或责任性受损。为此,seL4 和 Composite 利用内存类型重设(Memory Retyping)技术来避免将正在使用的内核内存公开为用户页,并且每个正在使用的页要么属于内核态,要么属于用户态。所有的内存开始都是未定义的,而且必须经过类型重设为内核类型或用户类型,才能被映射。此外,所有的内存对象(Composite)或内存池(seL4)无论其实际大小如何,都是单个页面,从而最小化管理复杂度。seL4 确实允许在同一池中分配不同的对象,但有对齐限制。
上述模型如果应用于普通的 x86 系统效果很好,但当应用于微控制器时效果就没有那么好了。当然,它也不适合一些深度嵌入式处理器。不同类型的制度存在两个相互矛盾的问题:(1) 分配机制过于灵活;(2) 分配机制过于不灵活。对于前者来说,大多数的微控制器只需要固定的将内存分为内核内存和用户内存,所有内核对象都是在启动时被创建,之后不再会发生进一步的创建或删除,并且所有的类型重设逻辑都是冗余的。对于后者来说,“太不灵活”有两种情况:(1) 在微控制器上,强制对象对齐并没有好处,反而浪费内存;当所有对象都必须是一个“页面”时,情况会变得更糟,微控制器实际上没有这个页面(它们只有物理内存段);(2) 在微处理器上,逐个操作页面可能会太慢,因为分配可能涉及数千个页面的映射和撤销映射。
考虑到以上所有因素,我们的设计简单的允许为所有内核对象设置一个编译时内核地址边界,所有内核对象都应在该边界内创建。其余内存可用作用户内存。内核对象注册表(位图)以非常细的粒度(即32字节)来记住哪个位置有内核对象。在微控制器上,这个边界是一个由 RVM 项目生成器决定的固定值;在深度嵌入式处理器上,这个边界是一个由系统设计人员确定的固定值。在这两种情况下,不再需要内核和用户内存的重分配,简单的二分法已经足够了。尽管这种简单的二分法似乎非常僵化,但通过添加用户级可信页面服务器,它可以变得非常灵活:在通用处理器上,我们将这个边界设置为最大地址,从而绕过内核的检查机制,因此所有内存都可以用作内核内存或用户内存。此时的可信服务器负责确保内核内存不会和用户内存重叠:它可以控制将哪些用户页和子内核内存权能授权给其下属,来避免安全隐患。这种可信服务器可以视作运行在用户态的强制内核模块。换句话说,我们在用户态实现了类型重设类型。此可信服务器处于用户态并不会损害内核最坏情况执行时间(WCET),因此可以使用任意复杂的类型重设类型策略。这类似于调度器的设计,因为我们允许用户绕过内核并提供一些替代方案。
不仅如此,RME 允许进一步推进“不完整内核 + 可信服务器”理念,并可以在编译时配置为完全不使用内核地址空间管理机制。如果选择了此选项,内核将接受在进程创建时给出的任何物理地址,并且将其传递到 HAL(即 x86 的 CR3)而无需进行任何检查!用户态可信进程现在负责所有地址空间管理内容,包括 ASID、缓存策略等。在计算范畴的两个极端上,其优势非常明显:(1) 在 RAM 小于60k的微控制器上,这允许将固定的闪存内内存保护单元(Memory Protection Unit, MPU)表直接加载到 MPU 中,而无需所有页表逻辑和内存数据结构。只要不在运行时修改用户态内存映射,这是一个很好的选择,可以节省大量内存。(2) 在拥有 TB 级内存的云原生服务器上,许多操作(如需求分页)涉及数千个页面,而允许直接操作页面可以将性能提高到单片内核的水平。此外,可信进程可以以符合应用程序性质的方式提供地址空间元操作(即 POSIX 或类似 Linux 的系统调用),与微内核内置操作相比,可显著降低摊销分页成本。请注意,这比 exokernel 更进一步:内核甚至不保护自己,需要一个经过精心设计的可信服务器来做到这一点。
我们为每个CPU仅保留一个内核堆栈。我们不需要可抢占的内核,因为没有不受限制的循环或递归。
并行性是现代高性能计算的标志,并且在实时系统中逐渐流行。为了支持尽可能多的并行性和确定性,我们使用无锁数据结构设计整个内核,并且每个系统调用都是具有有界执行时间的事务。竞争不是通过锁来解决,而是通过原子操作来解决。我们还采用基于静止的实时可扩展内存回收 (RT-SMR) 来回收废弃的内核对象。这部分大量借鉴了 Composite,但有明显的区别。RME 允许使用周期性计时器以及时间戳计数器,并要求在对线程进行操作之前明确将线程绑定到核心。不幸的是,我们目前没有可运行的 x86-64 移植。
并行性不在微控制器的范围之内(至少在当前的 RVM 版本中),可能会在具有缓存一致性核心和原子指令的微控制器普及时添加。
进程间通信(Inter-Process Communication, IPC)设计始终是任何操作系统性能的关键决定因素。对于微内核来说,IPC 的重要性就更加突出,因为此类系统将功能拆分为多个相互通信的服务。IPC 可进一步细分为两类:同步和异步。同步 IPC 要求客户端在服务器响应之前保持阻塞,类似于过程调用,而异步 IPC 没有这样的要求,看起来更像消息队列发送。在实际操作中,同步 IPC 通常用于客户端和服务器紧密耦合的场景,异步 IPC 则更多用于两者松散耦合的情况。
对于同步 IPC,我们实现了类似于 Mach 和 Composite 的迁移线程模型,允许线程“进入”和“离开”进程,就像在该进程中调用一个函数一样。就像系统调用“移动”线程进出内核空间一样,当一个客户端调用一个调用端口时,线程会暂时“移动”到服务器进程中并执行服务器代码;成功执行后,线程返回到原始进程。服务器进程在未被调用时可能不包含任何线程。该设计的亮点在于客户端使用自己的时间预算执行服务器代码;因此强制实施了时间隔离,因为恶意线程在过度请求服务时只会浪费自己的时间。这还提高了上下文切换的效率,因为它不涉及调度程序,毕竟我们使用的仍然是同一个线程。理解该模型的关键在于将线程视为持有时间预算的时间保护域;当然,同一个预算可以在不同的空间保护域(进程)中使用,而共享同一时间保护域的代码片段本质上是紧密耦合的,就像共享相同空间保护域的代码一样。用系统术语来说,我们允许一个调度上下文在不同的保护域之间迁移,几乎没有任何障碍。有人可能会认为这种模型的缺点是强制使用无锁(或至少无阻塞)数据结构实现多线程服务器。然而,我们认为服务器本质上是多线程和无锁的,否则我们将失去并行性、确定性和时间隔离。对于那些单线程服务器就足够的简单情况,我们还提供异步信号。
不过,调用的实现与 Mach 和 Composite 有所不同。我们不会为不同的线程使用同一个端口,而是为它们使用不同的端口,这样调用前的上下文可以存储在调用对象中,而不是线程对象中。这消除了一个编译时的配置宏,并使调用端口更容易与预先准备好的堆栈关联。
对于异步 IPC,我们实现了一个模型,其行为类似于一个处理器内的计数信号量。当一个线程请求一个信号量但没有信号量可用时,它将阻塞,直到同一处理器上的其他线程向端点发送信号。然而,跨处理器发送只会增加信号量计数,而不会解除等待线程的阻塞,且一个端点上最多只能有一个线程阻塞。换句话说,我们在用户态实现了跨处理器中断,这使得我们的内核看起来有点像 Barrelfish 这样的分离内核。当你意识到跨处理器中断是需要由用户态策略管理的硬件资源时,这种设计就显得合理了。但这与 seL4 和 Composite 非常不同,后者不提供计数功能,但提供跨处理器的激活。
与许多其他微内核一样,RME 在设计阶段就包含了对其他操作系统的半虚拟化支持(无需任何硬件扩展),而不是事后才考虑这一功能。目前主流存在两种方法:基于线程和基于虚拟CPU。许多研究微内核采用前一种方法,因为它将虚拟机线程与微内核线程相对应,因此可以使用微内核系统调用的组合来代替客户机 HAL 层。后一种方法在商业产品中更常见,并且将单个线程与 CPU 核心上的所有执行相对应。
这两种方法各有缺点。L4Linux(Fiasco.OC)采用的基于线程的方法明显受到操作放大的影响:一个客户机操作几乎总是会被转换为一个或多个微内核操作,导致较长的延迟。如果虚拟化的系统是Linux,这可能没有问题,但在虚拟化简单系统时(如 Unikernels 或 RTOSes),则会出现问题:这些简单系统为了满足苛刻的效率或延迟要求,完全省略了权限检查。天真地将它们的HAL替换为微内核系统调用,会将(无用的)权限检查重新引入这些系统,使其性能下降到不可接受的水平。如果原系统的操作语义与微内核本地的操作语义(调度器等)冲突,这尤其成问题;在这种情况下,必须提供大量的微内核系统调用作为原始操作的替代品,导致性能衰退几个数量级。此外,每个客户机线程都需要创建线程内核对象,这会迅速耗尽微控制器的内存。
基于虚拟CPU的方法则没有操作放大的问题,因为所有客户机操作都是按原样执行的,而不需要将它们转换为微内核系统调用,但却面临 vIRET 原子性问题。为了模拟中断机制,管理程序强制将虚拟CPU线程置于模拟原始硬件中断处理程序的 vIRQ 序列中。vIRQ 处理中断后,尝试通过 vIRET 返回到正常执行状态。问题出在vIRET上,在管理程序能够再次将vCPU强制进入该序列之前,必须知道vCPU是否已退出中断处理程序。此时,RAM 中的一个简单的“中断启用”标志不起作用,因为在设置标志和返回原始vCPU上下文之间,新vIRQ可能会被注入。最简单的解决方案是提供一个独立的 vIRET 超调用,让管理程序处理中断启用/返回的原子性问题,但这样会大大增加 vIRQ 延迟,尤其是在微控制器上。Xen 使用了复杂的解决方案,管理程序在检测到标志时总是强制执行 vIRQ,无论 vIRQ 是否正在执行返回;虽然这种方法确实有效,但 vIRQ 入口必须能够修复未完成返回时堆栈留下的问题,就像入口是在返回完全完成后发生的一样。这个入口序列很难正确编写,更不用说它必须使用特定于体系结构的汇编语言来编写。
最终实现可以看作是介于两者之间的基于vCPU的改进方法。它允许非常高效的 II 型半虚拟化。每个vCPU对应两个线程,其中一个线程运行正常代码,另一个线程运行中断向量。我们将前者称为用户线程,后者称为向量线程。向量线程优先级比用户线程高,并在端点上阻塞以接收中断激活。当中断发生时,向量线程立即抢占用户线程,模仿物理CPU上的中断机制。鉴于许多客户机内核通过修改中断上下文来实现系统调用和上下文切换,我们允许用户线程作为专用于管理程序的线程创建,其上下文不会存储在内核对象中,而是存储在某个用户指定的区域中。该区域始终映射到内核空间以及客户机空间,这样两者都可以无异常地访问它。因此,向量线程可以修改用户线程的上下文,而无需微内核调用,从而大大减少客户机中断延迟。当向量线程完成处理后,它会再次阻塞在信号端点上,等待进一步的中断。这里的关键是 RME 会在使用用户线程寄存器集之前检查其有效性,这样非法修改就不会突破保护边界。
有人可能会认为将整个微内核设计为 I 型管理程序而不是使用独立的用户态 II 型管理程序会更高效。然而,这样做(1)会强制为所有应用程序引入虚拟化思维,导致只需要原生应用程序的情况变得复杂,(2)需要内核中的管理程序策略和超级调用实现,违反了微内核设计原则,(3)会使特权代码(即可信计算基础)变得臃肿。
至于全虚拟化,我们将其留给硬件虚拟化扩展。虽然通过使用影子页表和动态二进制转换等复杂技术可以完全虚拟化任何架构,但我们认为当前的虚拟化扩展已经淘汰了这些技术。然而,在这些平台上,RME会以 I 型虚拟机管理程序的身份启动。未来,完全虚拟化只会引入到x86-64架构上,因为该架构的硬件/软件接口已经成为事实标准,我们避免将其引入缺乏标准操作系统映像的其他架构。
所有的现有系统组件列于下表。如果提供了github链接,那么该组件现在就是可用的。
- RVM,一个面向微控制器的虚拟机监视器,可以在一个MCU上同时运行多个裸机应用或RTOS。它最多可以在128k片上内存中运行多达32个虚拟机。
- RVM/RMP,RMP在RVM上的一个全功能移植。
- RVM/FreeRTOS,广为应用的FreeRTOS在RVM上的一个全功能移植。
- RVM/RT-Thread,颇有前景的RT-Thread在RVM上的一个全功能移植,包括其所有框架。
- 正在进行中。我们之前有一个可启动的 x64 移植,但是目前无法运行。
内核在实际操作中的时序性能如下。所有的编译器选项都被设置为最高优化(通常是-O3,并在可用时启用 LTO),而且针对运行时间进行了优化。所有值均为平均情况下的 CPU 周期。
- Yield/S :进程内两线程间进行切换。
- Yield/2 :进程间两线程间进行单向切换。
- Inv/2 :进程间迁移调用的进入/退出操作对。
- Sig/1 :进程内信号端点的发送/接收延迟。
- Sig/2 :进程间信号端点的发送/接收延迟。
- Sig/S :进程内信号端点的发送/接收对。
- Sig/I :中断信号端点的发送/接收延迟。
RME 在微控制器上的绝对最小值约为 64k ROM 和 20k RAM,这是在 STM32L071CB(Cortex-M0+)移植上达到的。这是可以完成基准测试(以及虚拟化 RMP 基准测试)的绝对最小概念验证。RME 还要求微控制器具备内存保护单元(Memory Protection Unit, MPU),通过 MPU 我们可以将进程的执行限制在各自的地址空间内。
在微控制器上,要求将 RVM 作为用户级库使用,它支持针对多种架构、工具链和集成开发环境(IDE)自动生成项目。它还在微控制器上实现了虚拟化,允许与现有的裸机代码或实时操作系统(RTOS)无缝集成。目前仅支持单核微控制器;对微控制器的多核支持目前不在计划之内。
芯片名称 | 架构 | 时钟频率 | 闪存 | 内存 | 工具链 | Yield/S | Yield/2 | Inv/2 | Sig/1 | Sig/2 | Sig/S | Sig/I |
---|---|---|---|---|---|---|---|---|---|---|---|---|
STM32L071CB | Cortex-M0+ | 32M | 128k | 20k | Keil | 492 | 763 | 956 | 718 | 810 | 749 | 522 |
... | ... | ... | ... | ... | GCC | 513 | 799 | 939 | 736 | 830 | 776 | 534 |
STM32F405RG | Cortex-M4F | 168M | 1M | 192k | Keil | 324 | 524 | 692 | 576 | 731 | 568 | 420 |
... | ... | ... | ... | ... | GCC | 332 | 520 | 684 | 608 | 735 | 540 | 416 |
STM32F767IG | Cortex-M7F | 216M | 1M | 512k | Keil | 264 | 400 | 600 | 438 | 484 | 477 | 282 |
... | ... | ... | ... | ... | GCC | 294 | 456 | 644 | 406 | 460 | 429 | 321 |
CH32V307VC | RV32IMAFC | 144M | 128k | 192k | GCC | 358 | 669 | 706 | 703 | 723 | 624 | 523 |
我们可以看到,保护模式内核的性能通常与裸机实时操作系统相当,测得的中断延迟约为 3 微秒。此外,当内核用于虚拟化实时操作系统时,虚拟化开销可以低至 6%。更多细节请参考 RVM 仓库。
常见问题: 能支持XXX(某些微控制器)吗? 回答: 如果您的微控制器像大多数 Cortex-M 微控制器一样,至少具有 64k ROM 和 20k RAM,并且具备内存保护单元,那么是的!不过,最好是从 128k ROM 和 64k RAM 开始,因为这样的系统在实际项目中会更有用。一些低于 1 美元的芯片(例如 STM32G0B1CBT6) 对于 RME 来说已经足够了,类似的芯片也正在被广泛部署。
在微处理器上,RME 的推荐资源配置约为 32M ROM 和 32M RAM,F1C100S (ARM926EJ-S) 移植可达到此要求。尽管这不是运行基准测试的绝对最低要求(需要的内存更少),但对于有意义且有用的系统而言,这确实是必需的。微处理器必须配备内存管理单元 (MMU),以便将进程限制在各自的地址空间内。对于微处理器,我们只支持 GCC 工具链(可能将来也支持 CLANG),其他工具链不在支持范围内。
在微处理器上需要使用 RMC(概念设计正在进行中),这样可以将轻量级类 Unix 容器、Unikernel 和 RTOS 集成到同一平台。这无需硬件虚拟化拓展等特定扩展即可实现。对于有虚拟化扩展硬件的情况,我们会努力提供完整的虚拟化环境,你可以在其中运行 Windows 和 Linux。不过,我们不会深入研究驱动程序,并假设所有外设都采用直通模型。虽然这样灵活性不高,但可以毫不费力的使用现有的软件投资(桌面环境、行业应用,甚至 3D 游戏)。以下指标仅适用于 RMC 的 Unix 容器:
- Pipe : 进程间管道发送/接收延迟。
- Sem : 进程间信号量发送/接收延迟。
- Msgq : 进程间消息队列发送/接收延迟。
- Signal : 进程间信号触发/激活延迟。
- Socket : 进程间本地主机套接字发送/接收延迟。
芯片名称 | 架构 | Bits | Cores | Yield/S | Yield/2 | Inv/2 | Sig/1 | Sig/2 | Sig/S | Sig/I | Pipe | Sem | Msgq | Signal | Socket |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
F1C100S | ARM926EJ-S | 32 | 1 | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD |
S3C2416 | ... | 32 | 1 | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD |
XC7Z010 | Cortex-A9 | 32 | 2 | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD |
XCZU2EG | Cortex-A53 | 64 | 4 | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD |
AWT-D1S | RV64IMAFCV | 64 | 1 | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD |
LS1C300B | GS232 | 32 | 1 | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD |
LS2K300 | LA264 | 64 | 1 | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD |
E5-2696 v2 | x86-64 | 64 | 12 | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD |
TMS320C6678 | C66x | 64 | 8 | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD |
常见问题: 为什么不支持 XXX(某些流行的主板)? 回答: 与微控制器不同,想要获取一些微处理器制造商的数据表需要花费高昂的代价。由于没有任何公开信息,为了支持这些主板,我们必须从他们的 Linux 驱动程序中逆向工程细节。一些制造商因这一点而臭名昭著,我们宁愿不去支持他们,而是专注于那些拥抱开放的制造商。不过,对于那些已经被彻底逆向工程的芯片,我们会做出例外。
下面的说明会帮助你在本地快速建立一个可用来开发或评估测试本系统的工程。
要运行测试,你需要一个上面列出的硬件平台。本项目专注于至少 32 位的高性能平台,不支持 8/16 位的低端微控制器或缺少内存保护单元的微控制器,或 ARM9 之前的旧式微处理器。对于 8/16 位微控制器,请改用 RMP 实时内核;RMP 支持所有 Cortex-M 和部分 Cortex-R,但不支持内存保护。
如果你没有单独的硬件平台,那么也可以使用KVM、VMware、Virtual Box等虚拟机软件运行本系统的x86-64 ISO镜像。不要使用QEMU模拟器来测试本系统,因为QEMU有很多不完善之处,与真正的硬件行为并不一致。你可以使用 x86-64 QEMU,因为它经过了充分的测试并且使用 KVM 作为其底层虚拟化引擎。
微控制器
更多细节请参考RVM仓库。RME 中既不包含工程文件,也不包含编译说明。
微处理器
正在进行中。
微控制器
要运行测试,只要将测试下载到对应的开发板并开始单步调试即可。示例工程使用的硬件只有串行端口,并且在示例工程中已被配置。
微处理器
正在进行中。
微控制器
当部署本系统到生产环境时,请仔细阅读本系统自带的手册,以确保各项配置正确。更多细节请参考RVM工程。不推荐由用户自己配置内核;它包含太多的细节。请尽量使用提供好的默认配置文件。此外,一定要阅读对应架构的用户手册。
微处理器
使用部署其他操作系统或者虚拟机监视器的方法直接部署这个系统。
- GCC/Clang-LLVM
- Keil uVision (ARMCC/ARMCLANG)
其他的工具链现在不推荐或者当前不受支持,虽然要增加新的支持应该也很简单。
请阅读CONTRIBUTING.md文档来获得关于行为规范和提交代码的相关信息。
我们希望感谢乔治华盛顿大学的Composite开发组(这也是对本系统的设计影响最大的系统),以及德累斯顿工业大学的Fiasco.OC开发组。
- M7M01 R3T1
权能是一种最早在多用户计算机系统中引入的用来控制访问权限的凭证。它是一种不可伪造的用来唯一标识某种资源以及允许对该资源所进行的操作的的凭证。比如,Unix的文件描述符就是一种权能;Windows的访问权限也是一种权能。从某种意义上讲,权能就是一个指向某种资源的胖指针。我们使用如下三条原则来保证系统的安全性:
- 权能是不可伪造和不可在用户态擅自修改的;
- 进程只能用良好定义的授权接口获取权能;
- 权能只会被给予那些系统设计时负责管理该资源的进程。
使用权能来进行权限控制是个很老的点子了。几千年以前,皇帝和国王们制作一种特别的符文,用来授予他们的将军调兵遣将的能力。通常而言,这些符文包含了不可复制的(或者极其难以复制的)文字,上面规定了哪一支或哪一种部队可以被调动。这样,皇帝就能把管理军队的任务安全地交给将军。同样地,基于权能的操作系统能够极为巧妙地提供非常细粒度的权限管理。系统中的所有权限都由权能管理,这样就可以由用户态程序来定义具体的系统策略,因此比传统的Unix系统的灵活性要好得多。其他的好处还包括强化的安全边界,彻底的错误隔离和容易进行形式化分析。
简而言之: 不会 。
详细解释:如果系统 被设计的很好,并且使用方法也正确 的话(尤其指通信机制),微内核设计实际上有助于在多个方面 提高系统的效率 ,因为那些经常被访问的路径现在相当于被特别地大大优化了。实际上,在某些架构上,RME的线程切换效率和中断响应速度比RT-Linux能够快出整整40倍。当用户态库的时间消耗也被计算在内时,结果仍然比RT-Linux好整整25倍。
这是通过大量使用无锁数据结构和原子操作做到的。有关无锁操作和原子操作的更多知识请查看 这篇文章。
系统调用 | 调用号 | 功能描述 |
---|---|---|
RME_SVC_INV_RET | 0 | 从迁移调用返回 |
RME_SVC_INV_ACT | 1 | 进行迁移调用 |
RME_SVC_SIG_SND | 2 | 向信号端点发送信号 |
RME_SVC_SIG_RCV | 3 | 从信号端点接收信号 |
RME_SVC_KFN | 4 | 进行内核特殊功能函数调用 |
RME_SVC_THD_SCHED_FREE | 5 | 将某线程从某个CPU上释放 |
RME_SVC_THD_EXEC_SET | 6 | 设置线程的执行属性(入口和栈) |
RME_SVC_THD_SCHED_PRIO | 7 | 更改某线程的优先级 |
RME_SVC_THD_TIME_XFER | 8 | 转移时间到某线程 |
RME_SVC_THD_SWT | 9 | 切换到某线程 |
RME_SVC_CPT_CRT | 10 | 创建一个权能表 |
RME_SVC_CPT_DEL | 11 | 删除一个权能表 |
RME_SVC_CPT_FRZ | 12 | 冻结权能表内的某权能 |
RME_SVC_CPT_ADD | 13 | 进行权能传递 |
RME_SVC_CPT_REM | 14 | 移除权能表内的某权能 |
RME_SVC_PGT_CRT | 15 | 创建一个页目录 |
RME_SVC_PGT_DEL | 16 | 删除一个页目录 |
RME_SVC_PGT_ADD | 17 | 添加一个页表项 |
RME_SVC_PGT_REM | 18 | 移除一个页表项 |
RME_SVC_PGT_CON | 19 | 构造页表 |
RME_SVC_PGT_DES | 20 | 析构页表 |
RME_SVC_PRC_CRT | 21 | 创建一个进程 |
RME_SVC_PRC_DEL | 22 | 删除一个进程 |
RME_SVC_PRC_CPT | 23 | 替换进程的权能表 |
RME_SVC_PRC_PGT | 24 | 替换进程的页表 |
RME_SVC_THD_CRT | 25 | 创建一个线程 |
RME_SVC_THD_DEL | 26 | 删除一个线程 |
RME_SVC_THD_SCHED_BIND | 27 | 将线程绑定到某处理器 |
RME_SVC_THD_SCHED_RCV | 28 | 接收某线程的调度器信息 |
RME_SVC_SIG_CRT | 29 | 创建一个信号端点 |
RME_SVC_SIG_DEL | 30 | 删除一个信号端点 |
RME_SVC_INV_CRT | 31 | 创建一个迁移调用 |
RME_SVC_INV_DEL | 32 | 删除一个迁移调用 |
RME_SVC_INV_SET | 33 | 设置迁移调用的执行属性(入口和栈) |
... | 34-63 | 保留 |