Skip to content

0.4.0 —— 一个包、两个门面、三个后端 - #1

Merged
Sunrisepeak merged 2 commits into
mainfrom
feat/hybrid-two-faces
Aug 20, 2026
Merged

0.4.0 —— 一个包、两个门面、三个后端#1
Sunrisepeak merged 2 commits into
mainfrom
feat/hybrid-two-faces

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

第三台机器把门槛变成了证据

riscv64 与 aarch64 都是弱内存序、定长指令的 load/store RISC 机器。一个同时适配两者的接口,可能是因为它对,也可能是因为它们像 —— 在这两台机器上再怎么测也分不开。

x86_64 两样都不是:变长指令;total store order(四条屏障里三条根本不需要指令);中断机制是 256 个门的表而非一个基址寄存器;控制台由 out 到达而非一次存储,没有任何指针能命名它

一份探针源码,三台机器,输出逐字节相同:

main: switching to task
task: arg=42
main: back, witness=7 before=1234
trap: raising
trap: back, witness=1
cpu: percpu round-trips
cpu: four barriers accepted
switch ok

第三台机器挖出的三条

  1. MAIR_EL1 那个决定不再是 aarch64 的例外。 x86_64 的 PWT/PCD/PAT 也是 IA32_PAT 的索引。⚠️ 而且规则更严:未编程的 MAIR_EL1 字段读作最严格类型(慢而正确);IA32_PAT 复位值在索引 1 上是 write-through,过早的设备映射是被缓存的 —— 不触发任何异常
  2. pc 在每台机器上不指同一件事。 x86_64 的 trap(含 int3)报告下一条指令的地址。后端做归一化,f->pc += f->instr_len 三台机器同解。
  3. 一条接口承诺在这台机器的页表项里无法表达。 x86_64 只有一个覆盖全特权级的 NX,"用户映射不可被内核执行"改由 CR4.SMEP 提供。

一个包、两个门面

mcpp.toml 同时是 [package][workspace]。消费者写一行依赖,然后 #include <mcpplibs/openarch.h>import mcpplibs.openarch; 二选一 —— 两者是一个库的两种拼写:模块的 trap_frame 就是 ::arch_trap_frame,枚举由契约的枚举量定义而来

后端由 feature 选择

消费者要什么 清单里写什么
本 target 的后端 openarch = "0.4.0"
指定某一个 default-features = false, features = ["backend-riscv64"]
自己实现 default-features = false, features = ["backend-external"] + 一个 provides = ["openarch-backend"] 的包

⚠️ backend-auto 刻意 require 该能力 —— 第一版让它 require 了,结果本包自己的宿主测试无法构建。

⚠️ 两处 CI 修正:从 0.3.0 起 CI 一直是红的,此前没有去看

  1. "探针不按架构分支"这条断言太宽。探针必须在恰好一处指名架构 —— 陷入指令。收窄为:一个条件块,块内除指令外别无他物。
  2. portability 作业在根跑 --target riscv64-none-elf,而虚拟 workspace 会对所有成员扇出,把 aarch64 汇编喂给 riscv 汇编器。混合式的根修好了它。

依赖

