17 KiB
17 KiB
挂载传播性机制
1. 概述
挂载传播性(Mount Propagation)是 Linux 内核在 2.6.15 版本引入的一项重要特性,DragonOS 对此进行了完整实现。该机制控制在一个挂载点上发生的挂载/卸载事件是否以及如何传播到其他相关的挂载点。
1.1 为什么需要挂载传播性?
在容器化和命名空间隔离的场景下,不同进程可能拥有不同的挂载命名空间(Mount Namespace)。传统的挂载行为无法满足以下需求:
- 共享存储:多个容器需要看到相同的存储变化
- 隔离性:某些容器的挂载变化不应影响其他容器
- 灵活配置:不同目录树需要不同的传播策略
1.2 核心概念
挂载传播性引入了以下核心概念:
| 概念 | 说明 |
|---|---|
| Peer Group | 一组共享挂载事件的挂载点集合 |
| 传播类型 | 定义挂载点如何参与事件传播 |
| Bind Mount | 将一个目录树绑定到另一个位置 |
| 命名空间 | 挂载点的隔离边界 |
2. 传播类型
DragonOS 支持四种传播类型,每种类型定义了不同的事件传播行为:
2.1 Shared(共享)
┌─────────────────────────────────────────────────────────┐
│ Peer Group │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Mount A │ ◄─────► │ Mount B │ ◄─────► │ Mount C │ │
│ │ shared │ │ shared │ │ shared │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │
│ └───────────────────┴───────────────────┘ │
│ 双向传播:mount/umount 事件 │
└─────────────────────────────────────────────────────────┘
特性:
- 属于同一 Peer Group 的挂载点双向传播事件
- 在任一 Peer 上的 mount/umount 操作会传播到所有其他 Peer
- 通过
MS_SHARED标志设置
典型用例:
- 多个容器需要共享相同的存储视图
- 跨命名空间的实时同步
2.2 Private(私有)
┌─────────────┐ ┌─────────────┐
│ Mount A │ │ Mount B │
│ private │ ✗ │ private │
│ │◄───────►│ │
└─────────────┘ └─────────────┘
不传播任何事件
特性:
- 挂载事件既不发送也不接收
- 每个挂载点完全独立
- 这是新建挂载的默认类型
典型用例:
- 需要完全隔离的容器
- 临时挂载点
2.3 Slave(从属)
┌─────────────┐ ┌─────────────┐
│ Master │ ───────►│ Slave │
│ shared │ │ slave │
│ │◄─ ✗ ────│ │
└─────────────┘ └─────────────┘
单向传播:Master → Slave
特性:
- 只接收来自 Master 的事件,不向外传播
- 可以有自己的本地挂载变化,但不影响 Master
- Master 必须是 Shared 类型
典型用例:
- 只读共享视图
- 容器需要看到主机的挂载变化,但不能影响主机
2.4 Unbindable(不可绑定)
┌─────────────┐ ┌─────────────┐
│ Mount A │ ✗ │ Mount B │
│ unbindable │◄───────►│ any │
│ │ 禁止bind │ │
└─────────────┘ └─────────────┘
特性:
- 不能被 bind mount
- 不参与任何传播
- 最强的隔离级别
典型用例:
- 敏感数据目录
- 防止意外暴露的系统目录
3. Peer Group 机制
3.1 什么是 Peer Group?
Peer Group 是共享挂载传播关系的挂载点集合。同一 Peer Group 内的所有 Shared 挂载点会双向传播挂载事件。
┌──────────────────────────────────┐
│ Peer Group (ID=42) │
│ │
Namespace A │ ┌─────────┐ │
┌─────────────────┼───│ /mnt/a │ │
│ │ │ shared │ │
│ │ └────┬────┘ │
│ │ │ │
└─────────────────┼────────┼─────────────────────────┤
│ │ │
Namespace B │ │ │
┌─────────────────┼────────┼─────────────────────────┤
│ │ │ │
│ │ ┌────▼────┐ │
│ │ │ /mnt/b │ │
│ │ │ shared │ │
│ │ └─────────┘ │
└─────────────────┼──────────────────────────────────┤
└──────────────────────────────────┘
3.2 Peer Group 的形成
Peer Group 在以下情况形成或扩展:
- 设置 Shared 类型:当挂载点首次被设为 Shared,分配新的 Group ID
- Bind Mount:对 Shared 挂载执行 bind mount,新挂载加入同一 Peer Group
- 命名空间复制:
unshare(CLONE_NEWNS)时,Shared 挂载被复制并加入同一 Peer Group
3.3 Group ID 分配
每个 Peer Group 由唯一的 Group ID 标识:
Group ID 分配器
┌─────────────────────────────────────┐
│ ID Pool: [1, 2, 3, 4, 5, ...] │
│ │
│ 已分配: {1 → Group A, 3 → Group B} │
│ 可用: {2, 4, 5, ...} │
└─────────────────────────────────────┘
- Group ID 从 1 开始分配
- 0 表示无效/未加入任何组
- 当 Peer Group 为空时,ID 可回收
4. 事件传播流程
4.1 Mount 事件传播
当在 Shared 挂载点上创建新挂载时:
步骤 1: 在源挂载点创建子挂载
┌──────────────┐
│ /mnt/a │ ← mount("", "/mnt/a/sub", "ramfs", ...)
│ shared │
│ │ │
│ ┌──▼───┐ │
│ │ sub │ │
│ └──────┘ │
└──────────────┘
步骤 2: 获取 Peer Group 成员
┌──────────────────────────────────────┐
│ Peer Group 42: │
│ - /mnt/a (源) │
│ - /mnt/b (Peer) │
│ - /mnt/c (Peer) │
└──────────────────────────────────────┘
步骤 3: 向每个 Peer 传播
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ /mnt/a │ │ /mnt/b │ │ /mnt/c │
│ shared │ │ shared │ │ shared │
│ │ │ │ │ │ │ │ │
│ ┌──▼───┐ │ │ ┌──▼───┐ │ │ ┌──▼───┐ │
│ │ sub │ │ │ │ sub' │ │ │ │ sub''│ │
│ └──────┘ │ │ └──────┘ │ │ └──────┘ │
└──────────────┘ └──────────────┘ └──────────────┘
源 复制 复制
4.2 Umount 事件传播
当在 Shared 挂载点上卸载子挂载时:
步骤 1: umount("/mnt/a/sub")
┌──────────────┐
│ /mnt/a │
│ shared │
│ │ │
│ ┌──▼───┐ │ ← umount
│ │ sub │ │
│ └──────┘ │
└──────────────┘
步骤 2: 传播到所有 Peer
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ /mnt/a │ │ /mnt/b │ │ /mnt/c │
│ shared │ │ shared │ │ shared │
│ │ │ │ │ │
│ (empty) │ │ (empty) │ │ (empty) │
│ │ │ │ │ │
└──────────────┘ └──────────────┘ └──────────────┘
已卸载 传播卸载 传播卸载
4.3 传播到 Slave
Slave 挂载点单向接收事件:
┌──────────────┐ ┌──────────────┐
│ Master │ │ Slave │
│ shared │ │ slave │
│ │ │ ───► │ │ │
│ ┌──▼───┐ │ │ ┌──▼───┐ │
│ │ sub │ │ │ │ sub' │ │
│ └──────┘ │ │ └──────┘ │
└──────────────┘ └──────────────┘
│
▼
Slave 上的本地挂载
不会传播回 Master
5. 命名空间交互
5.1 命名空间复制
当调用 unshare(CLONE_NEWNS) 创建新的挂载命名空间时:
复制前(父进程的命名空间):
┌─────────────────────────────────────┐
│ Mount Namespace (Parent) │
│ │
│ / ┌──────────┐ │
│ └── mnt/ │ shared │ │
│ └── data │ Group 1 │ │
│ └──────────┘ │
└─────────────────────────────────────┘
unshare(CLONE_NEWNS) 后:
┌─────────────────────────────────────┐
│ Mount Namespace (Parent) │
│ │
│ / ┌──────────┐ │
│ └── mnt/ │ shared │◄───────┼─┐
│ └── data │ Group 1 │ │ │
│ └──────────┘ │ │ Peer
└─────────────────────────────────────┘ │ 关系
│
┌─────────────────────────────────────┐ │
│ Mount Namespace (Child) │ │
│ │ │
│ / ┌──────────┐ │ │
│ └── mnt/ │ shared │◄───────┼─┘
│ └── data │ Group 1 │ │
│ └──────────┘ │
└─────────────────────────────────────┘
关键行为:
- Private 挂载:简单复制,无 Peer 关系
- Shared 挂载:复制后加入同一 Peer Group,建立跨命名空间传播
- Slave 挂载:保持 Slave 关系
- Unbindable 挂载:不可复制到新命名空间
5.2 跨命名空间传播示例
时间线:
───────────────────────────────────────────────────►
T1: 父进程创建 shared 挂载
Parent NS: /mnt/shared (Group 1)
T2: 子进程 unshare(CLONE_NEWNS)
Parent NS: /mnt/shared (Group 1) ◄──┐
│ Peer
Child NS: /mnt/shared (Group 1) ◄──┘
T3: 父进程在 /mnt/shared/sub 挂载
Parent NS: /mnt/shared/sub ←── 新挂载
│
▼ 传播
Child NS: /mnt/shared/sub ←── 自动出现
T4: 子进程也能看到 /mnt/shared/sub
6. 传播类型转换
6.1 状态转换图
┌───────────────┐
MS_SHARED │ │ MS_PRIVATE
┌───────────►│ SHARED │◄───────────┐
│ │ │ │
│ └───────┬───────┘ │
│ │ │
│ │ MS_SLAVE │
│ ▼ │
┌──────┴──────┐ ┌─────────────┐ ┌─────┴───────┐
│ │ │ │ │ │
│ PRIVATE │◄─────│ SLAVE │─────►│ UNBINDABLE │
│ │ │ │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
MS_PRIVATE MS_UNBINDABLE
6.2 转换规则
| 源类型 | 目标类型 | 操作 | 副作用 |
|---|---|---|---|
| Private | Shared | mount --make-shared |
分配新 Group ID |
| Shared | Private | mount --make-private |
从 Peer Group 移除 |
| Shared | Slave | mount --make-slave |
变为 Peer Group 的接收者 |
| Slave | Shared | mount --make-shared |
断开与 Master 的连接 |
| * | Unbindable | mount --make-unbindable |
清除所有关系 |
7. 设计原则
7.1 最小惊讶原则
- 新挂载默认为 Private,不产生意外的副作用
- 只有显式设置 Shared 才参与传播
- 传播行为明确且可预测
7.2 性能考虑
- Peer Group 使用全局注册表管理,O(1) 查找
- 传播操作使用延迟执行,避免阻塞挂载操作
- 弱引用(Weak)避免循环引用和内存泄漏
7.3 一致性保证
- 使用原子操作和锁保护传播状态
- 传播失败不影响原始操作
- 支持部分传播后的状态恢复
8. 与 Linux 的兼容性
DragonOS 的挂载传播性实现遵循 Linux 语义:
| 特性 | Linux | DragonOS |
|---|---|---|
| Shared 传播 | ✓ | ✓ |
| Private 隔离 | ✓ | ✓ |
| Slave 单向传播 | ✓ | ✓ |
| Unbindable | ✓ | ✓ |
| 跨命名空间传播 | ✓ | ✓ |
| 递归传播 (MS_REC) | ✓ | ✓ |
| /proc/self/mountinfo | ✓ | 部分 |