前面几篇,我们把 NAND 的读取流程基本讲完了。

默认 read level 读不回来,就 Read Retry。 

Read Retry 还不够,就 Vth Tracking。

再不够,就 Soft Read。

Soft Decode 之后还失败,就靠 RAID 做最后一层保护。

这些文章讲的是:

数据已经读出来了,但读得不对,怎么办?

这一篇要讲另一个问题:

数据还没来得及读,因为 NAND 正在忙,怎么办?

这就要讲到 NAND 里的一个重要功能:

Suspend。

Suspend 翻译过来就是暂停。

在 NAND 里,常见的有两类:

Program Suspend。Erase Suspend。

从名字看,一个和写入有关,一个和擦除有关。

但它们真正要保护的,其实是:

read。


一、先把主线说清楚:Suspend 是给 read 让路

Program 和 Erase 都是 NAND 里的慢操作。

read 通常比较快。 program 比 read 慢。 erase 往往更慢。

尤其是 erase。

它不是擦一个 bit,也不是擦一个 page,而是按 block 擦除。

Program 也不是瞬间完成。

它不是一下子把 cell 打到目标电压,而是要经过一轮一轮 program pulse 和 verify,把阈值电压慢慢推到目标范围。

所以一个问题就出现了:

如果 NAND 正在 program 或 erase,这时候主机来了一个读请求,怎么办?

如果没有 Suspend,那就只能等。

等 program 做完。 或者等 erase 做完。 然后才能读。

从 NAND 自己的角度看,这很正常。

但从 SSD 的角度看,这就很麻烦。

因为主机的读请求往往很敏感。

你打开一个程序,需要读。 你加载一个文件,需要读。 你访问文件系统元数据,也需要读。 系统后台写日志时,前台应用也可能突然要读。

这些读请求可能很小,但它们对延迟很敏感。

用户体感中的“卡一下”,很多时候不是因为写入吞吐不够,而是某个读请求被慢操作挡住了。

所以 Suspend 的核心目的就是:

当前 NAND 正在 program 或 erase。 如果这时候来了一个 read,先把慢操作暂停一下,让 read 先执行。 read 做完以后,再 resume 回来继续原来的 program 或 erase。

一句话:

Suspend 不是让 NAND 变快,而是让 read 不要被慢操作堵死。


二、不要误解:不是正在写的 page 可以读,也不是正在擦的 block 可以读

Suspend 最容易被误解的地方在这里。

有人可能会想:

既然 block 正在 erase,可以 suspend,那是不是说明这个 block 擦到一半还能读?

不是。

有人也可能会想:

既然 page 正在 program,可以 suspend,那是不是说明这个 page 写到一半也能读?

也不是。

正在 erase 的 block,不能当成正常数据源。 正在 program 的 page,也不能当成可靠数据源。

因为它们都处在“半途中”。

Erase 还没完成。 Program 也还没完成。

这个时候去相信它们的数据,本来就是不对的。

Suspend 真正解决的问题,不是:

正在操作的这个 block/page 能不能读。

而是:

同一个 die / LUN 正在忙,导致别的位置的 read 也进不来。

这才是关键。


三、真正被占住的是 die / LUN 的执行窗口

NAND 不是每个 block 都像一个完全独立的小设备。

在实际命令执行和状态返回上,controller 更关心的是 die / LUN 这个粒度。

可以先这样理解:

一个 die / LUN 就像一个办事窗口。

这个窗口正在处理一个很慢的业务。

比如:

block A 正在 erase。 或者 page A 正在 program。

这个业务还没结束,所以这个窗口一直被占着。

这时候来了一个很快的查询业务。

比如:

主机要读 page B。 或者要读 block B 里的某个 page。

注意,B 不是 A。

不是读正在 erase 的 block。 也不是读正在 program 的 page。

只是 B 和 A 刚好在同一个窗口背后。

如果没有 Suspend,B 就只能等 A 完成。

这就很尴尬。

因为 B 本来可以很快完成,但它被 A 这个慢操作挡住了。

Suspend 的作用就是:

先暂停 A,让这个窗口处理 B。 B 处理完,再回到 A 继续做。

所以,Suspend 释放的不是正在 program 的 page,也不是正在 erase 的 block。

它释放的是:

die / LUN 的命令执行窗口。

这句话是理解 Suspend 的关键。


四、Program Suspend:写 page A 的时候,先去读 page B

先讲 Program Suspend。

Program 是写入动作。

前面讲 NAND Program 的时候说过,它不是一次完成。

NAND 会通过一轮一轮 program pulse,把 cell 的阈值电压往目标状态推。

