Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
250dc9b
Refactor skill-creator scripts: remove init_skill.py, enhance packagi…
xboHodx Mar 20, 2026
03998d0
Merge branch 'master' of github.com:xboHodx/DragonOS into feat/mounts…
xboHodx Apr 2, 2026
3fd3be2
feat(procfs): 新增proc挂载导出接口实现
xboHodx Apr 3, 2026
6957233
fmt
xboHodx Apr 3, 2026
34fdac9
Revert "Refactor skill-creator scripts: remove init_skill.py, enhance…
xboHodx Apr 4, 2026
7a6e4c9
Merge branch 'master' of github.com:xboHodx/DragonOS into feat/mounts…
xboHodx Apr 4, 2026
599dacd
Merge branch 'master' of github.com:xboHodx/DragonOS into feat/mounts…
xboHodx May 21, 2026
4aee108
fix(procfs): update process handling in mount file operations
xboHodx May 21, 2026
f0b179d
Merge branch 'master' into feat/mountstats
xboHodx May 28, 2026
0dbb39d
fmt
xboHodx May 29, 2026
e121067
Merge branch 'master' of github.com:xboHodx/DragonOS into feat/mounts…
xboHodx May 29, 2026
f733b8b
Enhance procfs mount handling and add tests for mount exports
xboHodx May 30, 2026
0135707
fix(mount): rename
xboHodx May 30, 2026
353dec7
Merge branch 'master' of github.com:xboHodx/DragonOS into feat/mounts…
xboHodx May 31, 2026
f302f8c
feat(procfs): add proc mount export interfaces
fslongjin May 31, 2026
c4a5624
refactor(procfs): consolidate mount handling into a dedicated module
xboHodx Jun 1, 2026
9fb10f2
Merge remote-tracking branch 'origin/feat/mountstats'
xboHodx Jun 1, 2026
2a03eda
refactor(procfs): remove mount_exports_review_plan.md and update moun…
xboHodx Jun 1, 2026
f92003d
fmt
xboHodx Jun 1, 2026
2ca5d41
refactor(procfs): adjust inode mode for mount stats
xboHodx Jun 1, 2026
3ac1134
update(monitor_test_results): extend TEST_TIMEOUT from 30 to 35 minutes
xboHodx Jun 2, 2026
4f7c99f
fix(procfs): set mountstats owner from target process
fslongjin Jun 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ config/rootfs.generated.toml
*.cpio.xz
*.cpio*
.direnv/

*.md
.cursor
1 change: 1 addition & 0 deletions docs/kernel/filesystem/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ todo: 由于文件系统模块重构,文档暂时不可用,预计在2023年4
overview
fuse
vfs/index
proc/index
sysfs
kernfs
unionfs/index
17 changes: 17 additions & 0 deletions docs/kernel/filesystem/proc/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
====================================
ProcFS
====================================

ProcFS 用于向用户态导出内核中的进程、挂载、命名空间等运行时信息。

当前目录主要介绍 DragonOS 中与挂载导出相关的 proc 接口实现,包括:

- `/proc/mounts`
- `/proc/[pid]/mounts`
- `/proc/[pid]/mountinfo`
- `/proc/[pid]/mountstats`

.. toctree::
:maxdepth: 1

mounts
220 changes: 220 additions & 0 deletions docs/kernel/filesystem/proc/mounts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
# Proc 挂载导出接口

## 1. 概述

DragonOS 通过 procfs 向用户态导出挂载命名空间视图,主要入口如下:

| 路径 | 类型 | 视角 |
|------|------|------|
| `/proc/mounts` | 符号链接 → `self/mounts` | 当前读取进程 |
| `/proc/self/mounts` | 普通文件 | 当前读取进程 |
| `/proc/[pid]/mounts` | 普通文件 | 目标 `pid` |
| `/proc/[pid]/mountinfo` | 普通文件 | 目标 `pid` |
| `/proc/[pid]/mountstats` | 普通文件 | 目标 `pid` |

