# Genode:组件树 / session / quota 捐赠 / 局部命名 Genode 的核心不是某个固定微内核 API,而是: ``` component tree + parent 中介的服务 + session + capability + quota ``` 两条容易误导的规则: ① **服务名只在局部 parent 层级里成立**——两个程序都请求 `"Foo"`,**不能推出它们连到同一个 server**; ② **capability 整数是 protection-domain 局部的**——数值相同不代表同一对象,数值不同也可能是同一对象。 **与 seL4 的 CPtr、Zircon 的 handle 属于同一类坑**([[sel4-kernel]]、[[zircon-kernel]])。 ## 一、递归的组件树 - 每个程序运行在**自己的 sandbox** 里,并可用自己的资源**创建和管理子 sandbox**——形成严格的层级,策略在每一层生效 - 站在 **parent-child 接口**上看,只有两件事受 parent 策略约束:**child 宣告服务**、**child 请求服务** - parent 接口的操作: - **session**:child 请求连接到一个(parent 所知的)服务;参数里包含**愿意在 session 生命周期内捐给 server 的资源** - **close**:child 表示不再需要该 session;parent 应关闭它并**退还捐赠的资源** - **announce**:child 把本地实现的服务注册到 parent(从而成为 server) - **transfer_quota**:child 扩大它对提供该 session 的 server 的资源捐赠 ## 二、路由与"名字重映射" - child 宣告服务后,parent 用**本地名字**保存 server 的 root capability(放进自己的 root_list);**root capability 只供 parent 使用与保管** - parent 调 server child 的 root 接口创建 session,server 返回 session capability,parent 再用本地名字记录(session_list) - 这一过程**递归**:**服务名不必相同,其含义只延伸到直接 parent,每一层都可以重映射** - parent 对请求有四种处理:**拒绝**、**委托给自己的 parent**、**本地实现**、**在自己的其他 child 上开 session** - init 用 XML 的 `` 表描述路由(`` 下可选 `` 或 ``),支持通配(``、``)与 `` - 路由可基于 **session label**:label 在路由途中被逐级加前缀(**最靠近 server 的部分最可信,来自 client 的部分最不可信**);匹配属性包括 `label`、`label_prefix`、`label_suffix`、`unscoped_label`、`label_last` - **label 可以被重写**——目标节点里的重写可以**对 server 隐藏客户端身份** **所以恢复"谁连到谁"必须看路由表与 label 链,而不是看服务名。** ## 三、quota 捐赠:路径上的每一级都要"吃一口" - session 构造参数携带 client 捐出的资源 - **parent 会从 child 捐赠的 session quota 里扣掉本地记账开销**——于是"**session 请求路径上的每个组件都会从配额里咬一小口**" - 结果:**到达 server 的配额可能已被消耗**,server 可以返回 `QUOTA_EXCEEDED`;客户端一侧随后会**以逐步增大的配额重试** - capability 预算同理:每个 `` 节点有 `caps` 属性(建一个组件约需 35 个 cap,100 是常见实用值),cap 预算也可以在 client 与 server 之间转移 - 运行时可动态调整:child 可请求追加资源,也可被要求让出资源 **所以**: ``` 全系统还有 100 MB 空闲 某 child 只剩 8 KB quota,创建 session 失败 ``` **不矛盾**。排查顺序:全局物理内存 → 当前 PD 的 quota → 该 session 捐出的 quota → capability 预算。 ## 四、ROM session:配置可以更新,"ROM 内容变了"不是篡改 - 每个由 init 启动的组件通过向 parent **请求名为 `"config"` 的 ROM module** 获得配置;init 拦截该请求,只交付**与本 child 相关的那部分**配置;这一机制递归成立 - **ROM session 在生命周期内支持动态更新**(client 与 server 之间有专门的更新/通知协议),因此长期运行的组件配置可以被动态改变 - 嵌套配置使一棵组件树可以由单份配置描述 **所以**:ROM dataspace 从版本 A 变成版本 B 是**合法机制**,不是"只读内存被改写"。 ## 五、健康与生命周期信号 - 组件在 parent 注册信号处理器(资源可用、yield、session、heartbeat 等) - **heartbeat**:组件初始化时向 parent 注册心跳处理器,parent 触发信号后由默认处理器回应心跳——init 借此监控子组件健康 - init 支持 state report 与 exit 传播(可把子组件的退出事件转发给自己的 parent) ## 决策树 ``` 拿到 Genode 目标 ├─ 谁连到谁 │ ├─ 看 parent 的路由表()与 label 链,而不是服务名 │ ├─ 同一服务名在两层 parent 下可能指向不同 server │ └─ label 可能被重写(对 server 隐藏身份) ├─ capability 相关 │ ├─ 数值只在当前 PD 内有效;相同数值可能无关,不同数值可能是同一对象 │ └─ 按 session/对象对齐,而不是按整数对齐 ├─ 资源申请失败 │ ├─ 依次查:全局物理内存 → 当前 PD quota → session 捐赠配额 → cap 预算 │ └─ 路径上的每一级都会扣掉本地开销 ├─ ROM 内容变化 │ └─ ROM session 支持更新协议,"内容变了"属合法机制 └─ 组件消失/重启 └─ 查 heartbeat 监控与 exit 传播链 ``` ## 工具与验证 - 拓扑:init 的 XML 配置(`` 表、`` 节点、``、``)与 label 匹配/重写规则 - 资源:各节点的 quota 与 caps 配置;session 构造参数中的捐赠额;运行时的资源请求/让出 - 通信:announce(谁是 server)与 session(谁连谁),以及中间的转发层级 - 验证:能同时说清「服务的真实归属(哪一层提供的)+ capability 在哪个 PD 的命名空间里 + 资源是从哪一级配额里出的」 ## 该平台的坑(汇总) - **用服务名判断"连到同一个 server"**:名字只在局部 parent 层级有意义,且每层可重映射 - **把 capability 整数当全局标识**:它是 PD 局部命名;相同数值可能无关,不同数值可能同源 - **忽略 label 重写**:客户端身份可能被有意隐藏 - **把 root capability 当可自由传播的东西**:它只供 parent 使用与保管 - **用"系统还有内存"否定 OOM**:配额是分级的,且路径上每一级都扣过开销 - **忽略 capability 预算**:cap 与 RAM 一样是受限资源 - **把 ROM 内容变化当内存被篡改**:ROM session 本身支持更新 - **忽略 heartbeat/exit 传播**:组件消失/重启有既定的监控与通知路径