|
| 1 | +# Proc 挂载导出接口 |
| 2 | + |
| 3 | +## 1. 概述 |
| 4 | + |
| 5 | +DragonOS 通过 procfs 向用户态导出挂载命名空间视图,主要入口如下: |
| 6 | + |
| 7 | +| 路径 | 类型 | 视角 | |
| 8 | +|------|------|------| |
| 9 | +| `/proc/mounts` | 符号链接 → `self/mounts` | 当前读取进程 | |
| 10 | +| `/proc/self/mounts` | 普通文件 | 当前读取进程 | |
| 11 | +| `/proc/[pid]/mounts` | 普通文件 | 目标 `pid` | |
| 12 | +| `/proc/[pid]/mountinfo` | 普通文件 | 目标 `pid` | |
| 13 | +| `/proc/[pid]/mountstats` | 普通文件 | 目标 `pid` | |
| 14 | + |
| 15 | +其中: |
| 16 | + |
| 17 | +- **`mounts`**:传统格式,字段少,兼容 `mount(8)`、shell 脚本等。 |
| 18 | +- **`mountinfo`**:现代格式,含 mount id、父子关系、传播标签、superblock 选项等。 |
| 19 | +- **`mountstats`**:每个 mount 一行描述前缀,并可追加文件系统自定义统计(`proc_show_mount_stats`)。 |
| 20 | + |
| 21 | +传播类型(`shared` / `master` / `propagate_from` / `unbindable`)**仅出现在 `mountinfo` 的 optional 字段**,不会写入 `/proc/*/mounts` 的普通 option 列。 |
| 22 | + |
| 23 | +## 2. 各接口的功能作用 |
| 24 | + |
| 25 | +### 2.1 `/proc/mounts` 与 `/proc/self/mounts` |
| 26 | + |
| 27 | +`/proc/mounts` 在实现上是 **指向 `self/mounts` 的符号链接**(`readlink` 结果为 `self/mounts`),解析后等价于读取 `/proc/self/mounts`,即 **当前读取进程** 在其 mount namespace 与 `fs root` 下的挂载列表。 |
| 28 | + |
| 29 | +每一行通常包含: |
| 30 | + |
| 31 | +- 设备名(或文件系统名) |
| 32 | +- 挂载点 |
| 33 | +- 文件系统类型 |
| 34 | +- 挂载选项(`rw` 及 `nosuid,nodev,...` 等 per-mount 选项;不含传播标签) |
| 35 | +- 两个兼容字段 `0 0` |
| 36 | + |
| 37 | +### 2.2 `/proc/[pid]/mounts` |
| 38 | + |
| 39 | +格式与 `/proc/self/mounts` 相同,但 **open 时绑定目标线程组 leader** 的 `mnt_ns` 与 `fs_struct.root()`,导出的是目标进程视角下的可见挂载。 |
| 40 | + |
| 41 | +### 2.3 `/proc/[pid]/mountinfo` |
| 42 | + |
| 43 | +在 `mounts` 基础上增加: |
| 44 | + |
| 45 | +- mount id、parent mount id |
| 46 | +- 主设备号(`major:minor`) |
| 47 | +- mount root(`proc_show_mountinfo_root`) |
| 48 | +- per-mount options 与 superblock 选项(以 `-` 分隔的两段 optional 字段) |
| 49 | +- propagation tagged fields(`MountPropagation::proc_mountinfo_tags()`) |
| 50 | +- 文件系统类型名 |
| 51 | + |
| 52 | +### 2.4 `/proc/[pid]/mountstats` |
| 53 | + |
| 54 | +每个可见 mount 至少一行: |
| 55 | + |
| 56 | +```text |
| 57 | +device <dev> mounted on <mountpoint> with fstype <type> |
| 58 | +``` |
| 59 | + |
| 60 | +若底层文件系统通过 `proc_show_mount_stats` 返回额外内容,则追加在同一行末尾。权限为 **0400**(仅 owner 可读),与 `mounts` / `mountinfo` 的 0444 不同。 |
| 61 | + |
| 62 | +## 3. 内核源码布局 |
| 63 | + |
| 64 | +挂载导出逻辑集中在 **`kernel/src/filesystem/procfs/mount/`**,不再使用历史上的 `mount_view.rs` 单文件或 `procfs/mounts.rs`、`pid/mountinfo.rs` 等分散实现。 |
| 65 | + |
| 66 | +``` |
| 67 | +kernel/src/filesystem/procfs/mount/ |
| 68 | +├── mod.rs # 模块入口;导出 render API |
| 69 | +├── collect.rs # ProcMountEntry、collect_visible_mounts() |
| 70 | +├── fields.rs # MountProcFields:open 前预计算各导出字段 |
| 71 | +├── escape.rs # proc 字段转义(空格、制表符、反斜杠等) |
| 72 | +├── render.rs # ProcMountRenderKind;open 渲染 + read 读缓存 |
| 73 | +├── format/ |
| 74 | +│ ├── mounts_line.rs # /proc/*/mounts 行格式 |
| 75 | +│ ├── mountinfo_line.rs # /proc/*/mountinfo 行格式 |
| 76 | +│ └── mountstats_line.rs # /proc/*/mountstats 行格式 |
| 77 | +└── inode/ |
| 78 | + ├── mounts_symlink.rs # /proc/mounts → self/mounts(MountsSymOps) |
| 79 | + └── pid_mount.rs # /proc/[pid]/{mounts,mountinfo,mountstats}(MountProcFileOps) |
| 80 | +``` |
| 81 | + |
| 82 | +**注册位置:** |
| 83 | + |
| 84 | +- `kernel/src/filesystem/procfs/root.rs`:根目录项 `("mounts", MountsSymOps::new_inode)` |
| 85 | +- `kernel/src/filesystem/procfs/pid/mod.rs`:`PidDirOps::STATIC_ENTRIES` 中为 `mountinfo` / `mounts` / `mountstats` 各注册一个 `MountProcFileOps`(通过 `ProcMountRenderKind` 区分格式) |
| 86 | + |
| 87 | +**相关但不在 `procfs/mount/` 内的依赖:** |
| 88 | + |
| 89 | +- `kernel/src/filesystem/vfs/mount/mod.rs`:`MountFlags::proc_rw_token()`、`proc_per_mount_options()`、`proc_super_block_options()`、`options_string()` |
| 90 | +- `kernel/src/filesystem/vfs/mod.rs`:文件系统 trait 钩子 `proc_show_devname`、`proc_show_mount_options`、`proc_show_mountinfo_root`、`proc_show_mount_stats` |
| 91 | +- `kernel/src/process/namespace/propagation.rs`:`MountPropagation::proc_mountinfo_tags()` |
| 92 | + |
| 93 | +用户态测例:`user/apps/tests/dunitest/suites/normal/proc_mount_exports.cc`(whitelist:`normal/proc_mount_exports`)。 |
| 94 | + |
| 95 | +## 4. DragonOS 实现原理 |
| 96 | + |
| 97 | +### 4.1 统一渲染流水线 |
| 98 | + |
| 99 | +三种 proc 文件共用一条流水线(`render.rs`): |
| 100 | + |
| 101 | +1. **`open()`**(`MountProcFileOps::open` 或经 symlink 打开 `/proc/self/mounts`) |
| 102 | + - 解析目标:`ProcPidTarget` → 线程组 leader `ProcessControlBlock` |
| 103 | +2. **`collect_visible_mounts()`**(`collect.rs`) |
| 104 | + - 遍历目标 `mnt_ns.mount_list()`,按 mount id 排序 |
| 105 | + - 用目标 `fs_struct.root()` 做 **可见性裁剪**(`visible_mountpoint`) |
| 106 | +3. **`MountProcFields::from_entry()`**(`fields.rs`) |
| 107 | + - 为每个 `ProcMountEntry` 快照 devname、fstype、各类 options、mountinfo root/tags 等 |
| 108 | +4. **按 `ProcMountRenderKind` 调用 `format::*_line::render`** |
| 109 | +5. 将完整文本写入 `FilePrivateData::Procfs.data` |
| 110 | +6. **`read_at()`** 仅通过 `read_cached_mount_file()` → `proc_read()` 从缓存拷贝 |
| 111 | + |
| 112 | +因此当前模型是:**open 时生成整文件快照,同一次打开期间 read 不再重新遍历挂载树**。 |
| 113 | + |
| 114 | +### 4.2 目标进程视角 |
| 115 | + |
| 116 | +- `/proc/mounts` → `self/mounts` → 当前进程的 pid 目录下的 `mounts` |
| 117 | +- `/proc/[pid]/mounts|mountinfo|mountstats` 在 open 时固定绑定该 `pid` 对应线程组的 namespace 与 root |
| 118 | + |
| 119 | +导出内容反映的是 **目标进程的 `mnt_ns` + `fs root`**,不是读取者自己的挂载表(除非读取的就是自己的 `/proc/self/...`)。 |
| 120 | + |
| 121 | +### 4.3 可见性裁剪 |
| 122 | + |
| 123 | +`collect.rs` 中 `visible_mountpoint(mount_path, root_path)`: |
| 124 | + |
| 125 | +- 目标 root 为 `/` 时,挂载点路径原样导出 |
| 126 | +- 目标处于 chroot 等受限 root 时,只保留该 root 子树内的 mount,并将显示路径归一化到以 `/` 为根的视图 |
| 127 | + |
| 128 | +### 4.4 选项与传播字段的拆分 |
| 129 | + |
| 130 | +| 字段来源 | 用于 | 说明 | |
| 131 | +|----------|------|------| |
| 132 | +| `MountFlags::proc_rw_token()` | mounts / mountinfo per-mount | `ro` 或 `rw` | |
| 133 | +| `MountFlags::proc_per_mount_options()` | mountinfo per-mount | `nosuid,nodev,...`,不含 `rw` | |
| 134 | +| `MountFlags::proc_super_block_options()` + sb 只读状态 | mountinfo superblock 段 | `sync,mand,...` | |
| 135 | +| `FileSystem::proc_show_mount_options()` | mounts 行、mountinfo superblock 段 | 文件系统私有选项 | |
| 136 | +| `MountPropagation::proc_mountinfo_tags()` | 仅 mountinfo 尾部 | `shared:N` 等,**不进入 mounts** | |
| 137 | + |
| 138 | +`mounts_line` 使用预合并的 `mounts_options`;`mountinfo_line` 将 per-mount 与 superblock 选项用 `-` 分隔,再追加 propagation tags。 |
| 139 | + |
| 140 | +### 4.5 三种格式的职责划分 |
| 141 | + |
| 142 | +- **`format/mounts_line.rs`**:设备、挂载点、类型、选项、`0 0` |
| 143 | +- **`format/mountinfo_line.rs`**:id、parent、major:minor、root、挂载点、选项段、`-`、fstype、super 选项、tags |
| 144 | +- **`format/mountstats_line.rs`**:通用 `device ... mounted on ...` 前缀 + 可选 fs stats |
| 145 | + |
| 146 | +文件系统差异通过 VFS trait 钩子注入,procfs 只负责通用行结构与转义。 |
| 147 | + |
| 148 | +## 5. 当前接口的语义特点 |
| 149 | + |
| 150 | +### 5.1 `mounts` |
| 151 | + |
| 152 | +兼容性强、字段少;**不包含** propagation 标签。与 Linux 一样,应通过 `/proc/mounts` symlink 访问当前进程视图。 |
| 153 | + |
| 154 | +### 5.2 `mountinfo` |
| 155 | + |
| 156 | +恢复挂载拓扑与传播属性的首选接口;per-mount 与 superblock 选项、传播标签分列展示。 |
| 157 | + |
| 158 | +### 5.3 `mountstats` |
| 159 | + |
| 160 | +- 不是 mount 变更通知接口 |
| 161 | +- 同一次 `open()` 内内容为快照;重新 `open()` 可看到更新后的挂载集合与统计 |
| 162 | +- 行格式允许 `device` 或 `no device` 前缀(由 devname 是否为空决定,测例见 `proc_mount_exports.cc`) |
| 163 | + |
| 164 | +## 6. 与 Linux 的实现差异 |
| 165 | + |
| 166 | +### 6.1 总体差异概览 |
| 167 | + |
| 168 | +| 维度 | Linux | DragonOS 当前实现 | |
| 169 | +|------|-------|-------------------| |
| 170 | +| 打开方式 | `seq_file` + 迭代器 | `open()` 时一次性渲染并缓存 | |
| 171 | +| 读取方式 | 读时按需生成 | 从 `FilePrivateData` 缓存读取 | |
| 172 | +| `/proc/mounts` | symlink → `self/mounts` | 已实现(`MountsSymOps`) | |
| 173 | +| 视角绑定 | 目标 task 的 `mnt_ns + fs root` | 同左(`collect_visible_mounts`) | |
| 174 | +| `mounts` / `mountinfo` poll | 支持 mount namespace 事件 | 未实现 | |
| 175 | +| 遍历基础 | namespace list + cursor | `mnt_ns.mount_list()` 排序后迭代 | |
| 176 | +| 代码组织 | `fs/proc_namespace.c` 等 | `procfs/mount/{collect,fields,format,render,inode}` | |
| 177 | + |
| 178 | +### 6.2 Linux 的 `seq_file` 语义 |
| 179 | + |
| 180 | +Linux 使用 `mounts_open_common()` + `seq_file` 在读取过程中迭代 mount 列表。DragonOS 选择在 open 时拼完整字符串并缓存,实现更简单,同一次 fd 内结果稳定,但与 Linux 的迭代模型不完全等价。 |
| 181 | + |
| 182 | +### 6.3 `mounts` / `mountinfo` 的 `poll` |
| 183 | + |
| 184 | +Linux 可通过 mount namespace 事件对 `mounts` / `mountinfo` 做 `poll`/`epoll`。DragonOS 尚未实现 namespace 事件序号与等待队列,不能作为挂载变更通知源。 |
| 185 | + |
| 186 | +### 6.4 `mountstats` 的动态性与 `poll` |
| 187 | + |
| 188 | +Linux 无专门的 `mountstats` poll 语义;DragonOS 同样不为 `mountstats` 发明额外 poll。统计与拓扑变化通过重新打开文件观察。 |
| 189 | + |
| 190 | +### 6.5 可见性裁剪语义 |
| 191 | + |
| 192 | +Linux 使用 `seq_path_root` 等基于路径对象的 root 裁剪。DragonOS 当前基于 **绝对路径字符串** 与目标 `fs root` 比较,大方向一致,细节上与 Linux 路径对象语义仍有差距。 |
| 193 | + |
| 194 | +### 6.6 遍历与权威数据源 |
| 195 | + |
| 196 | +Linux 以 namespace 级 mount 链表为权威数据源。DragonOS 从 `MntNamespace::mount_list()` 取表并排序,而非从单棵 mount 树 DFS;后续若要对齐 Linux 迭代顺序与事件模型,需要在 `MntNamespace` 侧继续演进。 |
| 197 | + |
| 198 | +## 7. 当前适用场景与建议 |
| 199 | + |
| 200 | +已支持: |
| 201 | + |
| 202 | +- 通过 `/proc/mounts`(symlink)或 `/proc/self/mounts` 读取当前进程挂载表 |
| 203 | +- 调试时读取 `/proc/[pid]/mounts`、`mountinfo`、`mountstats` |
| 204 | +- 容器/命名空间工具解析 `mountinfo` 中的 propagation 字段 |
| 205 | + |
| 206 | +需注意: |
| 207 | + |
| 208 | +- 依赖 **mount namespace `poll` 通知** 的用户态工具尚未兼容 |
| 209 | +- 强依赖 Linux `seq_file` 逐行迭代语义的程序可能观察到行为差异 |
| 210 | +- 修改导出逻辑时,应同时更新 `procfs/mount/` 与 `proc_mount_exports` 测例 |
| 211 | + |
| 212 | +## 8. 小结 |
| 213 | + |
| 214 | +DragonOS 将 proc 挂载导出收拢到 **`kernel/src/filesystem/procfs/mount/`**: |
| 215 | + |
| 216 | +- **inode 层**:`/proc/mounts` symlink + `/proc/[pid]/*` 统一 `MountProcFileOps` |
| 217 | +- **数据层**:`collect` → `fields` 快照 → `format` 三种行渲染 |
| 218 | +- **选项语义**:传播标签仅在 `mountinfo`;`MountFlags` 与 VFS 钩子分工导出 |
| 219 | + |
| 220 | +对外功能定位已接近 Linux;底层仍为 **open 快照 + 字符串裁剪**,在 `poll`、迭代模型与路径语义上继续演进。 |
0 commit comments