目录
[二、PI 三件套结构](#二、PI 三件套结构)
[1. Guard(守卫字段,2 字节)](#1. Guard(守卫字段,2 字节))
[2. Application Tag(应用标签,2 字节)](#2. Application Tag(应用标签,2 字节))
[3. Reference Tag(引用标签,4 字节)](#3. Reference Tag(引用标签,4 字节))
[三、PI 类型:Type 1 / 2 / 3](#三、PI 类型:Type 1 / 2 / 3)
[写路径(Host → NAND)](#写路径(Host → NAND))
[读路径(NAND → Host)](#读路径(NAND → Host))
[五、NVMe 命令中的 PI 控制字段](#五、NVMe 命令中的 PI 控制字段)
[场景 1:PCIe 链路静默翻转](#场景 1:PCIe 链路静默翻转)
[场景 2:固件写错 LBA(Write Misroute)](#场景 2:固件写错 LBA(Write Misroute))
[场景 3:NAND 读出位翻转(ECC 无法纠正的多 bit 错误)](#场景 3:NAND 读出位翻转(ECC 无法纠正的多 bit 错误))
[场景 4:读错 LBA(Read Misroute)](#场景 4:读错 LBA(Read Misroute))
[七、PI 数据解析示例](#七、PI 数据解析示例)
一、背景:为什么需要端到端保护
传统存储链路中,数据从主机内存经过 PCIe 总线、NVMe 控制器、NAND 介质,每个环节都可能发生静默错误(Silent Data Corruption):
Host Memory → PCIe → NVMe Controller → NAND
↑ ↑ ↑ ↑
内存翻转 传输错误 固件 bug 介质位翻转
ECC 只保护 NAND 介质层,无法覆盖链路全程。PI(Protection Information,保护信息)的目标就是实现端到端(End-to-End)的数据完整性校验。
二、PI 三件套结构
每个逻辑块(LBA)附加 8 字节 Metadata,称为 DIF(Data Integrity Field),正是 PI 三件套:
┌─────────────────────────────────────────────────────┐
│ Logical Block (512B or 4KB) │
├──────────────┬──────────────────┬───────────────────┤
│ Guard (2B) │ App Tag (2B) │ Reference Tag (4B)│
└──────────────┴──────────────────┴───────────────────┘
↑ ↑ ↑
数据校验 应用标签 LBA 标签
1. Guard(守卫字段,2 字节)
本质:数据块的 CRC 校验值
-
算法:CRC-16(T10 标准,多项式
x¹⁶ + x¹² + x⁵ + 1,即 ITU-T CRC-16) -
覆盖范围:整个逻辑块的用户数据(512B 或 4096B)
-
作用:检测数据内容是否被篡改或损坏
Guard = CRC16(User Data[0..511])
2. Application Tag(应用标签,2 字节)
本质:主机应用层自定义标签
- 完全由 Host 应用控制,控制器不关心语义
- 典型用途:标记数据归属、版本号、IO 流 ID
- 当 Application Tag Mask 全为 0 时,控制器跳过检查
- 当值为
0xFFFF时,通常表示该字段被禁用
3. Reference Tag(引用标签,4 字节)
本质:逻辑块地址(LBA)的指纹
-
存储该逻辑块对应的 LBA 低 32 位
-
多块传输时,每个后续块的 Reference Tag 递增 +1
-
作用:检测写错位置 (Write-to-Wrong-Location)或读错 LBA 的错误
Block[0]: Reference Tag = LBA
Block[1]: Reference Tag = LBA + 1
Block[2]: Reference Tag = LBA + 2
三、PI 类型:Type 1 / 2 / 3
NVMe 规范定义了三种 PI 类型,差异在于字段的检查策略:
| 特性 | Type 1 | Type 2 | Type 3 |
|---|---|---|---|
| Guard 检查 | ✅ | ✅ | ✅ |
| Reference Tag 检查 | ✅(LBA 关联) | ✅(命令指定) | ❌(固定 0xFFFFFFFF) |
| App Tag 检查 | 可选 | 可选 | ❌ |
| LBA 绑定 | 强绑定 | 命令级绑定 | 无绑定 |
| 典型场景 | 通用场景 | 虚拟化/快照 | 不关心位置的场景 |
Type 1 最常用:Reference Tag 与 Namespace 的物理 LBA 强绑定,防写错位。
Type 2 适合虚拟化:Reference Tag 由每条 IO 命令的 EILBRT 字段指定,逻辑地址可与物理 LBA 解耦。
Type 3 最宽松:不检查 Reference Tag,适用于不关心写入位置的顺序写场景(如日志盘)。
四、端到端保护完整流程
写路径(Host → NAND)
┌──────────────────────────────────────────────────────────────────┐
│ 1. 主机应用层 │
│ 生成 PI(Guard=CRC16(Data), RefTag=LBA, AppTag=自定义) │
│ 数据 + PI 一起通过 DIX 或 DIF 模式提交给 HBA │
└─────────────────────────┬────────────────────────────────────────┘
│
┌─────────────────────────▼────────────────────────────────────────┐
│ 2. NVMe 控制器(Host 侧) │
│ PRACT=0:透传 PI,不修改 │
│ PRACT=1:由控制器生成 PI(Host 不需要自己算) │
│ PRCHK 字段控制检查哪些字段 │
└─────────────────────────┬────────────────────────────────────────┘
│ PCIe 传输(数据 + PI 一起走)
┌─────────────────────────▼────────────────────────────────────────┐
│ 3. SSD 固件(Firmware) │
│ 收到数据后验证 PI: │
│ - Guard 是否匹配 CRC16(Data)? │
│ - Reference Tag 是否匹配当前 LBA? │
│ - App Tag 是否符合预期? │
│ 通过:连同 PI 一起写入 NAND │
│ 失败:返回错误,拒绝写入 │
└─────────────────────────┬────────────────────────────────────────┘
│
┌─────────────────────────▼────────────────────────────────────────┐
│ 4. NAND 介质 │
│ 物理存储:User Data + PI(8B)+ NAND ECC │
└──────────────────────────────────────────────────────────────────┘
读路径(NAND → Host)
NAND 读出 → NAND ECC 纠错 → 固件验证 PI → 返回 Host → Host 验证 PI
↑ ↑
第一道关卡 第二道关卡
两道验证形成端到端闭环,任何一个环节的错误都会被捕获。
五、NVMe 命令中的 PI 控制字段
NVMe Read/Write 命令的 DW12 包含 PI 控制位:
DW12[31:16] = LBAT (Logical Block Application Tag)
DW12[15:0] = LBATM (Logical Block Application Tag Mask)
DW13 = EILBRT (Expected Initial Logical Block Reference Tag)
DW12 bit 26 = PRACT (Protection Information Action)
DW12[29:27] = PRCHK (Protection Information Check Field)
bit 0 → 检查 Guard
bit 1 → 检查 App Tag
bit 2 → 检查 Reference Tag
PRACT 的两种模式:
PRACT = 0(透传模式)
Write: Host 提供 PI,Controller 验证后原样写入
Read: Controller 读出 PI 连同数据一起返回 Host,Host 自行验证
PRACT = 1(生成/剥离模式)
Write: Host 只提供 User Data,Controller 自动生成 PI 写入 NAND
Read: Controller 验证 PI 后,只返回 User Data(PI 被剥离)
六、典型故障场景与检测能力
场景 1:PCIe 链路静默翻转
Host 写入数据 0xABCD... → PCIe 传输后变成 0xABCE...
Guard 检查:CRC16(0xABCE...) ≠ 原始 Guard 值
结论:Guard 命中,固件返回 Write Error,数据未落盘
场景 2:固件写错 LBA(Write Misroute)
Host 发出写 LBA=0x1000 的命令
固件 bug 导致实际写入 LBA=0x2000
写入时 Reference Tag 检查:PI 中 RefTag=0x1000 ≠ 目标 LBA=0x2000
结论:Reference Tag 命中,写操作被拦截
场景 3:NAND 读出位翻转(ECC 无法纠正的多 bit 错误)
NAND ECC 纠错失败(超出纠错能力),数据已损坏
CRC16(损坏数据) ≠ 存储的 Guard 值
结论:Guard 命中,读命令返回 Unrecovered Read Error(URE)
场景 4:读错 LBA(Read Misroute)
Host 请求读 LBA=0x1000
固件返回了 LBA=0x1001 的数据(调度 bug)
Host 收到数据后检查:RefTag=0x1001 ≠ 期望的 0x1000
结论:Reference Tag 命中,Host 侧报错
七、PI 数据解析示例
以一个 512B 块 + 8B PI 为例:
偏移 0x000 ~ 0x1FF : User Data (512 字节)
偏移 0x200 : Guard[1] = 0xA3
偏移 0x201 : Guard[0] = 0x7F → Guard = 0xA37F
偏移 0x202 : App Tag[1] = 0x00
偏移 0x203 : App Tag[0] = 0x01 → App Tag = 0x0001
偏移 0x204~0x207 : Reference Tag = 0x00001000 → LBA = 0x1000
验证步骤:
import struct, crcmod
def verify_pi_type1(data_512b, pi_8b, expected_lba):
guard, app_tag, ref_tag = struct.unpack('>HHI', pi_8b)
# 1. 验证 Guard
crc16 = crcmod.predefined.mkCrcFun('crc-16') # T10 CRC-16
computed = crc16(data_512b)
assert computed == guard, f"Guard mismatch: {computed:#06x} vs {guard:#06x}"
2. 验证 Reference Tag
assert ref_tag == (expected_lba & 0xFFFFFFFF),
f"RefTag mismatch: {ref_tag:#010x} vs {expected_lba:#010x}"
3. App Tag 检查(应用层自定义逻辑)
print(f"Guard: OK | RefTag: OK | AppTag: {app_tag:#06x}")
八、配置与使用要点
Namespace 格式化时指定 PI 类型:
# NVMe CLI:格式化 Namespace,启用 PI Type 1,元数据附加在数据末尾
nvme format /dev/nvme0n1 --lbaf=1 --pi=1 --pil=1
参数说明:
--lbaf : LBA Format(选择包含 8B metadata 的格式)
--pi : Protection Information Type (1/2/3)
--pil : PI Location (0=metadata 头部, 1=metadata 尾部)
查看 Namespace 的 PI 状态:
nvme id-ns /dev/nvme0n1 | grep -E "dps|lbaf"
# dps 字段:bit[2:0] = PI Type, bit[3] = PI Location
九、总结
| 字段 | 保护目标 | 检测的故障类型 |
|---|---|---|
| Guard | 数据内容 | 位翻转、静默损坏、传输错误 |
| Reference Tag | 数据位置 | 写错 LBA、读错 LBA、Misroute |
| Application Tag | 数据归属 | 数据混用、版本错误、流隔离 |
PI 三件套通过将校验信息与数据绑定并随数据全程流动,把原本分散在各层的局部保护升级为一条完整的端到端信任链。Guard 管内容,Reference Tag 管位置,Application Tag 管归属------三者各司其职,共同构成企业级存储可靠性的基础。
你在实际项目中遇到过哪些静默数据损坏(Silent Data Corruption)的案例?或者对 PI 三件套的落地有什么疑问?欢迎在评论区留言交流,我们一起探讨。