很多人第一次理解 SSD 的 Write Path 时,脑海中可能会有这样一幅画面:
Host 生成一条 Write 命令,把要写入的数据装进命令里,然后一起发送给 SSD。
但实际情况并不是这样。
一条 NVMe Write 命令只有 64 Byte,真正的 4KB 用户数据并不在命令里。
Write 命令更像是一张“写入任务单”。它只告诉 SSD:这次要写什么、写到哪里,以及真正的数据放在 Host 内存的什么位置。
也就是说,命令和数据其实是分开的。
这一篇,我们就从一条已经放进 Submission Queue 的 Write 命令开始,看看命令和数据究竟是怎样进入 SSD 的。
一、先假设有一笔 4KB 写请求
为了方便理解,我们假设操作系统现在要向 SSD 写入一笔 4KB 数据。
这笔数据对应的起始逻辑地址是 SLBA 1000。
此时,操作系统和 NVMe 驱动已经完成了前面的准备工作:一边是已经放在 Host 内存里的 4KB 用户数据,另一边是一条描述这次写入任务的 NVMe Write 命令。
这条命令中包含很多字段,但这一篇只需要认识其中五个。
Opcode 表示要执行什么操作。这里是 Write,SSD 看到它,就知道这是一笔写请求。
NSID 表示数据要写进哪个 Namespace。因为一块 NVMe SSD 可以包含多个 Namespace,命令必须说明自己的目标。
SLBA 表示从哪个逻辑块地址开始写。这里是 SLBA 1000。
NLB 用来表示写入长度。这个字段按 0 开始计数,因此字段值为 0 时,实际表示写入 1 个逻辑块。假设一个 LBA 是 4KB,那么这笔 4KB 写请求只涉及 1 个 LBA。
PRP 则用来描述:真正的 4KB 数据位于 Host 内存的什么位置。

注意,最后一项写的是“数据位置”,而不是“数据内容”。
真正的 4KB 数据仍然放在 Host 内存里。
二、Write 命令先进入 Submission Queue
操作系统准备好 Write 命令以后,会把它放进 Submission Queue,简称 SQ。
SQ 可以理解成 Host 为 SSD 准备的一条“任务队列”。
它位于 Host 内存中,由多个固定大小的槽位组成。每个槽位可以存放一条 64 Byte 的 NVMe 命令。
当一条新的 Write 命令被放进 SQ 时,它通常会被写入当前队尾对应的空闲位置,随后 SQ 的 Tail,也就是队尾指针,向前移动。
但走到这里,并不代表 SSD 已经知道有新任务了。
因为 SQ 位于 Host 内存中。Host 只是把命令放进了队列,还需要再通知 SSD:
我刚刚往队列里放了一条新命令,你可以来取了。
这个通知动作,就是更新 Doorbell。
三、Doorbell 真的像“按门铃”
Doorbell 这个名字非常形象。
Host 把新命令放进 SQ,就像把一个包裹放在门口。放好以后,它还需要按一下门铃,通知屋里的人出来取件。
在真实的 NVMe 流程中,Host 更新的是对应 Submission Queue 的 SQ Tail Doorbell。
写入的新值表示:
SQ 的队尾已经推进到了这里,在旧队尾和新队尾之间,出现了新的命令。
这里有一个很容易被误解的地方:
Doorbell 本身既不携带 Write 命令,也不携带那份 4KB 数据。
它只是一次通知。
Host 写入 Doorbell 的不是整条命令,而是更新后的 SQ Tail 位置。真正的命令仍然躺在 Host 内存里的 SQ 中,等待 SSD 去读取。
所以,“Host 向 SSD 提交了一条命令”并不等于“Host 把 64 Byte 命令写进了 Doorbell”。
真实过程是:Host 先把命令放进 SQ,再通过 Doorbell 告诉 SSD 队列发生了变化。

四、SSD 先取回 64 Byte 命令
SSD Controller 得知 SQ Tail 发生变化后,就知道对应的 Submission Queue 里出现了新命令。
它会从之前处理到的位置开始,依次读取新增的 SQ Entry。
我们这个例子中的 SQ Entry,就是那条 64 Byte 的 Write 命令。
命令被取回以后,SSD 才能开始解析其中的信息:
看到 Opcode,它知道这是一笔 Write;
看到 NSID,它知道这笔请求属于哪个 Namespace;
看到 SLBA 和 NLB,它知道 Host 要修改哪一段逻辑地址;
看到 PRP,它知道真正的 4KB 数据放在 Host 内存的什么位置。
到这里,进入 SSD 的还只有“任务单”。
真正的 4KB 用户数据仍然没有进入 SSD。
五、SSD 再根据 PRP 搬运 4KB 数据
PRP 并不是用户数据本身,它保存的是与数据缓冲区有关的地址信息。
SSD 解析完 Write 命令后,会按照 PRP 所描述的位置,通过 DMA 把真正的 4KB 数据从 Host 内存搬运到 SSD 内部的数据缓冲区。
这里就能清楚地看到两条不同的路径。
第一条是命令路径:
64 Byte Write 命令进入 Submission Queue,Host 更新 Doorbell,SSD 随后取回并解析命令。
第二条是数据路径:
4KB 用户数据仍放在 Host 内存中,SSD 根据 PRP 找到它,再通过 DMA 将其搬入 SSD 内部缓存。

两条路径并不是完全独立的。
SSD 必须先拿到并解析 Write 命令,找到其中的 PRP,才知道应该去 Host 内存的什么位置读取数据。
因此,命令更像一张取货单,而 PRP 给出了货物所在的地址。
至于这笔数据进入 SSD 后,具体暂存在 Controller 的 SRAM、外部 DRAM,还是其他数据缓冲区,要看 SSD Controller 和固件的具体实现,不能一概写成“进入 DRAM”。
所以在这一系列文章中,我们统一把它称为“SSD 内部缓存”。
六、到这里,数据还没有写进 NAND
当 64 Byte 命令和 4KB 用户数据都进入 SSD 后,这笔 Write 请求才真正从 Host 侧进入了 SSD 内部。
但此时,数据通常还只是进入了内部缓存,并不代表它已经写进 NAND,更不代表整个 Write 流程已经结束。
接下来,SSD 内部还要处理一连串问题:
这笔逻辑地址应该对应哪个物理地址?
FTL 应该从哪里分配新的物理空间?
旧的 L2P 映射什么时候失效,新的映射什么时候生效?
4KB Host 数据如何组织成适合 NAND 写入的单位?
数据进入缓存以后,SSD 什么时候可以向 Host 报告命令完成?
如果中途突然掉电,又该怎样保证数据和映射关系不会丢失?
这些才是 Write Path 真正进入 SSD Firmware 和 FTL 之后的核心内容。

这一篇解决的是:
一条 Write 命令和它对应的数据,究竟是怎样进入 SSD 的?
下一篇,我们将继续沿着这笔 4KB 数据往下走:当数据进入 SSD 内部缓存以后,FTL 究竟怎样接手,又怎样为它找到一块真正可以写入的 NAND 空间。
从下一篇开始,Write Path 将进入 SSD Firmware 内部,也是这组文章真正深入的部分。

Alipay
Wechat