# Plan 9 / 9front:进程私有命名空间 / /srv / 9P **pathname 在 Plan 9 里不是全局标识。** 每个进程(或 namespace group)可以有自己的文件名空间: ``` Process A: open("/foo") → 可能是某个 file server 的树 Process B: open("/foo") → 可能是完全不同的对象 ``` **不能只看路径就推断"访问的是同一个对象"**——必须先恢复各自的 namespace 与绑定历史。 ## 一、bind / mount:修改的是"当前进程及其 namespace group"的视图 - `bind` 与 `mount` 修改**当前进程及同一 namespace group 内其他进程**的文件名空间 - `bind` 之后,`old` 成为 `new` 所命名对象的**别名**;**`new` 在 bind 时求值**(不是使用时) - `mount` 的 `servename` 是"**打开后返回一个已存在的到某 file server 的连接**"的文件——**几乎总是 `/srv` 下的文件**;`spec` 参数随 attach 消息传给 server,用于在 server 提供的多棵树中选择 - 目录与文件的绑定类型必须匹配(两边都是目录,或都不是) ## 二、union directory:是**单层**叠加,不是真正的目录树合并 选项决定叠加方式: | 选项 | 效果 | |---|---| | (无) | 用 `new` **替换** `old`;对目录而言,`old` 变成"只含一个目录的 union" | | **`-b`** | 把新目录加到 union 的**开头** | | **`-a`** | 把新目录加到 union 的**末尾** | | **`-c`** | 允许在 union 目录中创建——新文件落在**第一个以 `-c` 绑定**的元素里 | | `-C`(mount) | 允许内核使用本地缓存,每次 open 校验时效 | | `-q` | 失败时静默退出 | **关键限制**:Plan 9 的 union **不是完整的目录树 union**——查找按顺序检查绑定目标并**返回第一个匹配**,只有一层被叠加。例如把 `b` 绑到 `a` 前面时,`b` 中不存在的名字**不会**回落到 `a` 的深层树里——上层目录里"被遮住"的深层项仍然不可见。 **所以**: ``` readdir("/bin") 的结果 → 不能简单归属到某一个文件系统 ``` ## 三、`/srv` 不是 socket 目录,而是服务注册表 - `/srv` 是**服务注册设备**,作为可挂载服务的**汇合点**;引导后 `/srv/boot` 里是"到系统加载来源文件系统"的通信端口 - 典型流程(lib9p):建立管道 → 把 fd **以名字发布到 `/srv/`** → fork 一个子进程运行服务循环 → 需要时把它挂载到某个挂载点 **所以**: ``` 一个新文件系统出现 → 完全不需要内核里有对应的文件系统模块 → 它可能就是一个用户态 server 发布到 /srv 后被挂载的结果 ``` ## 四、9P:fid 是 session 内的 client 侧句柄 - 请求-响应模型:client 发 **T-message**,server 回 **R-message**,各有类型与 tag - 标准操作:`Tversion/Rversion`、`Tattach/Rattach`、**`Twalk/Rwalk`**、`Topen/Ropen`、`Tread/Rread`、`Twrite/Rwrite`、`Tclunk/Rclunk`、`Tstat/Rstat`、`Twstat/Rwstat` - **`fid` / `newfid` 是当前 9P session 内的 client 侧句柄命名空间**:`Twalk` 从已有 fid 出发遍历路径,并把结果绑定给 `newfid` - 库层(lib9p)里,请求对象与 fid 与"未完成的请求""活跃的 fid"一一对应;**服务循环会拒绝重用已存在的 fid,或打开一个已经打开的 fid** - 若服务用"文件树"方式实现,walk 由库内部处理,不回调驱动的 walk **所以**: ``` 连接 A 的 fid 5 连接 B 的 fid 5 → 没有任何必然关系 即使同一连接,fid 也会随 walk/clunk 生命周期变化 → 不能当 inode 那样的持久标识 ``` 恢复 9P 通信应当对齐:**connection/session + fid + walk 历史 + qid**。 ## 五、`open`/`read`/`write` 很可能实际是用户态 RPC lib9p 的服务循环:从输入 fd 读请求 → 分发到函数指针(auth / attach / open / create / read / write / remove / flush / stat / wstat / walk / walk1 / clone / destroyfid / destroyreq / start / end)→ 把响应写到输出 fd;**服务循环是单线程的**。 **所以**: ``` open("/net/tcp/clone"); write(...); read(...) → 不能直接套 "syscall → 内核 TCP 栈" → 实际路径可能是:namespace → 已挂载的 file server → 9P → 用户态处理程序 ``` 这是 Plan 9 逆向最根本的心智模型。 ## 决策树 ``` 拿到 Plan 9 / 9front 目标 ├─ 两个进程访问同名路径 │ ├─ 先恢复各自的 namespace 与 bind/mount 历史 │ └─ 不能因路径相同就认定同一对象 ├─ 目录内容异常 │ ├─ 查是否 union(-b/-a 顺序、-c 创建位置) │ └─ union 只有单层叠加:上层目录不存在的名字不会落到下层深树 ├─ 出现"新的文件系统" │ ├─ 查 /srv 下是否有对应服务被发布 │ └─ 不需要内核文件系统模块 ├─ 分析 9P 流量 │ ├─ 按 connection/session 对齐,而不是裸比较 fid 整数 │ └─ 结合 walk 历史与 qid └─ 文件操作行为与预期不符 └─ 先确认它到底 hit 到哪个 server(namespace → 9P → 用户态处理程序) ``` ## 工具与验证 - 命名空间:每个进程/namespace group 的绑定与挂载记录、bind 选项与顺序 - 服务发现:`/srv` 下的条目与它们的发布者;挂载点与 `spec` 参数 - 协议:9P 会话(连接、fid、walk 历史、qid)与消息类型 - 验证:能同时说清「路径在谁的 namespace 里解析 + 最终落到哪个 file server + 数据经过哪些用户态处理程序」 ## 该平台的坑(汇总) - **把 pathname 当全系统唯一资源**:命名空间是每进程/每组的 - **因为路径相同就认定同一对象**:必须先恢复各自的绑定历史 - **把 union 目录当真正的树合并**:只有单层叠加,查找返回第一个匹配 - **忽略 `-b` / `-a` 的顺序与 `-c` 的创建位置**:结果差异很大 - **把 `/srv/foo` 当 socket 文件**:它是服务注册表里的一个连接 - **以为新文件系统必须由内核模块提供**:用户态 server 发布后挂载即可 - **把 9P 的 fid 当 inode/全局文件 ID**:它是 session 内的 client 侧句柄 - **跨连接比较 fid 整数**:不同 session 之间没有可比性 - **把文件操作当 syscall → 内核栈**:通常要经过 namespace → file server → 9P → 用户态处理程序