需要 mcpp 2026.8.21.1(mcpp-community/mcpp#472),其中带 x86_64-none-elf 目标行。

## 第三台机器

riscv64 与 aarch64 都是弱内存序、定长指令的 load/store RISC 机器。一个同时适配两者
的接口,可能是因为它对,也可能是因为它们像 —— 在这两台机器上再怎么测也分不开这两种
情况。

x86_64 两样都不是:变长指令;total store order,四条屏障里有三条根本不需要指令;中断
机制是 256 个门的表而不是一个基址寄存器;控制台由 `out` 到达而不是由一次存储到达,
没有任何指针能命名它。**三台都活下来的才是抽象。**

一份探针源码在三台机器上构建并运行,输出逐字节相同。

### 第三台机器改了什么

- **`MAIR_EL1` 那个决定不再是 aarch64 的例外。** 两台机器时是一比一,"这一层拥有属性
  寄存器"还可以被称作 aarch64 的权宜。x86_64 做同一件事:`PWT`/`PCD`/`PAT` 三个分散的
  位构成 `IA32_PAT` 的索引。现在是二比一,而且方向反了过来。
  ⚠️ 它的规则更严:未编程的 `MAIR_EL1` 字段读作最严格的类型,过早的 aarch64 映射只是
  慢而正确;`IA32_PAT` 的复位值在索引 1 上是 **write-through**,过早的设备映射是被缓存
  的 —— 写在程序没有选择的时刻到达设备,且不触发任何异常。

- **`pc` 在每台机器上并不指同一件事,接口吸收了它而不是复述它。** 两台 RISC 机器都报告
  出错指令的地址;x86_64 把异常分为 *fault*(如此)与 *trap*(报告**下一条**的地址),
  而 `int3` —— `instr_len` 正是为跨过它而存在的断点 —— 是 trap。后端做归一化,于是
  `f->pc += f->instr_len` 在三台机器上都恢复到同一处。另一条路是告诉每一个将要被写出来
  的处理函数(包括永远只跑在 RISC 机器上的那些)`pc` 在这里含义不同。

- **接口的一条承诺在这台机器的页表项里无法表达。** riscv 用 `U` 限定 `X`,aarch64 有
  独立的 `PXN`/`UXN`,所以"用户映射不可被内核执行"在两者都是编码的属性。x86_64 只有一
  个覆盖全部特权级的 `NX`,该规则改由 `CR4.SMEP` 提供,由
  `install_memory_attributes()` 设置 —— 与 `MAIR_EL1` 同形的答案,却出于不同的理由。

## 一个包、两个门面

根 `mcpp.toml` 同时是 `[package]` 与 `[workspace]`。虚拟 workspace 会把接口放进成员目录,
`openarch = "0.4.0"` 就得指名它。

消费者写一行依赖,然后二选一:`#include <mcpplibs/openarch.h>` 或
`import mcpplibs.openarch;`。两者是一个库的两种拼写,不是两份互相对齐的声明:模块的
`trap_frame` **就是** `::arch_trap_frame`(`using`,不是同形体),枚举由契约的枚举量
*定义而来* —— `illegal = ARCH_TRAP_ILLEGAL`。`tests/faces.cpp` 检查的是这个推导,而不是
一致性,后者是更弱的东西。

## 后端由 feature 选择

三个曾由一个机制回答的问题被分开了:

- `backend-auto` —— 默认开启,按 target 解析。
- `backend-<arch>` —— 显式指定,为一个 ISA 有多个后端的情形留出位置(riscv 会需要:
  这个后端陷入 M 模式,SBI 之下的内核陷入 S 模式)。
- `backend-external` —— **使用者自己实现**。它不指名任何包,而是 *require 能力*
  `openarch-backend`;图里没有提供者时构建在 configure 阶段就停下并说明,而不是在链接期
  报出一个改过名的符号。这与 `std-freestanding` 的分配器同形。

⚠️ `backend-auto` 刻意**不** require 该能力,而第一版让它 require 了。feature 是可加的,
而 `requires` 是无条件的(哪怕满足它的 `feature-deps` 是 target 条件化的)—— 于是本包
自己的宿主测试无法构建:

    error: no package provides capability 'openarch-backend' required by 'openarch'

宿主目标没有后端是关于目标的事实,不是消费者能处理的错误。

## 类型集中到一处

`openarch/types.h` 定义 `arch_u32`/`arch_u64`/`arch_uptr` 并**断言它们的宽度**。此前每处
用点各自拼出 `unsigned long long`,顶上一段注释解释为什么不是 `unsigned long` —— 一条被
描述而从未被检查的规则。它唯一一次被违反(`1UL << 53`)是靠运气发现的:那个移位恰好在
`constexpr` 里,编译器被迫求值。

## CI

⚠️ **两处修正,而 CI 从 0.3.0 起就是红的,我此前没有去看。**

1. "探针不按架构分支"这条断言太宽:探针必须在恰好一处指名架构 —— 陷入指令,`ebreak` /
   `brk #0` / `int3` 是同一个想法的三种拼写,没有可移植的第四种。断言收窄为:一个条件块,
   块内除指令外别无他物。
2. portability 作业在仓库根跑 `mcpp build --target riscv64-none-elf`,而 0.3.1 的根是虚拟
   workspace —— 于是它对**所有成员**扇出,把 aarch64 汇编喂给 riscv 汇编器
   (`unrecognized instruction mnemonic, did you mean: sra, srl?`)。混合式的根修好了它:
   根现在是接口包,构建它只拉入该 target 的后端。

x86_64 一行的模拟器来自 apt 并注明了原因:xPack 按目标族发布 QEMU 而没有 x86 构建,
所以生态里没有 `xim:qemu-x86` 可装。
`mcpp new mykernel --template openarch` 生成的工程就是探针本身:一份
src/main.cpp、三个带控制台与断电寄存器的 machine_<arch>.cpp、三份链接脚本,
以及只有 x86_64 才需要的那一百来条到达长模式的指令。

⚠️ CI 用 sed 手工渲染模板而不是走 `mcpp new`。scaffolder 从**索引**解析
--template 且不接受路径,所以为一个「本次提交才加进来的模板」调用它,解析到的
是上一个已发布版本,而那个版本不带这个模板 —— 新模板的 CI 永远不可能在它被发布
之前通过。属于本仓库的是模板的**内容**。

⚠️ 只在 Linux 上跑:那一步把 $PWD 写进清单,而 Windows runner 的 Git Bash 下
$PWD 是 /d/a/openarch/openarch,mcpp 要的是原生路径 —— 本生态已经被这个形状咬过
一次(一个 C++ 字符串字面量里出现了 "D:\a\openkal\openkal/include")。
@Sunrisepeak
Sunrisepeak merged commit 6a34941 into main Aug 20, 2026
8 of 16 checks passed
@Sunrisepeak
Sunrisepeak deleted the feat/hybrid-two-faces branch August 20, 2026 19:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant