块存储
定义沙箱的固定块存储角色、构建与发现方式、客户机可见能力、请求完成路径,以及宿主存储与快照契约。
译自microsoft/nvx · doc/design/3.4-block-storage.md。技术细节以英文原文为准。
如表 3.11 所示,沙箱存储使用两个固定角色,而不是任意磁盘。
表 3.11. 沙箱块角色。
| 角色 | 访问 | 用途 |
|---|---|---|
distro |
只读 | 精选的基础操作系统层 |
scratch |
读写 | 私有 ext4 上层与工作存储 |
角色是唯一的,并按固定顺序出现;省略 distro 角色会让其插槽留空。基于磁盘的拓扑以 scratch 结束,RAM 后备的叠加布局除外,它恰好使用一个只读的 distro 块。定义身份的是角色,而不是 Linux 的枚举顺序。
3.4.1 构建与发现
每个附件都必须使用其角色的访问模式。VMM 通过通用磁盘接口打开宿主存储,并在角色的固定插槽上为每个角色创建一个单队列 virtio-blk 设备,不使用 PCI 枚举。它为每个块向内核命令行添加一个由机器配置文件掌管的发现标记。客户机内核依据该标记创建设备,在角色的固定特性集合内协商特性,并把分离 virtqueue 放在客户机 RAM 中。客户机代理按 MMIO 地址而不是按设备名称来识别每个角色。
3.4.2 客户机可见的能力
每个角色提供一组固定的能力:virtio 1.0 分离环(带间接描述符和事件索引通知抑制)、每请求分段上限、块大小与拓扑报告,以及带回写缓存缓存和显式刷新的写回缓存。distro 角色还会被公布为只读。绝不提供 discard。设备报告后端存储的容量以及它的逻辑与物理块大小。
3.4.3 请求与完成路径
MMIO 窗口是用于配置和队列通知的控制平面;块数据绝不经过它。一个请求是客户机 RAM 中的一条描述符链,其中包含带有操作与起始扇区的头部、任意数据缓冲区以及一个状态字节。客户机发布该描述符链,并且除非设备抑制了通知,否则通知队列,从而唤醒设备的 I/O 工作线程。
工作线程直接从客户机内存读取请求,并并发处理多个请求。它把每个 virtio 扇区地址转换为后端存储的逻辑块,并拒绝那些不是从逻辑块边界开始、或者长度不是逻辑块整数倍的读写;物理块大小只是给客户机的对齐提示。被接受的读写成为异步的分散-聚集 I/O。单次写入不会被强制写入稳定存储;客户机的刷新会成为后端缓存刷新。完成时,设备存储成功、I/O 错误或不支持的状态,发布一个已用环条目,并且除非客户机抑制了通知,否则通过角色的共享状态字和边沿中断向客户机发出信号。
3.4.4 宿主存储与访问强制
由于该设备只依赖通用磁盘接口,它的客户机可见行为与宿主存储类型无关。后端看到的是块偏移和缓冲区;文件系统语义完全留在客户机中。只读访问在三层上强制执行:客户机被告知该设备是只读的,设备拒绝写入和 discard 请求,而 VMM 以不具备写权限的方式打开宿主文件。scratch 是唯一可写的角色。
一个快照目的地或一次恢复会让每个块都具备快照能力。此后每个块都必须是非空、按 512 字节对齐的原始普通文件,使用带缓存的 I/O,并且不是通过最终符号链接到达的。VMM 保留该已打开的文件用于刷新和配对暂存盘的发布。
3.4.5 快照契约与静默
快照契约记录每个块的角色、只读标志、逻辑长度、逻辑与物理块大小以及身份。每个被消费的块共享同一个身份,即一个经调用方认证的不可变存储世代;参见快照身份。平台层记录为未绑定,因为镜像绑定尚未被消费,因此恢复可以提供相同几何形状的不同层。可写 scratch 遵循以下两种策略之一:
- 配对。 捕获发布精确的 scratch 镜像及其长度与身份。恢复拒绝调用方提供的 scratch 块,并把所记录的产物实体化为私有副本、reflink 写时复制克隆或一次性的认领延续。
- 全新。 捕获发生在 scratch 被挂载之前,并且不发布任何 scratch 产物。恢复需要一个几何形状匹配的新可写文件。
传输和队列进度与其他设备状态一起保存。停止一个队列会阻止新的描述符进入,并把每一个已接受的 I/O 排空到已用环上的一次完成之内,且不超过有界的快照静默超时。控制器只在排空之后刷新并复制 scratch。