其中:

- **`mounts`**:传统格式,字段少,兼容 `mount(8)`、shell 脚本等。
- **`mountinfo`**:现代格式,含 mount id、父子关系、传播标签、superblock 选项等。
- **`mountstats`**:每个 mount 一行描述前缀,并可追加文件系统自定义统计(`proc_show_mount_stats`)。

传播类型(`shared` / `master` / `propagate_from` / `unbindable`)**仅出现在 `mountinfo` 的 optional 字段**,不会写入 `/proc/*/mounts` 的普通 option 列。

## 2. 各接口的功能作用

### 2.1 `/proc/mounts` 与 `/proc/self/mounts`

`/proc/mounts` 在实现上是 **指向 `self/mounts` 的符号链接**(`readlink` 结果为 `self/mounts`),解析后等价于读取 `/proc/self/mounts`,即 **当前读取进程** 在其 mount namespace 与 `fs root` 下的挂载列表。

每一行通常包含:

- 设备名(或文件系统名)
- 挂载点
- 文件系统类型
- 挂载选项(`rw` 及 `nosuid,nodev,...` 等 per-mount 选项;不含传播标签)
- 两个兼容字段 `0 0`

### 2.2 `/proc/[pid]/mounts`

格式与 `/proc/self/mounts` 相同,但 **open 时绑定目标线程组 leader** 的 `mnt_ns` 与 `fs_struct.root()`,导出的是目标进程视角下的可见挂载。

### 2.3 `/proc/[pid]/mountinfo`

在 `mounts` 基础上增加:

- mount id、parent mount id
- 主设备号(`major:minor`)
- mount root(`proc_show_mountinfo_root`)
- per-mount options 与 superblock 选项(以 `-` 分隔的两段 optional 字段)
- propagation tagged fields(`MountPropagation::proc_mountinfo_tags()`)
- 文件系统类型名

### 2.4 `/proc/[pid]/mountstats`

每个可见 mount 至少一行:

```text
device <dev> mounted on <mountpoint> with fstype <type>
```

若底层文件系统通过 `proc_show_mount_stats` 返回额外内容,则追加在同一行末尾。权限为 **0400**(仅 owner 可读),与 `mounts` / `mountinfo` 的 0444 不同。

## 3. 内核源码布局

挂载导出逻辑集中在 **`kernel/src/filesystem/procfs/mount/`**,不再使用历史上的 `mount_view.rs` 单文件或 `procfs/mounts.rs`、`pid/mountinfo.rs` 等分散实现。

```
kernel/src/filesystem/procfs/mount/
├── mod.rs # 模块入口;导出 render API
├── collect.rs # ProcMountEntry、collect_visible_mounts()
├── fields.rs # MountProcFields:open 前预计算各导出字段
├── escape.rs # proc 字段转义(空格、制表符、反斜杠等)
├── render.rs # ProcMountRenderKind;open 渲染 + read 读缓存
├── format/
│ ├── mounts_line.rs # /proc/*/mounts 行格式
│ ├── mountinfo_line.rs # /proc/*/mountinfo 行格式
│ └── mountstats_line.rs # /proc/*/mountstats 行格式
└── inode/
├── mounts_symlink.rs # /proc/mounts → self/mounts(MountsSymOps)
└── pid_mount.rs # /proc/[pid]/{mounts,mountinfo,mountstats}(MountProcFileOps)
```

**注册位置:**

- `kernel/src/filesystem/procfs/root.rs`:根目录项 `("mounts", MountsSymOps::new_inode)`
- `kernel/src/filesystem/procfs/pid/mod.rs`:`PidDirOps::STATIC_ENTRIES` 中为 `mountinfo` / `mounts` / `mountstats` 各注册一个 `MountProcFileOps`(通过 `ProcMountRenderKind` 区分格式)

**相关但不在 `procfs/mount/` 内的依赖:**