每推一步,还要 verify 一下。

够了就停。 不够就继续。

这就是为什么 program 比 read 慢。

现在假设一个场景:

page A 正在 program。 这个 program 还没结束。 所在的 die / LUN 正在 busy。 这时候主机来了一个读请求,要读 page B。 page B 不是 page A。 page B 里的数据本来是稳定的。

如果没有 Program Suspend,读 page B 也要等 page A program 完。

但主机只是想读 page B。

它不关心 page A 还要写多久。

于是 Program Suspend 就有价值了。

controller 可以暂停 page A 的 program,让这个 die / LUN 先去完成 page B 的 read。

page B 读完以后,再 resume 回来,继续 page A 的 program。

所以 Program Suspend 的准确理解是:

写 page A 的时候,先暂停一下,让同一个 die / LUN 去读 page B。

不是:

page A 写到一半也可以读。

这两句话差别很大。

正在 program 的 page A,仍然不能当成可靠数据。

Suspend 只是让 read 有机会插进来。


五、Erase Suspend:擦 block A 的时候,先去读 block B

再看 Erase Suspend。

Erase 更典型。

因为 erase 是 block 级操作。

一个 block 要被回收,必须先把里面的有效数据搬走。

搬完以后,这个 block 才能 erase。

Erase 完成之后,它才重新变成可写的空 block。

但 erase 很慢。

所以如果某个 die / LUN 正在 erase block A,这个 die / LUN 就可能被占住一段时间。

这时候如果主机来了一个读请求,要读 block B 里的数据,就可能被挡住。

注意:

block B 不是 block A。 block B 本身没有正在 erase。 block B 的数据可以是正常数据。

问题只是:

block A 的 erase 占住了这个 die / LUN 的执行窗口。

如果没有 Erase Suspend,读 block B 只能等 block A erase 完。

但这对 read 来说很不划算。

因为 block B 的读,本来应该很快。

Erase Suspend 的作用就是:

暂停 block A 的 erase,让同一个 die / LUN 先去读 block B。 block B 读完以后,再 resume 回来继续 block A 的 erase。

所以 Erase Suspend 的准确理解是:

擦 block A 的时候,先暂停一下,让同一个 die / LUN 去读 block B。

不是:

block A 擦到一半还能正常读 block A。

正在 erase 的 block A,不能当成正常数据源。

它已经进入擦除过程。

Suspend 不是救它,也不是让它提前完成。

Suspend 只是让别的 read 不要被它堵住。


六、这两个 Suspend 的共同点:都是给 read 让路

Program Suspend 和 Erase Suspend 表面上不同。

一个暂停写。 一个暂停擦。

但它们的主线是一样的:

都是给 read 让路。

Program 太慢,不能让 read 一直等。 Erase 更慢,更不能让 read 一直等。

所以 NAND 提供了 Suspend 这种能力,让 controller 可以在慢操作中间插入 read。

这不是说任何时候都要 suspend。

也不是说只要来了 read 就一定 suspend。

controller 会判断:

这个 read 急不急? 这个 read 能不能调度到别的 die? 当前 program/erase 还剩多久? 现在 suspend 值不值得? 原来的 program/erase 会不会被拖太久?

但从功能目的上看,Suspend 的核心就是:

不要让 read 被 program/erase 长时间挡住。

这也是为什么它和用户体验关系很大。


七、给 read 让路,但不是无限让路

这里还要补一个很重要的点。

Suspend 不是说:

我把 program 或 erase 挂起来,然后 read 想来多少就来多少,想挂多久就挂多久。

不是这样的。

Suspend 给 read 打开的,只是一个有限窗口。

当前 program 或 erase 被暂停以后,controller 可以利用这个窗口去处理读请求。

但这个窗口不能无限拉长。

读完以后,controller 必须及时 resume,让原来的 program 或 erase 继续往前走。

否则会出现一个问题:

read 是快了,但原来的慢操作一直完不成。

Program 一直完不成,写入落盘就一直拖着。 Erase 一直完不成,空 block 回收就一直拖着。 空 block 回收拖久了,后面的写入也会被反噬。

所以 Suspend 不是把后台任务无限期牺牲掉。

它只是短暂让 read 插队。

更细一点看,Suspend 至少有三层边界。

第一,挂起以后不能一直不 resume。

也就是说,program/erase 不能被无限期挂在那里。

controller 必须在一个时间窗口内回来继续原来的操作。

第二,resume 以后不能马上又反复 suspend。

因为原来的 program/erase 也需要时间向前推进。

