挂载传播性机制

1. 概述

挂载传播性(Mount Propagation)是 Linux 内核在 2.6.15 版本引入的一项重要特性,DragonOS 对此进行了完整实现。该机制控制在一个挂载点上发生的挂载/卸载事件是否以及如何传播到其他相关的挂载点。

1.1 为什么需要挂载传播性?

在容器化和命名空间隔离的场景下,不同进程可能拥有不同的挂载命名空间(Mount Namespace)。传统的挂载行为无法满足以下需求:

  1. 共享存储:多个容器需要看到相同的存储变化

  2. 隔离性:某些容器的挂载变化不应影响其他容器

  3. 灵活配置:不同目录树需要不同的传播策略

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 在以下情况形成或扩展:

  1. 设置 Shared 类型:当挂载点首次被设为 Shared,分配新的 Group ID

  2. Bind Mount:对 Shared 挂载执行 bind mount,新挂载加入同一 Peer Group

  3. 命名空间复制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

部分

9. 参考资料

  1. Linux Kernel Documentation: Shared Subtrees

  2. LWN.net: Shared subtrees