- `kernel/src/filesystem/vfs/mount/mod.rs`:`MountFlags::proc_rw_token()`、`proc_per_mount_options()`、`proc_super_block_options()`、`options_string()`
- `kernel/src/filesystem/vfs/mod.rs`:文件系统 trait 钩子 `proc_show_devname`、`proc_show_mount_options`、`proc_show_mountinfo_root`、`proc_show_mount_stats`
- `kernel/src/process/namespace/propagation.rs`:`MountPropagation::proc_mountinfo_tags()`

用户态测例:`user/apps/tests/dunitest/suites/normal/proc_mount_exports.cc`(whitelist:`normal/proc_mount_exports`)。

## 4. DragonOS 实现原理

### 4.1 统一渲染流水线

三种 proc 文件共用一条流水线(`render.rs`):

1. **`open()`**(`MountProcFileOps::open` 或经 symlink 打开 `/proc/self/mounts`)
- 解析目标:`ProcPidTarget` → 线程组 leader `ProcessControlBlock`
2. **`collect_visible_mounts()`**(`collect.rs`)
- 遍历目标 `mnt_ns.mount_list()`,按 mount id 排序
- 用目标 `fs_struct.root()` 做 **可见性裁剪**(`visible_mountpoint`)
3. **`MountProcFields::from_entry()`**(`fields.rs`)
- 为每个 `ProcMountEntry` 快照 devname、fstype、各类 options、mountinfo root/tags 等
4. **按 `ProcMountRenderKind` 调用 `format::*_line::render`**
5. 将完整文本写入 `FilePrivateData::Procfs.data`
6. **`read_at()`** 仅通过 `read_cached_mount_file()` → `proc_read()` 从缓存拷贝

因此当前模型是:**open 时生成整文件快照,同一次打开期间 read 不再重新遍历挂载树**。

### 4.2 目标进程视角

- `/proc/mounts` → `self/mounts` → 当前进程的 pid 目录下的 `mounts`
- `/proc/[pid]/mounts|mountinfo|mountstats` 在 open 时固定绑定该 `pid` 对应线程组的 namespace 与 root

导出内容反映的是 **目标进程的 `mnt_ns` + `fs root`**,不是读取者自己的挂载表(除非读取的就是自己的 `/proc/self/...`)。

### 4.3 可见性裁剪

`collect.rs` 中 `visible_mountpoint(mount_path, root_path)`:

- 目标 root 为 `/` 时,挂载点路径原样导出
- 目标处于 chroot 等受限 root 时,只保留该 root 子树内的 mount,并将显示路径归一化到以 `/` 为根的视图

### 4.4 选项与传播字段的拆分

| 字段来源 | 用于 | 说明 |
|----------|------|------|
| `MountFlags::proc_rw_token()` | mounts / mountinfo per-mount | `ro` 或 `rw` |
| `MountFlags::proc_per_mount_options()` | mountinfo per-mount | `nosuid,nodev,...`,不含 `rw` |
| `MountFlags::proc_super_block_options()` + sb 只读状态 | mountinfo superblock 段 | `sync,mand,...` |
| `FileSystem::proc_show_mount_options()` | mounts 行、mountinfo superblock 段 | 文件系统私有选项 |
| `MountPropagation::proc_mountinfo_tags()` | 仅 mountinfo 尾部 | `shared:N` 等,**不进入 mounts** |

`mounts_line` 使用预合并的 `mounts_options`;`mountinfo_line` 将 per-mount 与 superblock 选项用 `-` 分隔,再追加 propagation tags。

### 4.5 三种格式的职责划分

- **`format/mounts_line.rs`**:设备、挂载点、类型、选项、`0 0`
- **`format/mountinfo_line.rs`**:id、parent、major:minor、root、挂载点、选项段、`-`、fstype、super 选项、tags
- **`format/mountstats_line.rs`**:通用 `device ... mounted on ...` 前缀 + 可选 fs stats