如果刚 resume,立刻又 suspend,NAND 内部可能还没来得及完成一次有效推进。

看起来一直在切换,实际上原操作没怎么前进。

第三,同一个 program/erase 不能被无限次打断。

如果一个操作被 suspend 太多次,最终还是要让它连续跑完。

否则 read 虽然被照顾了,但后台 erase、program、GC 都可能被拖垮。

所以 Suspend 的正确理解不是:

read 永远优先。

而是:

read 可以优先,但必须在不饿死原操作的前提下优先。

这才是 SSD controller 调度真正难的地方。

它要在两个目标之间找平衡:

一边是 read latency,不能让读请求被慢操作堵太久。 另一边是 program/erase progress,不能让写入和擦除一直停在半路。

因此,Suspend 不是一个简单的暂停按钮。

它更像是一个带时间预算的调度窗口。

窗口打开,read 先过。 窗口到了,必须回来继续原来的慢操作。

这句话可以概括整个机制:

Suspend 是给 read 让路,但这条路只能临时借用,不能一直占着。


八、Suspend 不是提高写速度,而是降低写入时的读延迟

这里还有一个常见误区。

有人会说:

Program Suspend 和 Erase Suspend 是不是为了提高写性能?

这个说法要小心。

Suspend 不会让 program 本身更快。

原来一个 page 要那么久写完,Suspend 之后不会神奇变短。

Suspend 也不会让 erase 本身更快。

原来一个 block 要那么久擦完,中间暂停几次以后,它甚至可能更晚完成。

所以,从单个 NAND 操作看,Suspend 不但不是加速,反而可能让原来的 program/erase 完成得更晚。

那为什么还要用?

因为 SSD 关心的不只是这个 program 什么时候完成,也不只是这个 erase 什么时候完成。

SSD 还关心:

写入压力下,read 会不会被卡住。

真实系统里,很少只有纯写。

更多时候是读写混合。

比如:

一边下载文件,一边打开软件。 一边写日志,一边读取配置。 一边解压数据,一边加载文件。 一边做 GC,一边响应主机读请求。

这些场景里,program 和 erase 会不断出现。

如果它们完全不能让路,read 的延迟就会变差。

用户看到的就是:

写入时系统变卡。 打开文件慢一拍。 程序加载突然变慢。 后台 GC 时前台读延迟飙高。

Suspend 改善的就是这种问题。

所以更准确的说法是:

Suspend 不是提高 NAND 写速度。 Suspend 是改善写入和 GC 压力下的 read latency。

也就是:

写的时候,别把读拖死。


九、为什么 read 值得优先?

原因很简单:

read 通常更短、更快,也更影响体感。

一个 read 请求,如果能及时插进来,很快就能结束。

但是如果它排在一个 program 或 erase 后面,它等待的时间可能远远超过自己真正执行的时间。

这就像一个人只是想查一句话,结果前面的人在办一个很长的业务。

查询本身只要几秒。 排队却可能排很久。

对 SSD 来说也是一样。

read 本身可能很快。

但如果被 program 或 erase 挡住,主机看到的延迟就变大了。

Suspend 的价值,就是把这种不合理的等待减少掉。

它不是让 read 的物理读取时间变短。

它是让 read 少等一点。

有时候,性能优化不是把事情做得更快。

而是不要让快事情排在慢事情后面。

Suspend 就是这个思路。


十、Suspend 期间也有边界

Suspend 听起来很灵活,但它不是随便暂停、随便操作。

有几个边界必须记住。

第一,正在操作的目标不能当成正常数据源。

正在 program 的 page,不要当成可靠 page。 正在 erase 的 block,不要当成有效 block。

能读的是别的位置。

第二,Suspend 不是取消。

Program suspend 之后,原来的 program 还要 resume。 Erase suspend 之后,原来的 erase 也要 resume。

慢操作只是被切开了,不是消失了。

第三,Suspend 有代价。

每次 suspend 和 resume 都不是免费的。

如果频繁 suspend,原来的 program/erase 会被拖长。

第四,不能让后台任务一直饿死。

如果一有 read 就 suspend,后台 erase 可能迟迟完不成。

后台 erase 完不成,空 block 回收就会受影响。

空 block 紧张以后,后面的写入也会被拖住。

所以 Suspend 是调度手段,不是万能开关。

真正高明的地方,不是“能 suspend”。

而是:

什么时候该 suspend,什么时候不该 suspend。


十一、Controller 真正难在哪里?

NAND 提供 Suspend 能力。

但用不用、什么时候用、怎么用,是 controller 的事情。

controller 要面对的不是一个简单命令,而是一整个系统。

