2026-08-21。本文评估 mcpp 及其生态在裸机方向的现状,分七个角度。
本文区分三类陈述,并逐条标注:
- 实测 —— 本次或既往会话中跑出过输出的;
- 读码 —— 从
src/的表与分支读出的,未单独跑验证; - 推断 —— 由前两类推出的判断,可能被后续测量推翻。
目标表(src/toolchain/triple.cppm)当前十三行,其中裸机四行:
| Triple | Tier | 目标 C 库 |
|---|---|---|
riscv64-none-elf |
verified | xim:picolibc-riscv |
riscv32-none-elf |
verified | xim:picolibc-riscv |
aarch64-none-elf |
preview | 无 —— 零 libc 层 |
x86_64-none-elf |
preview | 零 libc 层 |
后两行的 C 库列为空,而空列的语义与清单里 sysroot = "" 完全相同。由此得到一条
设计性质:
⭐ 「没有 C 库」与「选择某个 C 库」走同一套机制。 一个工程针对这两个目标时不必 声明任何东西就落在零 libc 层;要 C 库就声明一个,而那与「换一个别的 C 库」是同一个 动作。缺省不是特例。
| 方面 | 裸机目标上的行为 |
|---|---|
| 链接行 | -nostdlib -nostartfiles -static,无 crt、无动态链接器、无 C++ 运行时 |
| 链接器选择 | ld.lld 按绝对路径寻址,路径由驱动自身目录推出 |
| ISA 参数 | -march / -mabi / -mcmodel 来自一个目标一行的表,故 --target <triple> 单独即足够 |
| 异常与 RTTI | 图中每个翻译单元都关掉,包括依赖的 |
import std |
不可用,且在 configure 期拒绝并给出替代 |
| 默认链接方式 | 静态 —— 没有加载器,所以没有第二种选择 |
⭐ 「删减」与「构造」的差别是可观察的。 从宿主链接行删减会漏,而漏掉的那条宿主假设 在链接期才现形,表现为一堆未定义符号。从零构造则要求每一项被显式加入,遗漏在配置期 就是缺失。
-fuse-ld=lld 按名字经 PATH 解析,而绝对路径不。 这与 openarch 的构建程序按绝对
路径要模拟器是同一条纪律:一个按名字解析的东西,解析到哪取决于机器上装了什么。
x86_64-none-elf 落地时,该表新增两列(extra、lldEmulation),因为:
- clang 的 BareMetal 工具链只覆盖 arm / aarch64 / riscv,x86_64 落到通用 GCC 工具链,
链接经宿主
g++—— 六个裸 triple × 四个 flag 实测确认; -mno-red-zone在中断可以踩栈的机器上不是偏好而是目标的属性。
⭐ 这两条都不是「多加一个 case」,而是表的列不够。一个抽象是否成立,看的是新增一行 要不要改表的形状。
import std 在裸机目标上给出的是解释加替代方案,而不是链接期的符号墙。缺失 runner 给
出的是「构建成功而执行不可能」之间的那条缝,并附可粘贴的 runner 键。
生态里有一条从「机器机制」到「可引导镜像」的完整链:
| 层 | 包 | 版本 | 提供什么 |
|---|---|---|---|
| 机器机制 | openarch |
0.5.1 | 执行上下文、陷入、地址空间、per-CPU、屏障 |
| 语言子集 | std-freestanding 及其三个分配半 |
0.5.0 / 0.2.0 / 0.1.0 | 不需要 OS 的那部分标准库 |
| 内核抽象 | openkal |
0.5.2 | 内核抽象层 |
| 固件侧 | openkal-opensbi / openkal-uefi |
0.1.0 | SBI 与 UEFI 两条启动路径 |
| 板级 | riscv-virt-rt |
0.5.1 | qemu virt 板的运行时 |
| 模拟器 | qemu-riscv / qemu-arm / qemu-x86 |
9.2.4-1 | 三个目标族各一个 |
实测:mcpp new mykernel --template openarch 生成的工程在 riscv64 / aarch64 /
x86_64 三台机器上 mcpp run 全部打印 switch ok。
openarch 的判据是一份探针源码在三台机器上输出逐字节相同。这条判据的力量来自第三
台机器:
riscv64 与 aarch64 都是定长指令、弱内存序的 load/store RISC 机器,一个同时适配两者 的接口,分不清它是对的还是它们像。x86_64 变长指令、TSO(四条屏障里三条不需要任何 指令)、中断机制是 256 项的门表而不是一个基址寄存器。三台都活下来的才是抽象。
第三台机器实测挖出三条另外两台不可能暴露的事实:IA32_PAT 的复位值让一处早期设备映射
被当作可缓存而不报错;pc 在故障与陷阱两类事件里分别指故障地址与下一条指令;NX 是
单级的,所以规则必须由 CR4.SMEP 承担。
时钟归属的调研是最清楚的一例。三台机器都能用一条无地址指令读到单调递增的计数器,只有
一台能说出它走多快(aarch64 的 cntfrq_el0;riscv 架构不报告;x86_64 的 CPUID leaf
0x15 拒绝)。
⇒ counter() 属于本层,frequency() 与 set_deadline() 不属于。
⭐ 先写接口会得到三件一套,其中两件在三台里的两台上无法兑现 —— 而那种接口的失败方式 是「在一台机器上写得出、在另一台上写不出」,只会在第二台机器被加进来时才暴露。
- 页表遍历 —— 造一个表项是机制,决定表项放哪是策略,属于内核而不是本层;
- 单 ISA 的第二个后端 —— 结构已支持(
backend-riscv64命名的是后端而非架构),但 尚未有;riscv 会需要它,因为现有后端陷入 M 态,而 SBI 之下的内核陷入 S 态。
riscv32-none-elf与riscv64-none-elf是 verified 级,且索引里有目标 C 库 (xim:picolibc-riscv);- 产物集面向烧录:
docs/13-baremetal.md有专节; - 板级包模型成立:板级包在索引描述符的平台
deps里声明模拟器/工具链,而那些 是随包安装的 —— 这是普通裸机工程不会踩到「声明≠安装」的原因。
mcpp: 指令表有 action / cfg / cflag / cxxflag / generated / graph / include / link /
protocol / rerun / runner / source,没有 warning。而 mcpp 用 capture_exec 抓构建
程序,只在非零退出时打印抓到的东西。
后果(实测):清单里的 [xlings] deps 是声明不是安装触发器,干净机器上
mcpp::xpkg_dir 返回空 → 构建程序静默地不配 runner → mcpp run 报「没有 runner」并建议
写一个 runner 键 —— 这句话一般情况下对,在这里不对,因为工程有 runner,只是条件性
的。而构建程序无法说出这件事:写一条 std::cerr 提示实测后被删掉,因为它在最需要它
的那些构建上一个字都不打印。留着比不留更糟,因为它看起来像个修复。
⇒ 目前唯一到得了用户的通道是 README。这是一个真实的引擎缺口,而不是文档问题。
aarch64-none-elf 与 x86_64-none-elf 的 C 库列为空,一半是设计(零 libc 层),一半是
索引里没有这两个架构的 picolibc 构建。想在这两台机器上用 C 库的工程,目前要自己解决
目标 C 库。
十三个目标,六个 verified(含两个裸机),三个 planned,两个裸机 preview。索引服务五个 宿主(linux x64/arm64、darwin x64/arm64、win32 x64)。
宿主的 clang 是拿什么标准库编出来的,不参与一次交叉编译。
Windows 的 LLVM 载荷把 clang 编译在 MSVC 标准库上、不带 libc++。此前这被记为「Windows 不支持 freestanding」。实测(Windows runner,载荷自己的 clang):
── MSVC STL, for a bare-metal target ──
t.cpp:1:10: fatal error: 'array' file not found
── MSVC STL, for its own target, as a control ──
(无诊断)
⭐ 那些头从来没被读到。 clang 只在目标是 MSVC 目标时才搜索 MSVC 标准库。 ⇒「用 MSVC STL 服务裸机目标」不是一件会失败的事,是一件驱动从不尝试的事。
同样 40 个 C++23/26 freestanding 强制头,libstdc++ 16.1.0 + -D_GLIBCXX_HOSTED=0,
只改 target:
x86_64-linux-gnu 40 / 40 编过
riscv64-none-elf 0 / 40,全部倒在
bits/c++config.h -> bits/os_defines.h -> 'features.h' not found
⇒ 判据不是「哪个实现」,是「那份实现有没有为这个目标 configure 过」。 libc++ 的逐
目标配置是一个扁平宏文件(__config_site),可以合成;libstdc++ 的是 configure 输出,
会把宿主 C 库拽进来。
解法是目标的 C++ 标准库应当像目标的 C 库一样解析:载荷带就用载荷的,不带就用一个
包(xim:libcxx-headers)。Windows 那一行因此从「断言缺口」变成真正构建。
| 动作 | 结果 |
|---|---|
mcpp new x --template openarch |
三个目标的工程,含链接脚本与板级源 |
mcpp run --target <triple> |
板级包或构建程序供 runner 时直接在模拟器里引导 |
mcpp build --target <triple> |
ISA 参数、链接行、目标 C 库全部由 triple 推出 |
| 失败 | 命名诊断 + 可粘贴的修法,而不是原始工具输出 |
一个无依赖工程仍能构建(Size norunner text 12),这是「ISA 表一行即足够」的证据。
见 §3.2。用户第一次在干净机器上跑模板,会看到一条一般情况下正确、在此处不正确的建议。 这是当前方便性上最大的一处折损,且成因在引擎而不在文档。
- 索引是数据,mcpp 是程序 —— 发布数据不得让既有程序失效(
min_mcpp下限必须降级 而不是变砖); - 跨档位 BMI 硬拒,因此没有「看起来能用」的缓存;
- 已发布版本保留在索引表里 —— 一个已发布的版本可能已被 pin,即使它有缺陷,也只在描述
符里记录缺陷而不删行(
std-freestanding0.3.0/0.3.1 即如此); ⚠️ 模板的 CI 不可能测试尚未发布的模板 ——mcpp new --template从索引解析、不接受 路径。解法是 CI 手工渲染模板文件,测的是模板的内容;能否被抓取是 mcpp 自己的 测试范围。
索引 120 个包;裸机方向十个仓,全部有 CI 覆盖且 main 全绿。引擎自身 274 个 e2e,其中六个 直接针对裸机与 freestanding。
- 双端镜像(GitHub + GitCode),每个资产带
.sha256侧文件; ⚠️ 判定上传成功只能靠回探下载并逐字节核验。本次会话该判据两次救场:一次抓住被超时 杀掉的截断 zip(算出的 hash 会让每个 Windows 用户校验失败),一次抓住gtc报告uploaded而对象 404;- 模拟器不再依赖宿主发行版:
qemu-x86由生态自建,五宿主载荷自足(实测:linux 载荷解析 15 个对象,12 个在载荷内,只有libc/libm跨界)。
本谱系内三次同源事故:
- 模板断言放在不装模拟器的作业里 —— 物理上只可能断言
mcpp build,一个「三台机器 都编得过、一台都跑不起来」的工程通过了; - 修好后放在
gate的一行上,而每行只装自己那台机器的模拟器 —— 它能支持的主张只有 「模板在 riscv64 上能跑」,于是模板自己的build.mcpp.in漏改了 x86_64 分支而无人察觉; - 载荷闭包检查写成
ldd … 2>/dev/null | grep -v <载荷>,而ldd崩在 stderr —— 空输出 与「没有越界」不可区分。
⭐ 共同形状:断言必须放在能观察到所要主张之现象的环境里,而矩阵的每一行是一个独立的 观察环境。 前两次把这条规则执行到了作业一级,没执行到行一级。
diff 断言。
- 教学与研究性内核:三 ISA 同源探针 + 一条命令引导,是当前最顺的路径;
- RISC-V 嵌入式:两行 verified、目标 C 库在索引里、板级包与模拟器齐备;
- 需要「同一份源码跑在几台不同机器上」作为判据的工作 —— 这正是 openarch 的判据本身。
- 产品级嵌入式的完整 BSP 生态 —— 板级包目前只有 qemu virt 一块板;
- aarch64 / x86_64 上需要 C 库的裸机工程 —— 索引里没有这两个架构的 picolibc;
- 依赖动态加载的裸机场景 —— 共享库在裸机目标上被命名诊断拒绝(正确,但是个边界)。
| 优先级 | 事项 | 理由 |
|---|---|---|
| 高 | 给构建程序一条 warning 通道 |
§3.2 的缺口是当前方便性上最大的折损,且成因在引擎;它让「一次性安装」这类信息物理上到不了用户 |
| 高 | 补 aarch64 / x86_64 的目标 C 库 | 让裸机四行的能力对称,零 libc 才真正成为「选择」而不是「只有这一种」 |
| 中 | 第二块板 | 板级包模型只被一块板验证过,而一个只服务过一块板的抽象与 openarch 的两台 RISC 机器同形 —— 分不清对还是像 |
| 中 | 把「判据放在能观察的环境里」做成可检查的东西 | 三次同源事故说明这条规则靠人执行不可靠 |
| 低 | 单 ISA 第二后端(riscv S 态) | 结构已支持,需求尚未出现 |