QRV 是对 QNX Neutrino 6.4 操作系统 的一种从底层进行的适配和重新实现,面向现代 64 位硬件,以 RISC-V (rv64g) 为主要架构,x86-64 为次要目标。该项目始于 2020 年圣诞夜。名称 QRV 刻意避免与 QNX 商标产生任何关联。
QRV 是一个 完整的操作系统,而不仅仅是一个内核。微内核是其核心——但大部分工作都投入到了围绕它的一切之中。最引人注目的是 taskman,即用户态进程/内存/路径管理器(QNX 的 procnto),它被深度重写并提升出内核,移入用户空间;与之相伴,C 库、设备驱动程序、文件系统、动态加载器和系统服务器均已移植,实现了 64 位清洁化,并且在许多地方进行了实质性的重写。微内核的设计很小;QRV 的主体是围绕它的操作系统。
这不是一个仅仅在新编译器上编译旧代码的分支。这是一个逐模块仔细移植到真正的 LP64 模型的过程,拆除了专有的 procnto 边界,替换了 IFS/启动机制,并且——在最近的版本中——完全移除了内核的大内核锁,并将进程/内存/路径管理器移出内核,进入一个用户模式服务器。
本 README 描述的是 QRV v0.43。
开发博客(包含移植的完整故事)位于 **https://r-tty.blogspot.com**。一份书籍长度的叙述,《QRV 移植故事》,存在于源代码树的 doc/tex/PortingStory/ 下。
QRV 是在与 Claude Code(Anthropic 的智能编码工具)紧密合作下开发的——许多移植、SMP 调试和文档(包括本 README)都是以人-AI 结对编程的方式进行的,与作者并肩工作。
obtain_proj.sh 与 os/ 树TM_PRIV 特权系统调用devb-nvme 与 fs-qrvQNX 是一个 微内核 实时操作系统,其核心理念是 同步消息传递。在 QNX 中,内核本身非常小——它知道如何调度线程、传递消息、发送信号、处理定时器和中断,除此之外几乎不做其他事。一个单体内核会放在 内核内部 的所有东西——进程管理器、内存管理器、文件系统、设备驱动程序、网络栈——都在普通的用户进程(称为 资源管理器)中运行,它们通过同样的 发送 / 接收 / 回复 IPC 原语相互通信以及与客户端通信。
这种架构正是 QNX 优雅的原因,也是 QRV 所保留的。一个想要打开文件的程序发送一条消息;文件系统服务器接收它,完成工作,然后回复。内核只负责协调会合。结果是:一个驱动程序可以崩溃并在不关闭内核的情况下重新启动,可信计算基以数十 KB 计,而“内核”与“应用程序”之间的边界是一条消息,而不是一个充满系统调用的特权墙。
QRV 采用 2009 年时期的 QNX Neutrino 6.4 社区源代码,并将该设计向前推进:
int/uint32_t/pid_t 在指针上的截断问题。qemu-system-riscv64(virt 机器)和 SiFive Unmatched (FU740) 开发板。x86-64 保持可构建,作为可移植性检查。mkifs(QRV 改用标准的 CPIO 格式);没有独立的启动/内核拆分(启动代码直接与内核链接);没有 callout 和迷你驱动程序。Kconfig 配置、增量内核链接(模块逐个添加和测试,而不是一次性扔给链接器进行 32→64 转换),以及交叉编译器工具链(riscv64-linux-gnu-gcc)。procnto 在整个代码中变为 taskman(任务管理器);所有“Neutrino”引用均被清除。QRV 不 实现 fork()(程序通过 posix_spawn() 启动),并且 没有请求分页和交换空间——这与 QNX 在其 8.0 一代中做出的选择相同。
QRV 受 两个许可证同时管辖,理解它们的区别在您构建或重新发布任何东西之前至关重要。
QRV 自身的代码采用 Apache License 2.0。 本项目从头编写的所有内容——RISC-V 移植、新的构建系统、用户态 taskman 拆分、无锁内核重写、我们创作的驱动程序和工具——均为 Apache 2.0。全文见 LICENSE.txt。
QNX 衍生代码采用 BlackBerry QNX Community License (QCL) 2.0。 QRV 中源自 2009 年 QNX Neutrino 社区源代码的部分仍受 QCL 管辖,该许可证仅允许对衍生源码进行 非商业和学术使用。QRV 不会,也不能重新许可 QNX 的代码。
这种双重许可的现实正是 为什么此仓库不包含一个可直接构建的源码树 的原因。我们不被允许重新分发 QNX 衍生源码。因此,本仓库不提供代码,而是提供一个 配方(见下一节):一张每个 QNX 文件去往何处的映射,加上用于转换它的 QRV 补丁。您自行从公共镜像获取上游的 QNX 社区源代码,配方会在您的机器上重建 QRV 树。您的副本归您所有;我们仅重新分发我们自己拥有 Apache 许可的补丁和元数据。
QRV 还包含了在其他宽松许可证下的代码——例如从 FreeBSD 采用的 BSD 许可组件(用于替换老旧的 QNX 模块)、一个源自 xv6 的 MIT 许可 virtio 块驱动程序,以及作为系统 shell 的 MirBSD Korn shell (mksh)。WHAT_IS_WHAT.md 是权威的、按组件细分的许可证和来源说明——当您对某个特定文件或子系统不确定时,请查阅它。
最后,仓库中包含 PETITION.md:一份向 QNX Software Systems 和 BlackBerry 发出的公开请求,希望将历史上 2007–2009 年的 Neutrino 源代码在经 OSI 批准的宽松许可证下重新发布。如果您希望这项工作的基础有一天能完全自由,该文档就是您可以加上自己签名的地方。
obtain_proj.sh 与 os/ 树由于 QNX 衍生源代码无法在此重新分发,此仓库是一个 源码重建分发版。它包含:
$ ./obtain_proj.sh
1. **克隆上游 QNX 社区镜像** (`github.com/vocho/openqnx`,
浅克隆)。
2. **放置文件** 根据 `placement.txt`,将每个上游
文件复制到其在 `os/` 下的 QRV 位置。它会报告放置、
已存在或缺失的文件数量。
3. **删除克隆** 在放置完成后。
4. **应用 QRV 补丁系列** 按顺序从 `patches/series` 应用。每个
补丁是 LZ4 压缩的 (`*.patch.lz4`),并使用
`lz4cat … | patch -p1` 应用。补丁带有当前
发布的版本。
5. **设置可执行权限** 针对少数需要此权限的脚本
(例如 `emu.sh`、`host_tools/mkgpt.py`)。
所需的工具是 `git`、`patch` 和 `lz4cat` (来自 `lz4`
软件包);脚本会提前检查它们,并告诉您如何安装
任何缺失的工具。
最终产物是 **`os/`** —— 一个完整、可构建的 QRV 源代码树:```
os/
├── kernel/ Everything linked into the qrv-kernel binary
│ ├── arch/riscv/ RISC-V port: vectors, traps, SBI, context switch,
│ │ ├── startup/ arch-specific startup (head.S, mmu.c, …)
│ │ ├── platform/ qemu_virt/, unmatched/
│ │ └── include/ context.h, cpu_paging.h, sbi.h, …
│ ├── startup/ arch-independent startup (hardware_init, smp, …)
│ ├── nano/ core nanokernel: messaging, scheduling, sync, xfer
│ ├── kext/ kernel extensions (kerexts)
│ └── include/ kernel-internal headers
├── taskman/ The Task Manager (QNX's "procnto"), a user-mode server
│ ├── sys/ system manager: main, ELF loader, support
│ ├── proc/ process manager: spawn, wait, …
│ ├── mem/ memory manager: page tables, physical allocator
│ │ └── pageman/ page-granularity virtual-memory operations
│ └── path/ path manager: namespace, /dev/*, /proc/*
├── lib/ C library and runtime
├── include/ User-space-visible headers (the public ABI)
├── userland/ Shell, utilities, drivers, servers (resource managers)
├── servers/ pci, slogger
├── dev/ Device drivers (virtio block, 8250 UART, …)
├── boot/ Boot artifacts and deploy helpers
├── host_tools/ Host-side tooling (mkgpt.py, …)
├── doc/ Documentation, incl. The QRV Porting Story (LaTeX)
├── Kconfig, Makefile, def.mk, common.mk, emu.sh
从那里,cd os && make 构建系统(参见 §9)。
QRV 是一个真正的微内核系统。在 RISC-V 上,从硬件向上看,特权栈如下所示:``` ┌───────────────────────────────────────────────────────────────────────┐ │ User space (U-mode) │ │ │ │ sh pidin lspci sloginfo login ... │ │ │ │ │ │ │ │ │ devb-nvme fs-qrv devc-ser* pci slogger ← resource managers │ │ │ │ │ │ │ │ │ │ └──────┴───────┴────────┴─────────┴───────┘ │ │ │ libc (send / receive / reply stubs) │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ taskman — process · memory · path manager │ │ │ │ a U-mode server, privileged via TM_PRIV │ │ │ └─────────────────────────────────────────────────────────────────┘ │ └────────────────────────────────────│──────────────────────────────────┘ │ ecall (syscall / message) ┌───────────────────────────────────────────────────────────────────────┐ │ QRV microkernel (S-mode) │ │ │ │ message passing · channels & connections · scheduling · │ │ threads · synchronization · signals · timers & clocks · │ │ interrupts · syscall dispatch · TM_PRIV kernel extensions │ └───────────────────────────────────────│───────────────────────────────┘ │ SBI ecall ┌───────────────────────────────────────────────────────────────────────┐ │ OpenSBI firmware (M-mode) │ └───────────────────────────────────────│───────────────────────────────┘ │ ┌───────────────────────────────────────────────────────────────────────┐ │ RISC-V 64-bit hardware — QEMU virt · SiFive Unmatched U740 │ └───────────────────────────────────────────────────────────────────────┘
**内核(S模式)** 是唯一在经典意义上运行在特权级的组件。其子系统小巧而专注:
- **消息传递** — `ker_message.c`, `ker_fastmsg.c`, `nano_message.c`
- **通道与连接** — `ker_channel.c`, `ker_connect.c`
- **线程与调度** — `ker_thread.c`, `ker_sched.c`, `nano_sched.c`
- **同步** — `ker_sync.c`, `nano_sync.c`
- **信号** — `ker_signal.c`
- **定时器与时钟** — `ker_timer.c`, `ker_clock.c`
- **中断** — `ker_interrupt.c`
- **系统调用分发** — `ker_call_table.c`
- **数据传输** — `nano_xfer*.c` 系列(跨地址空间拷贝引擎,安全地在进程间移动消息负载)
引导程序与内核之间的接口是 **syspage**(`include/sys/syspage.h`);每个 CPU 的状态位于 **cpupage** 中;完整的寄存器上下文是 `RISCV_CPU_REGISTERS`。在 RISC-V 上,每个“CPU”始终通过其 **hart ID** 标识——整个系统存在一个统一的 CPU 命名空间。
**其他一切均为用户进程。** 进程/内存/路径管理器、块设备和串口驱动、文件系统、PCI 服务器、系统日志记录器——全部都是通过发送消息访问的资源管理器。内核不包含文件系统;它只包含让一个进程请求另一个进程 *扮演* 文件系统的能力。
---
## 5. Taskman 和 `TM_PRIV` 特权系统调用
**`taskman`** 是 QRV 对 QNX 中称为 `procnto` 的组件的称呼:它集成了**进程管理器、内存管理器和路径(命名空间)管理器**。在经典的 QNX 系统中,这段代码被融合到内核镜像中。QRV 较大的结构成果之一是 **taskman 现在运行在用户模式**——它是一个普通的 U 模式进程,而不是特权内核的一部分。
这就引出一个明显的问题:如果 taskman 位于用户空间,它如何执行进程和内存管理器必须执行的深度特权操作——操作页表、分配物理内存、创建和销毁地址空间、传递信号和脉冲、销毁进程?
答案是单个严格控制的网关:**`__KER_TM_PRIV`**,系统调用槽位 2。它是 taskman 进入内核特权操作的*唯一*入口,并且在该单个槽位背后是一个包含**约 98 个子操作**的分发表——这些是小的**内核扩展**(“kerexts”),每个执行一个明确定义的特权操作并返回。几个有代表性的家族:
- **进程生命周期** — `PROCESS_CREATE`, `PROCESS_EXEC`,
`PROCESS_DESTROY`, `PROCESS_STARTUP`, `PROCESS_SHUTDOWN`, `REPARENT`
- **物理与虚拟内存** — `PA_ALLOC`, `PA_FREE`,
`PA_QUANTUM_TO_PADDR`, `PAGE_CONT`, `STACK_CONT`, `ASPACE_MEMCPY`
- **凭据与限制** — `CRED_GET`, `CRED_SET`, `LIMITS_GET/SET`
- **传递与对象** — `PULSE_DELIVER`, `SIGNAL_DELIVER`,
`QUERY_OBJECT`, `CHANNEL_DESTROY`, `CONNECT_DETACH`
- **SMP 与平台** — `SMP_BRINGUP`, `LEGAL_CPU_MASK`, `GET_KERN_PGDIR`,
`ICACHE_SYNC`(v0.43 中新增的 RISC-V 指令缓存一致性操作)
这种设计使得**可信计算基础保持较小**——实际内核保持最小化——同时给予 taskman 它所需的精确特权原语,并且**不多不少**。关键的是,内核堆和其他内核内部结构仍对 U 模式不可访问(内核页保持 `PTE_U=0`);taskman 通过拷贝入/拷贝出的 kerexts 完成工作,绝不会被授予原始内核指针。枚举定义位于 `kernel/include/ker+tm/tm_kercalls.h`;分发表位于 `kernel/ker_tm_priv.c`。
---
## 6. 大内核锁——及其移除
早期的 QRV——如同它在多处理器系统上继承的 QNX 一代——使用单个**大内核锁(BKL)**保护内核:一个全局的 `inkernel` 字,每次只允许一个 hart 进入内核。无论有多少 CPU 在运行,每个系统调用和每个 taskman 消息都通过该锁串行化。正确、简单——但对 SMP 可扩展性造成了硬上限。
**自 v0.42 起,BKL 已被移除。** 这曾是漫长候选发布序列的标题,也是《QRV 移植故事》第 9–14 章的主题。系统调用和 taskman 消息现在在**跨 hart 并发运行**,使用细粒度的、每个对象的锁:
- **每对象锁定。** 每个 `tChannel` 锁和 `tConnect` 锁保护消息队列;每进程 `vec_slock` 保护线程向量;每分发 `sched_slock` 保护运行队列;`alloc_slock` 保护内核堆。
- **无锁消息传递。** `MsgSend` / `MsgReceive` / `MsgReply` 以及 `Sync*` 家族**根本不获取全局锁**。跨 hart 的 rendezvous 通过每个线程的桥接位和硬件内存屏障协调,而非通过互斥。
- **SMR(RCU 等效)回收。** 线程、连接和通道通过安全内存回收机制退役,使得无锁查找永远不会解引用已释放的对象。
在 QEMU `virt` 上使用 `-smp 8`,内核可靠地启动到 `login:` 提示符,并能够承受 300 次迭代的 `pidin` 压力循环而不会停滞。
---
## 7. 存储:`devb-nvme` 和 `fs-qrv`
与微内核模型一致,QRV 中的存储由**两个协作的用户进程**构成,而非内核子系统:
- **`devb-nvme`** — 块设备驱动。它通过 PCIe 使用 NVMe 协议(内置 GPT 分区解析),通过 PCI 服务器发现控制器,并提供诸如 `/dev/nvme0n1` 及其分区的块设备。(还有一个 `devb-virtio` 兄弟驱动,用于模拟目标上的 QEMU virtio-blk 设备。)
- **`fs-qrv`** — 文件系统资源管理器。它挂载分区,并通过消息传递提供 POSIX 文件系统命名空间服务:应用程序的 `open`/`read`/`write`/`close` 变为由 `fs-qrv` 应答的消息。
典型的启动过程会挂载一个真实的 NVMe 分区并从该分区运行程序:```
mount -t qrv /dev/nvme0n1p5 /disk2
这条路径——块驱动程序、分区、文件系统服务器以及内核在它们之间传递每条消息——在QEMU和SiFive Unmatched的NVMe驱动器上都能够端到端运行。
QRV通过sysinit/init引导,启动串口控制台驱动程序(QEMU上为devc-ser8250,FU740上为devc-sersifive)、PCI服务器、存储栈以及getty/login,最后进入shell提示符。主要的用户空间程序有:
sh——mksh,MirBSD Korn shell。 一个真正的、可脚本化的POSIX shell作为系统shell;启动脚本(level1.sh等)是普通的shell脚本。pidin——经典的QNX“进程信息”工具:列出进程、线程、它们的状态、内存等。它是QRV的主要“系统是否正常运行?”探针以及标准压力测试工作负载。lspci——通过PCI服务器枚举PCI/PCIe总线。sloginfo——转储由slogger服务器收集的系统日志。除了这些,还包括一个可用的多用户系统的构建模块:getty和login(带有凭据/认证支持)、mount、shutdown、pipe以及核心工具(ls、cat等)。每个QRV程序都是多线程的——至少有一个主线程和一个系统线程——这正是正确的SMP同步(参见§6)如此重要的原因。
Cross-compiler : riscv64-linux-gnu-gcc CPU flags : -march=rv64g -mcmodel=medany -mno-relax Build style : -nostdinc -nostdlib -ffreestanding
### 常用命令(在 `os/` 内运行)```bash
make # Build everything: startup + kernel + module package
make -Bj # Force a full parallel rebuild (do this after header changes)
make startup # Build startup only
make kernel # Build kernel only
make modpkg # Create the module package (CPIO)
make qemu # Build and run in QEMU
./emu.sh # Run in QEMU (4 CPUs, 256M RAM, virt machine)
./emu.sh -P 1 # Run with a single hart
./emu.sh -gdb # Run with the GDB remote stub (port 1234)
QRV的次要但严肃的目标是 SiFive Unmatched (FU740) —— 一款真正的RISC-V工作站主板。让一个在QEMU上干净启动的微内核也能在物理硅片上干净启动,暴露了一类仿真完全不会表现出来的错误,而追逐这些错误正是最近几个版本大部分工作的核心。
一个典型的例子,在 v0.43 中修复:两个月来,QRV在QEMU上运行完美,但在真实硬件上却 出错。在FU740上,任何程序——pidin、lspci,任何东西——都会在几次spawn后崩溃,每次崩溃各不相同,程序计数器游走到垃圾地址。原因根本不是内存损坏,而是指令缓存不一致:RISC-V不保证数据存储与指令获取之间的一致性,因此新加载的程序代码对hart的取指单元是不可见的,直到该hart执行fence.i——而将在不同hart上运行的代码需要在该hart上执行远程fence.i。QRV的加载器两者都没做(它甚至计算了一个“无效化I-cache”标志,然后将其丢弃)。QEMU没有模拟指令缓存,因此这个错误在那里是不可见的,而在U74上则是确定性的。
修复方案——在每一页变为可执行的地方,执行本地加上SBI广播的fence.i(cpu_icache_sync_all())——将一个每运行几次就出错的spawn循环,变成了在FU740上连续600次spawn干净运行。完整的调查过程,包括最初产生的错误假设以及将其推翻的诊断,是《QRV移植故事》第14章的结尾部分。
到目前为止达到的硬件里程碑包括:在FU740上启动到用户态任务管理器中的login:提示符,以及从一个真实的NVMe分区挂载并运行测试程序。
QNX是有史以来最受影响的微内核设计之一。它的发送/接收/回复模型教会了一代又一代系统工程师,一个干净的OS架构可以是什么样子。然而,体现这一设计的代码却在一种奇特的困境中度过了十多年:在社区许可下足够可见以供研究,但又不够自由以至于无法重新分发、演进或构建一个鲜活的生态系统。这样一个优秀的设计不应仅仅作为只读文物被保存。
这就是本工作存在的原因。坦率地说,QRV只是一个过渡性的工具——一种通过移植它来深入学习架构的方式,通过拆解并在新硬件上重组,通过移除内核锁并将进程管理器提升到用户空间,从而发现哪些假设是承载性的。每一个在真实硅片上追踪到的错误,每一个被重写为64位清洁的子系统,每一个被拆除的专有边界,都是真正自由实现所需要的知识。
因为长期目标不是永远维护一个别人源码的修补副本。而是一个完全自由、从头编写的操作系统,与QNX的接口兼容,并忠实于其微内核哲学,但不欠专有代码任何东西——一个可以在不征求任何人许可的情况下使用、教学、分发和改进的系统。QRV是我们证明这样一个系统不仅可能而且实用的方式,也是我们积累构建它的正确经验的方式。
如果你希望历史基础本身变得自由,请在 PETITION.md 中添加你的名字。如果你希望从内部看到一个现代的、诚实设计的微内核是什么样子——克隆配方,运行 obtain_proj.sh,然后阅读代码。
QRV — 为64位硬件适配并重新实现QNX Neutrino 6.4。始于2020年圣诞夜。Apache 2.0(QRV自有代码)+ BlackBerry QCL 2.0(QNX衍生源码)。组件划分详见WHAT_IS_WHAT.md。
| 文件/目录 | 用途 |
|---|
obtain_proj.sh | 重建脚本——运行它。 |
placement.txt | 将每个上游 QNX 路径映射到其 QRV 路径(约 680 条记录)。 |
patches/ | QRV 补丁系列,LZ4 压缩,以及一个 series 顺序文件。 |
LICENSE.txt | Apache License 2.0。 |
WHAT_IS_WHAT.md | 按组件细分的许可证和来源说明。 |
PETITION.md | 重新许可证请愿书。 |