它要同时考虑:

前台 read。

前台 write。

后台 GC。

block erase。

page program。

映射表更新。

Meta数据读写。

多 die 并行。

读写延迟。

所以 controller 不会看到 read 就无脑 suspend。

如果这个 read 可以从别的 die 走,那就没必要打断当前操作。

如果当前 program 快结束了,也许等一下更划算。

如果后台 erase 已经被暂停很多次了,继续暂停可能会影响空 block 回收。

如果系统正在高写入压力下,controller 还要考虑后面的写入是否会被空 block 不足反噬。

所以 Suspend 的背后,其实是 SSD controller 的调度能力。

NAND 只是提供了一个“可以暂停”的按钮。

真正难的是:

什么时候按这个按钮。 这个按钮能按多久。 按完以后什么时候必须回来。 这一次让 read 先走,会不会把后面的 program/erase 拖垮。

这才是 SSD 固件真正要做的平衡。


十二、Suspend 和多 die 并行不是一回事

有人可能会问:

SSD 有那么多 die,那么多 channel。

一个 die 忙了,换另一个 die 去读不就好了?

确实,SSD 会尽量做并行。

如果 A die 忙,就找 B die。 如果这个 channel 忙,就看别的 channel。 如果这个 plane 忙,就看有没有别的 plane 可以用。

但问题是:

read 不是想去哪读就去哪读。

数据在哪里,就要去哪里读。

某个用户数据可能就在这个 die 上。 某个映射表项可能就在这个 die 上。 某段元数据可能也在这个 die 上。

如果目标数据绕不开这个正在 busy 的 die / LUN,那么 read 还是会被挡住。

这时候 Suspend 才有意义。

多 die 并行解决的是:

能不能绕到别的资源上去。

Suspend 解决的是:

绕不开的时候,能不能让当前资源先服务 read。

这两个能力不是替代关系。

它们是互补关系。


十三、为什么这篇要放在 NAND 部分?

Suspend 放在 NAND 部分很合适。

因为它首先是 NAND 自己提供的一种能力。

它不是 ECC。 不是 Read Retry。 不是 RAID。 也不是 FTL 映射算法。

但它又和前面所有内容都能连起来。

前面讲 read,我们知道读请求很敏感。

前面讲 program,我们知道写入不是瞬间完成。

前面讲 erase,我们知道擦除是 block 级慢操作。

所以真实 SSD 里,read、program、erase 经常交织在一起。

Suspend 就是在这种交织场景中出现的。

它不是为了解决“读错了怎么办”。

它解决的是:

还没开始读,NAND 正在忙,read 怎么尽快执行?

这就是 Suspend 的位置。

它是 NAND 给 controller 留出来的一个调度口子。


十四、最后总结

NAND Suspend 可以用一句话概括:

当 die / LUN 正在执行 program 或 erase 这种慢操作时,controller 可以暂停当前慢操作,让 read 先执行;read 完成后,再 resume 回来继续原来的慢操作。

Program Suspend 是:

page A 正在 program,先暂停一下,让同一个 die / LUN 去读 page B。

Erase Suspend 是:

block A 正在 erase,先暂停一下,让同一个 die / LUN 去读 block B。

这里最重要的是四点:

第一,Suspend 的主线是给 read 让路。

第二,Suspend 不是让正在 program 的 page 可读,也不是让正在 erase 的 block 可读。

第三,Suspend 释放的是 die / LUN 的命令执行窗口,而不是把目标 page 或 block 变成完成状态。

第四,Suspend 给 read 的窗口是有限的,不能无限期挂起原来的 program/erase。

所以 Suspend 真正改善的是:

读延迟。

它不会让 NAND 的 program 变快。 也不会让 NAND 的 erase 变快。

它只是让 SSD 在慢操作很多的时候,仍然能尽量把 read 及时送出去。

用户不会关心 NAND 里面哪个 block 正在 erase,哪个 page 正在 program。

用户只会感觉:

为什么我正在写文件的时候,系统还能响应? 

为什么后台 GC 的时候,打开文件没有明显卡住? 

为什么 SSD 在混合负载下还能保持稳定?

Suspend 就是答案之一。

NAND 的慢操作不可避免。

Program 要时间。 Erase 更要时间。 GC 也绕不开。

但 controller 可以通过 suspend / resume,把这些慢操作切开,在合适的时候让 read 先走。

只是这个“先走”不是无限优先。

read 可以先走。 但原来的 program/erase 也必须继续往前走。

这不是让 NAND 变快。

而是让 SSD 更会调度。

谢谢你读我的文章🌹