rCore 论文精读:用 Rust 写操作系统的完整记录
精读对象:王润基《Rust 语言操作系统的设计与实现》。
说明:本文是学习笔记,不是摘要翻译。凡论文里的数字、结论,都标了出处章节;我自己的对照和引申会明确写出来。
为什么值得花时间读这篇
今天的 rcore-os 已经是一个有 ArceOS、StarryOS、Axvisor、TGOSKits 的庞大家族。但往回追,源头就是这篇 2019 年的文章——它记录了「从零开始,把一个能跑真实 Linux 程序的操作系统用 Rust 写出来」这件事完整发生了一遍。
它的价值不在结论(结论一句话就说完了),而在过程:哪一步先做、哪些坑是怎么踩出来的、哪几个设计是后来才回过味来的。这些东西在今天的成品代码里已经看不到了——代码只会告诉你「最后长什么样」,不会告诉你「为什么是这样」。
论文结论
论文的结论在摘要和第 5 章:
评估结果显示,rCore 在 Bug 数量、开发体验方面明显优于传统的 C 语言系统,但在性能上仍有少量差距。……综合来看,我们认为 Rust 语言是编写操作系统的理想选择。
但它同时列了四条「Rust 还不是完美语言」(第 5 章、4.3 节):
- 平台支持不足:Rust/LLVM 对小众指令集的支持落后于 GCC。论文写作时(2019 年初)RISC-V 支持才刚能用;rCore 甚至因为 RISC-V 原子变量最小是 32 位、而核心库里
AtomicBool是 8 位,不得不手动改库代码。 - 语言特性复杂:抽象能力强,但也意味着学起来慢。
- 容易误用导致优化出错:这一条最实在,见下文「能力分析」一节。
- 初学者门槛高:写不出能通过编译的代码时,人会倾向于用
unsafe绕开,结果退化成 C 风格的代码。
这四条是 2019 年的判断,今天看有的已经变了(平台支持好多了),有的没变(门槛)。读的时候心里要有这根时间线。
一、它站在谁的肩膀上
论文第 3.1.1 节画了一张继承关系图(图 3.1):
Unix v6 ──> xv6 ──┬──> uCore ──> rCore
│ ↑
OS/161 ───────────┘ │
Linux ───────────────────┘
BlogOS ──────────────────┘
三条线汇进来:
- 教学系统那条线:uCore 是清华操作系统课用的教学系统,基于 MIT 的 xv6 和哈佛的 OS/161,还参考了 Linux,2012 年由王乃峥实现。rCore 的命名就说明了初衷——用 Rust 重新实现 uCore。
- BlogOS 那条线:Philipp Oppermann 的 Writing an OS in Rust 系列。rCore 「直接基于 BlogOS 第一版第 10 章的代码完成后续工作」。这一步很关键:BlogOS 已经把最难、最脏的 boot 做完了,还给 Rust 代码建立了一个能跑的执行环境。
- Linux 那条线:后来为了支持真实程序,接口标准整体倒向 Linux。
一个对比目标值得单独提:MIT 的 Biscuit——用 Go 写的完整 POSIX 兼容系统,能跑 Nginx 和 Redis。论文从它那里拿了两样东西:一是「支持 Nginx 和 Redis 所必须的 58 个系统调用」这份清单,直接指明了要做什么;二是一个性能参照——Go 版相比 C 下降约 10%,那么「不需要 GC 和 goroutine 的 Rust 一定可以在性能上做得更好」(1.3 节)。
二、系统架构:类 Unix 单体,但把「低耦合」当真做
rCore 是类 Unix 的单体内核(图 3.2),纵向分三层:
- 平台相关设备驱动(最底):页表、上下文切换、磁盘驱动 IDE AHCI、网卡驱动 E1000/IXGBE;
- 内核:内存管理(物理 + 虚拟)、进程管理(进程管理器 + 线程调度器)、文件系统(VFS + 具体文件系统 SFS)、网络(Socket 接口 + smoltcp + 网卡驱动);
- 用户空间:musl libc 之上跑原生 Linux 程序、Rust std、Busybox、Nginx。
3.1.3 节「低耦合的项目结构」作者的出发点是:Linux 作为世界上规模最大的单体内核,「各模块间藕断丝连,是一个难以触动的庞然大物」,原因之一是 C 语言没有统一的包管理生态,事事都要自己造。rCore 的对手做法有四条:
- 大量使用现成的外部 crate。表 3.1 列了依赖:
log、spin、lazy_static、bitflags、x86_64、pc-keyboard、xmas-elf、linked_list_allocator、smoltcp。作者的原话是「不仅免去了『重复造轮子』的过程,而且由于它们使用人数众多,质量更有保障」。 - 最小化平台相关代码。相比 uCore,每个平台独有的代码量控制在 2000 行以内——「使得移植工作变得更加容易」。
- 子系统模块化,独立发包(表 3.2):
rcore-memory(虚拟内存管理)、rcore-thread(线程调度)、rcore-fs-*(文件系统,进一步分 VFS、SFS 等子项目)、isomorphic_drivers(设备驱动集合,同一份代码可同时在内核态和用户态使用)。 - 共享给别的项目。
smoltcp、isomorphic_drivers都被发到 GitHub 上了。
第 4 条里 isomorphic_drivers(「同构驱动」)的设计(虽然论文只用了3.3.3 节一小段带过):把驱动的底层依赖抽象成 Provider 接口(内存分配、DMA、MMIO 访问),驱动本身只写一遍;在内核里接内核的 Provider,在用户态接 mmap 映射进来的 BAR——同一份驱动代码,内核态能用,用户态也能用。
三、范例拆解:线程调度模块是怎么做成「可复用」的
3.2 节是全篇最完整的一个案例,讲了从概念梳理到接口落地的全过程。
3.2.1 先把概念理清楚
作者先花了一整节梳理线程相关的概念:线程是调度的最小单位、有自己的栈、切换分协作式/抢占式、用户线程与内核线程有一对一/多对一/多对多三种模型(图 3.3)。rCore 选的是多对多:若干内核线程当 worker,用户线程放线程池,worker 独立取活干。理由说得很直接——一对一每次切换都要进内核,开销大;多对一一个线程做系统调用就把全组堵死;多对多「没有阻塞问题,切换又很高效,是现代多线程程序的常用做法」。
这一步是很多人会跳过的:先花篇幅把概念讲清楚,再动手设计。 论文里那句话我印象很深:「想要写好代码,我们首先必须要对目标对象有足够深刻的认识。」
3.2.2 数据结构与接口
结构很小(图 3.4),四个对象:
Processor——代表一个逻辑处理器,每核一个。「我们将其定义为全局变量,以便任何时候都能访问到它」。关键一句:因为每个 CPU 核只能访问自己对应的 Processor 对象,所以它是 CPU-Local 的数据结构,不用加锁保护。ThreadPool——全局唯一的线程池,线程的容器,管理所有线程状态。Scheduler/Timer——调度策略与定时器。Thread——线程对象本身。
切换流程(图 3.5):Processor::run 进循环 → 向 ThreadPool 要一个线程 → 上下文切换过去 → 线程里可以调 yield_now 让出 → 回到循环 → stop 归还。
调度策略被抽成一个 trait,只有四个方法(论文第 21-22 页的代码):
/// The scheduler for a ThreadPool
pub trait Scheduler: 'static {
/// Push a thread to the back of ready queue.
fn push(&self, tid: Tid);
/// Select a thread to run, pop it from the queue.
fn pop(&self, cpu_id: usize) -> Option<Tid>;
/// Got a tick from CPU.
/// Return true if need reschedule.
fn tick(&self, current_tid: Tid) -> bool;
/// Set priority of a thread.
fn set_priority(&self, tid: Tid, priority: u8);
}
注意这个接口的形状:push 传进去的是 Tid(线程编号)而不是线程对象的引用/指针。这是个有分量的取舍——调度器只认编号,不碰对象,于是线程池怎么存线程、调度器要不要并发访问,两件事就解耦了。后面 --features pscnv 之类的多实现只要换 trait 实现即可。
关于加锁,作者的态度很坦然(3.2.2 节末):
由于 ThreadPool 是多线程共享对象,需要对其加锁以实现 Sync 特性。为了提高并发度,我们将其进一步分解成细粒度的锁,也就是 Scheduler、Timer 和每一个 Thread 对象都加了锁。Rust 语言可以坦然面对这种大量细粒度锁的情况,而不会出现 C 语言中忘记上锁解锁的问题。
3.2.3 底层依赖与对外接口
这一节的思路是:设计完模块内部结构后,先问两个问题——这个模块底层依赖哪些函数?对外暴露什么样的接口? 作者的判据是「底层依赖应尽可能少,以提高模块的泛用性;上层接口应在功能完整、易于扩展的前提下,尽量精简并且正交」。
底层依赖被压到只剩一个 trait:
pub trait Context {
/// Switch to target context
unsafe fn switch_to(&mut self, target: &mut Context);
}
上下文切换在不同指令集上完全不同,但在这里「仅使用到了『上下文切换』这一抽象的行为」——所以抽象成一个 unsafe fn 就够了。新建线程时先构造 Context,把它加进线程池;线程退出时 Context 对象被销毁。
销毁即释放——这就是 RAII 的用法(论文明确点名):把线程的所有资源集中到一个 Thread 里,Thread 实现 Context,于是资源生命周期和 Context 对象绑死:
pub struct Thread {
context: ArchContext, // 平台相关的寄存器上下文
kstack: KernelStack, // 内核栈
proc: Arc<Mutex<Process>>, // 共享其所在进程的资源
}
impl Context for Thread { /* ... */ }
对外接口更值得说:直接照抄 std::thread。论文写得很直接——「我们全盘接受了它的设计,将底层接口进一步包装成和 std::thread 一模一样的接口」。于是:
let handle = thread::spawn(|| {
thread::park();
return 1;
}).unwrap();
thread::sleep(Duration::from_millis(10));
handle.thread().unpark();
let ret = handle.join().unwrap();
assert_eq!(ret, 1);
好处是「大幅降低了使用者的学习成本」——裸机环境下就是标准库线程接口,没人需要重新学。 代价是有一个技术难点:spawn 要接受闭包,但新线程需要一个静态的函数入口地址。解法是用泛型技巧为每个闭包创建对应的静态函数,闭包放堆上、指针当参数传进静态函数,新线程进入后先解析参数再调用。
3.2.4 顺带留了个伏笔:和 async 无栈协程的对比
这一小节其实是个「展望」。作者指出无栈协程和线程的代码结构「几乎相同,唯一的区别是切换任务的方式:前者是函数调用,后者是上下文切换(实际上也被封装成函数调用)」,因此二者在任务调度上本质是相同的,进而抛出问题:未来是否可以统一协程和线程的调度框架?在 OS 内部用无栈协程实现 IO 子系统、甚至整个内核系统?
他还注意到 BlogOS 已经开始试着用 async 实现内核线程。这是 2019 年的判断——今天这个方向确实成了 Rust 异步生态的主战场。
四、系统调用层:从 Hello world 到 Rustc 的四级台阶
3.3 节讲怎么让 rCore 跑真实的 Linux 程序。数字先说:rCore 共处理约 130 个系统调用,其中只有 90 个真正实现了功能,剩下的空实现分两种——「直接返回成功」(骗用户程序,用于不影响主要功能的调用)和「直接返回错误」(如 brk 失败后 musl 会改用 mmap)。
方法上最值得学的是「阶梯式目标」:不是一次把系统调用铺满,而是每级定一个目标程序,跑不通就补缺的:
| 台阶 | 目标程序 | 检验什么 |
|---|---|---|
| 1 | Hello world(musl 静态链接) | libc 运行时的最小集合 |
| 2 | Busybox | 文件相关系统调用;sh 还会依赖进程相关调用 |
| 3 | Nginx + Redis | Socket 接口、select/poll IO 多路复用(同时也是和 Biscuit、Linux 做性能对比的载体) |
| 4 | Rustc | 多线程、动态链接、最新系统调用;验证对 Rust 标准库的支持 |
几个具体的坑,值得记下来
坑 1:libc 的选择是个战略决策。 glibc 功能全但「过于复杂,一个从零实现的系统难以支持」;musl 简单且常用于静态链接;Biscuit 自剪的 lite 最简单但不兼容 Linux,会导致后续大量程序的移植成本。论文的选择是 musl,评价是「这是一件一劳永逸的事情」。这一段还有个细节:当时还有第三条路——在用户空间而不是内核里适配,简化 musl 的需求。作者事后说「后面发生的事情大家也知道了,我们十分庆幸当时选择了完全兼容 Linux 的路线」。
坑 2:TLS(线程局部存储)。 这是 musl 运行的必要条件,论文里是靠 strace 分析发现的——拿一个最简单的静态链接 Hello World 在 Linux 上跑 strace,看到 arch_prctl(ARCH_SET_FS, 0x4065f8),再去比 musl 代码,才知道 FSBASE 寄存器被用来存 pthread 的数据结构指针,不设置就会访问 0 地址、段错误。修法分三步:实现 arch_prctl 用 wrmsr 设 FSBASE;中断时保存 FSBASE;把 ELF 头的地址通过辅助向量 AT_PHDR 传给 musl。
「先用 strace 看真实程序要什么,再去实现」——这个手法比我见过的很多『照着 man page 铺系统调用』的做法都实在。
坑 3:futex 有个隐蔽的调用点。 大家都知道 futex 是用户态互斥,但论文点出一个容易漏的地方:Linux 给每个线程规定了 clear_child_tid 属性,线程退出时会隐式地在这个地址上做一次 futex 唤醒。前面 arch_prctl 那段里出现的 set_tid_address 就是来设这个属性的。少了这一下,join 就永远等不到。
坑 4:延迟映射导致的死锁(3.3.4 节末)。 这个案例我觉得是全篇最有代表性的一段工程实录。为了让 Rustc 这种几百 MB 动态库的程序别几分钟都在加载,作者把内存映射改成延迟映射物理内存——不在页表里直接映射,等访问触发缺页异常时再分配。改完之后系统出了一个 Bug:
在内核态处理系统调用时访问用户内存,可能出现缺页异常(这个页还没有被用户访问过),此时系统会重入中断处理函数,尝试获取内存管理对象的锁,而这个对象可能已经被之前的系统调用锁住了,于是就出现了死锁。
试过可重入锁、细化锁粒度都不理想,最后的解法是去翻 Linux 怎么做的:Linux 在内核态访问用户内存时「不进行任何用户地址合法性检查」,而是把这段操作放在一个特殊函数里,期间不锁内存管理对象;如果发生缺页异常且属于非法地址,就修改本次异常时的指令指针,让它返回到一个专门的汇编函数(fixup 函数),由那个函数让原函数返回错误。
论文对这一长串操作的评价是:「这一系列精巧的操作相当于实现了高级语言中的 try { ... } catch (PageFault) { return EFAULT; }。」
而且作者特意说明了为什么把这段写进论文:「为了说明实现新功能往往伴随着新问题的暴露,事实上这种情况在日常开发中经常遇到。」
五、开发经验
1. 迭代式开发,但「快糙猛」要留痕
主旋律是增量式:每次先定一个小目标,然后依次做方案设计、实现、测试、重构。实现新功能和修 Bug 阶段一律用最简单的方案(quick and dirty),作者举的例子是加载用户程序:先立即映射,再改成延迟映射——「这些 dirty 的代码一般都会在不久的将来被重构、重写,甚至彻底废弃。但它们有独特的存在价值,那就是用最快的速度完成目标」。
关键在下一句:快糙猛可以,但要给未来留哨兵——
每个函数签名处必须有描述文档,尚未实现的特性必须写 TODO 或 FIXME,可能出问题的地方也必须用 WARNING 提示出来。它们将为下一阶段的重构标明雷区、指明方向。
2. 测试驱动开发:真正的动力来自「有现成的测试程序」
作者讲得很诚实:测试驱动的效果显著,但实践者普遍坚持不下来,因为写测试这件事是大家都不愿意干的。rCore 之所以能做到,是因为「已经有了现成的测试程序」——每支持一个真实程序(Busybox、Nginx、Rustc),就瞬间解锁了一大批测试用例。论文给了个量化感受:完成 musl libc 适配后,在后一个月里补全了 20 多个文件相关的系统调用,开发进度「高歌猛进」。反过来也说了:当这些目标达成后,没有新目标程序了,开发速度就渐渐慢下来。
3. 遵守行业标准 = 白捡现成资源
作者的论证是:标准落后于技术,所以「事实标准」往往是几年前的东西——而正因为它是几年前的,才白捡到大量现成的资源(文档、测试、对比目标)。
rCore 换了两次「标准」:
- 早期以 uCore 为标准。甚至为 x86_64 实现了 i386 的功能——因为 uCore Lab 只有 i386 版本。代价(做了兼容)换来的是 uCore 自带的 10 余个测试用例,两周实现了大部分测试要求的功能。
- 追平后转向 Linux/POSIX 标准。为的是能跑真实程序。
除了系统调用,接口层面也尽量对齐事实标准:INode 参考标准库里 File 的命名风格、线程模块封装成和 std::thread 一致的接口、用 cargo fmt 统一格式、参考其它 crate 给代码加文档。
4. 面向接口编程——但接口是「归纳」出来的,不是「设计」出来的
这一节有个反直觉的结论:
在实践中我们发现,往往是先有具体实现,然后再抽象出接口。……此外,我们还观察到接口是会随着需求动态演化的。这些事实都说明,接口是自然归纳出来的,而不是刻意设计出来的。
反例也写出来了,很有说服力:作者一度从「缺页置换算法」出发设计了一套页表接口,代码能在用户态顺利跑单元测试,但集成到内核时却困难重重,很多实际需求不知如何在这套接口下实现。
5. 持续推进重构
因为一路 quick and dirty,复杂度积累必然导致腐化,所以重构是必须的。论文给了 rCore 历史上七次大型重构的完整时间表(表 3.5):
| 时间 | 重构内容 | 目的 |
|---|---|---|
| 2018.05 | 统一中断处理函数 | 支持创建新进程 |
| 2018.05 | 统一内存管理器 MemorySet | 支持多进程的虚拟内存管理 |
| 2018.07 | 分离平台无关代码 | 支持跨平台 |
| 2018.10 | 重写线程调度模块 | 支持多核处理器 |
| 2018.12 | 抽象出页表映射策略接口 MemoryHandler | 支持页表延迟映射 |
| 2019.03 | 分离进程和线程对象 | 支持用户态多线程 |
| 2019.05 | 自映射改为线性映射 | 简化页表和 DMA 操作 |
注意每一行的「目的」栏——大型重构的动机都是新功能被现有结构卡住了。 论文写:「一次完整的重构从分析策划到具体实施,再到回归测试,一般要花上几天时间,可以说代价很大。但回报也是巨大的。」最后一句收:
没有人能一开始就设计出最合理的软件架构,架构是根据需求的推动而自然演化的。这样看来,rCore 就像是一个有机生命体,开发者们持续的维护是它活力的根源。
六、能力分析:Rust 写系统的四个维度(第 4 章)
第 4 章不再讲 rCore 本身,而是从 rCore 的实践回过头评 Rust。
4.1 底层控制能力——结论是「和 C 等价」。 论据:unsafe 块里可用裸指针访问特定内存;没有 volatile 关键字但核心库有相关函数、也有第三方封装的 C 风格 Volatile<T>;不支持结构体位域但第三方有 bit_field、bitflags;原子操作用核心库封装的 LLVM 接口,不用手写汇编;可内联汇编,可集成 C/C++ 代码,可与 C ABI 精确对接。另外补了一句:驱动这类「大量直接访问内存、全部使用 C ABI」的场景,还是用 C 语言更合适——这个自认边界的态度挺难得。
4.2 抽象与优化能力——重点在「强优化带来的三个坑」。 作者先夸 Rust 抽象几乎零开销(甚至能内联虚函数),紧接着说:
强大到激进的优化策略也会带来一些问题。尤其是编写 unsafe 代码时,很容易错误地打破编译器假设,导致优化后出现未定义行为。
三个坑:
- 内部可变性:对象的不变方法(
&self)改了内部数据(常见于互斥锁、环形缓冲)。Rust 规定所有内部可变的数据最终都要包一层UnsafeCell,否则「编译器看到不可变就可能把所有写操作优化掉」。 - 数据合法性:Rust 规定所有类型的变量在任何时刻都有合法值。缓冲区数组这种「以未初始化状态诞生」的场景必须正确使用
MaybeUninit。另外——即使是在unsafe代码里,也不能同时出现两个可变引用指向同一个数据,这违反&mut的语义。 - 栈对齐:比较隐蔽但危害很大。每种指令集的函数调用 ABI 对栈指针对齐有不同要求,编译器会基于栈对齐假设对栈上变量的访问做优化;栈没对齐会导致隐蔽的数据读写错误。所有需要手动控制栈指针的地方都要注意——boot 后的入口函数、上下文切换、中断帧保存。
4.3 平台适配能力——Rust 后端的短板。 Rust 后端是 LLVM,广泛支持各指令集,但相对 GCC「对小众平台的支持还是普遍落后一些」。具体例证两个:RISC-V 支持到 2019 年初才基本可用(rCore 几乎第一时间用上新特性,同时也踩了很多坑);更具体的是 RISC-V 的原子变量最小是 32 位,而核心库里 AtomicBool 是 8 位,最后不得不手动改库代码才能实现正确的原子操作。
4.4 高质量开发能力——好处与门槛是一体两面。 好处:「我们几乎没有遇到 C 语言中常见的内存安全问题,在多核物理机上也没有遇到任何数据竞争问题。在合理使用 unsafe 的前提下,只要编译通过,我们就能放心地认为代码不会出太大的差错。」剩下的就是纯逻辑问题,而 Rust 会在运行时及时把它们暴露出来(unwrap 一个 None、越界访问会立刻 panic);并发问题上,由于强制给共享对象上锁,所有数据竞争都转化成了死锁,更容易定位和解决。
门槛那一段回到开头提的第 4 条:初学者写不出能过编译的代码,就会用 unsafe 或把引用转成裸指针/整型来绕开生命周期约束,结果完全丧失了 Rust 保证安全的好处,代码退回 C 风格还写得十分啰嗦。论文的结论是这个问题「由内存安全问题固有的难解性决定」,Rust 已经给出了历史上最好的解决方案,但门槛问题有待未来探索。
七、项目对照
我在做的是另一件事:把一套 C/C++ 写的 GPGPU 运行时(gdev)翻译成 Rust,要求产出的 .so 换掉原版后用户程序可以使用。方向上有区别,但读这篇时有好几处对上了。
1.「接口是归纳出来的,不是设计出来的」——这条我踩过反面。 论文里那个「从缺页置换算法出发设计的页表接口,用户态单测得挺顺、集成到内核就崩」的反例,几乎就是我这边「照 C++ 继承关系直译出 trait、结果越写越别扭」的翻版。反过来,v1 里那些「先照抄 C 的行为、跑通再说」的地方,最后确实自然收敛成了干净的 Rust 接口。
2. quick and dirty 但必须留哨兵——这和我们的 PITFALLS 是同一个东西。 论文要求「每个函数签名处必须有描述文档,尚未实现的特性必须写 TODO/FIXME,可能出问题的地方必须 WARNING」。我们这边的对应物是一份专门记录踩坑的文件,外加每个任务一份 spec。两边解决的是同一个问题:快糙猛产生的债,必须有人在原地插旗,否则下一个接手的人(往往就是三个月后的自己)会重新踩一遍。
3. 那七次大型重构的时间表,是「架构演化」最有说服力的证据。 每一行的动机都是「现有结构卡住了新功能」。我们这边是「一任务一提交、测试全绿才提交」,好处是任何一次重构都有回退点——但论文提醒的是另一半:重构的触发条件不是「代码不好看」,而是「结构挡住了下一个功能」。
4. 关于 unsafe 的三个坑,我的场景里全都真的出现了。 内部可变性、MaybeUninit 之外,最有共鸣的是栈对齐那条——我们做的是用户态运行库,不碰 boot 和中断帧,但上下文切换这类活(ring buffer 提交、命令流编码)一样对内存布局和访问方式极其敏感。论文那句「编译器会基于栈对齐假设对栈上变量的访问做优化」值得贴在墙上。
5. 最根本的一条:这篇论文和我的项目在回答同一个问题的两面。 它问的是「用 Rust 从零写,能写成什么样」;我们问的是「已经写好的 C/C++,用 Rust 重写一遍能不能一模一样」。它的结论(Rust 更适合写操作系统)只有在能用 Rust 的抽象重新设计这个前提下才成立;而我们的项目里,有地方不许做那个重新设计——因为 C 那边的行为就是验收标准。这两件事不矛盾,但确实是两种纪律。先搞清自己在哪一种纪律下干活,比争论哪种语言好重要得多。
附:论文的量化信息一览(便于引用)
| 项 | 值 | 出处 |
|---|---|---|
| 支持指令集 | x86_64、RISC-V 32/64、ARM64、MIPS32 | 表 1.1 |
| 可运行程序 | Busybox、GCC、Nginx、Redis、Rustc | 1.2 节 |
| 系统调用 | 约 130 个,其中 90 个真正实现 | 3.3 节 |
| 平台相关代码 | 每平台 2000 行以内 | 3.1.3 节 |
| 外部依赖 crate | 9 个主力包(表 3.1) | 表 3.1 |
| 独立发布的子系统 | rcore-memory、rcore-thread、rcore-fs-*、isomorphic_drivers | 表 3.2 |
| 大型重构 | 7 次(2018.05 ~ 2019.05) | 表 3.5 |
| 对比参照 | Go 写的 Biscuit(性能下降约 10%) | 1.3 节 |
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)