文件系统差异通过 VFS trait 钩子注入,procfs 只负责通用行结构与转义。

## 5. 当前接口的语义特点

### 5.1 `mounts`

兼容性强、字段少;**不包含** propagation 标签。与 Linux 一样,应通过 `/proc/mounts` symlink 访问当前进程视图。

### 5.2 `mountinfo`

恢复挂载拓扑与传播属性的首选接口;per-mount 与 superblock 选项、传播标签分列展示。

### 5.3 `mountstats`

- 不是 mount 变更通知接口
- 同一次 `open()` 内内容为快照;重新 `open()` 可看到更新后的挂载集合与统计
- 行格式允许 `device` 或 `no device` 前缀(由 devname 是否为空决定,测例见 `proc_mount_exports.cc`)

## 6. 与 Linux 的实现差异

### 6.1 总体差异概览

| 维度 | Linux | DragonOS 当前实现 |
|------|-------|-------------------|
| 打开方式 | `seq_file` + 迭代器 | `open()` 时一次性渲染并缓存 |
| 读取方式 | 读时按需生成 | 从 `FilePrivateData` 缓存读取 |
| `/proc/mounts` | symlink → `self/mounts` | 已实现(`MountsSymOps`) |
| 视角绑定 | 目标 task 的 `mnt_ns + fs root` | 同左(`collect_visible_mounts`) |
| `mounts` / `mountinfo` poll | 支持 mount namespace 事件 | 未实现 |
| 遍历基础 | namespace list + cursor | `mnt_ns.mount_list()` 排序后迭代 |
| 代码组织 | `fs/proc_namespace.c` 等 | `procfs/mount/{collect,fields,format,render,inode}` |

### 6.2 Linux 的 `seq_file` 语义

Linux 使用 `mounts_open_common()` + `seq_file` 在读取过程中迭代 mount 列表。DragonOS 选择在 open 时拼完整字符串并缓存,实现更简单,同一次 fd 内结果稳定,但与 Linux 的迭代模型不完全等价。

### 6.3 `mounts` / `mountinfo` 的 `poll`

Linux 可通过 mount namespace 事件对 `mounts` / `mountinfo` 做 `poll`/`epoll`。DragonOS 尚未实现 namespace 事件序号与等待队列,不能作为挂载变更通知源。

### 6.4 `mountstats` 的动态性与 `poll`

Linux 无专门的 `mountstats` poll 语义;DragonOS 同样不为 `mountstats` 发明额外 poll。统计与拓扑变化通过重新打开文件观察。

### 6.5 可见性裁剪语义

Linux 使用 `seq_path_root` 等基于路径对象的 root 裁剪。DragonOS 当前基于 **绝对路径字符串** 与目标 `fs root` 比较,大方向一致,细节上与 Linux 路径对象语义仍有差距。

### 6.6 遍历与权威数据源

Linux 以 namespace 级 mount 链表为权威数据源。DragonOS 从 `MntNamespace::mount_list()` 取表并排序,而非从单棵 mount 树 DFS;后续若要对齐 Linux 迭代顺序与事件模型,需要在 `MntNamespace` 侧继续演进。

## 7. 当前适用场景与建议

已支持:

- 通过 `/proc/mounts`(symlink)或 `/proc/self/mounts` 读取当前进程挂载表
- 调试时读取 `/proc/[pid]/mounts`、`mountinfo`、`mountstats`
- 容器/命名空间工具解析 `mountinfo` 中的 propagation 字段

需注意:

- 依赖 **mount namespace `poll` 通知** 的用户态工具尚未兼容
- 强依赖 Linux `seq_file` 逐行迭代语义的程序可能观察到行为差异
- 修改导出逻辑时,应同时更新 `procfs/mount/` 与 `proc_mount_exports` 测例

## 8. 小结

DragonOS 将 proc 挂载导出收拢到 **`kernel/src/filesystem/procfs/mount/`**:

- **inode 层**:`/proc/mounts` symlink + `/proc/[pid]/*` 统一 `MountProcFileOps`
- **数据层**:`collect` → `fields` 快照 → `format` 三种行渲染
- **选项语义**:传播标签仅在 `mountinfo`;`MountFlags` 与 VFS 钩子分工导出

对外功能定位已接近 Linux;底层仍为 **open 快照 + 字符串裁剪**,在 `poll`、迭代模型与路径语义上继续演进。
2 changes: 1 addition & 1 deletion kernel/src/filesystem/procfs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub mod kmsg;
mod kmsg_file;
mod loadavg;
mod meminfo;
mod mounts;
mod mount;
mod net;
mod pid;
pub mod root;
Expand Down
81 changes: 81 additions & 0 deletions kernel/src/filesystem/procfs/mount/collect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use alloc::{string::String, string::ToString, sync::Arc, vec::Vec};

use system_error::SystemError;

use crate::{filesystem::vfs::MountFS, process::ProcessControlBlock};

#[derive(Debug)]
pub(crate) struct ProcMountEntry {
pub mount: Arc<MountFS>,
pub mountpoint_display: String,
pub parent_mount_id: usize,
}

pub(crate) fn collect_visible_mounts(
target: &Arc<ProcessControlBlock>,
) -> Result<(Vec<ProcMountEntry>, String), SystemError> {
let nsproxy = target.nsproxy();
let mount_list = nsproxy.mnt_ns.mount_list();
let root_path = target
.fs_struct()
.root()
.absolute_path()
.unwrap_or_else(|_| "/".to_string());

let mut mounts = mount_list
.clone_inner()
.into_iter()
.map(|(path, mfs)| (path.as_str().to_string(), mfs))
.collect::<Vec<_>>();

mounts.sort_by_key(|(_, mfs)| {
let mount_id: usize = mfs.mount_id().into();
mount_id
});

let mut entries = Vec::new();
for (mount_path, mount) in mounts {
let Some(mountpoint_display) = visible_mountpoint(&mount_path, &root_path) else {
continue;
};

let parent_mount_id: usize = mount
.self_mountpoint()
.map(|mountpoint_inode| mountpoint_inode.mount_fs().mount_id().into())
.unwrap_or_else(|| mount.mount_id().into());

entries.push(ProcMountEntry {
mount,
mountpoint_display,
parent_mount_id,
});
}

Ok((entries, root_path))
}

fn visible_mountpoint(mountpoint: &str, root_path: &str) -> Option<String> {
if root_path == "/" {
return Some(mountpoint.to_string());
}

let root_prefix_with_slash = if root_path.ends_with('/') {
root_path.to_string()
} else {
format!("{root_path}/")
};

if mountpoint == root_path {
return Some("/".to_string());
}

if let Some(stripped) = mountpoint.strip_prefix(&root_prefix_with_slash) {
return Some(if stripped.is_empty() {
"/".to_string()
} else {
format!("/{stripped}")
});
}

None
}
24 changes: 24 additions & 0 deletions kernel/src/filesystem/procfs/mount/escape.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use alloc::string::String;

pub(crate) fn escape_mount_token(input: &str, escape_hash: bool) -> String {
escape_proc_field(input, escape_hash)
}

pub(crate) fn escape_path_token(input: &str) -> String {
escape_proc_field(input, false)
}

fn escape_proc_field(input: &str, escape_hash: bool) -> String {
let mut escaped = String::with_capacity(input.len());
for ch in input.chars() {
match ch {
' ' => escaped.push_str("\\040"),
'\t' => escaped.push_str("\\011"),
'\n' => escaped.push_str("\\012"),
'\\' => escaped.push_str("\\134"),
'#' if escape_hash => escaped.push_str("\\043"),
_ => escaped.push(ch),
}
}
escaped
}
Loading
Loading