闪存
嵌入式设备中的闪存是一个非常热门的话题——在选择“正确”的闪存类型之前,应该关注很多问题。 通常错误的决定会适得其反——我在几个项目和公司中都经历过这种情况。
评估备选的闪存芯片和类型时应该关心的问题有:
- 闪存需要多少个读/写周期以及多大的容量才能不发生故障?
- NOR 还是 NAND 闪存?
- SLC还是MLC闪存?
- 如何处理坏块(badblock)?
- 我的项目需要多少备用闪存空间?
- 如果需要文件系统:选择什么样的文件系统?
在为项目评估正确的闪存类型时,应该非常仔细地考虑这些问题。
由于上述大多数问题都没有真正简单的通用解,每个解决方案都有自己的优缺点,那么我将尝试说明一些场景。
擦除块和擦除循环
闪存由所谓的“擦除块”(从现在开始称为块)组成。 它的大小在很大程度上取决于闪存的种类(NOR/NAND)和闪存总大小。
通常 NOR 闪存块比 NAND 闪存大得多(指块大小和总容量的相对大小)——典型的如:4MB NOR 闪存块大小为 64KB,256MB NAND 闪存块大小为 64KB,512MB NAND 闪存块大小为 128KB。 当闪存(尤其是 NAND 闪存)的存储容量越来越大时,引入页(page)和子页(sub-page)的概念。
NAND 闪存由擦除块组成,擦除块可能由页组成,页可能由子页组成。
尽管从技术上讲擦除块、页和子页并不相同,但它们都表示(如果存在)最小的闪存 I/O 单元。 每当我在闪存上写(擦除)块并且您的 NAND 闪存支持页(大多数现代 NAND 闪存都支持)时,只需将这里提到的块视为页。
NAND 闪存还可能包含一个“带外 (OOB) 区域”,大小通常是块大小的一部分。 其专用于储存元信息(如关于坏块、ECC 数据、擦除计数器等的信息),而不是实际数据。 闪存需要“按块”寻址以进行写入。 块只能“擦除”特定次数,之后将损坏无法使用(典型值是10.000 – 100.000 次)。
以上通常得出结论,闪存可以按字节读取但不能按字节写入,即要写入闪存,您需要先擦除整个块。 这个结论并非完全错误,但由于简化而具有误导性。 默认情况下,闪存存储单元是“已擦除”状态(逻辑位为“1”)。 一旦位被翻转为“0”,只能通过擦除整个块将其恢复为“1”。
即使您确实只能针对闪存“每个擦除块”进行写入 – 并且当您打算将位从“0”翻转到“1”时,您必须擦除(意味着写入整个擦除块) – 但实际上,这并不意味着每个写操作都需要事先进行块擦除。单个位可以从“1”翻转到“0”,但将位从“0”恢复到“1”需要擦除整个块。
考虑到一个(不切实际小的)例子,将一个擦除块内的 '1111 1110' 更改为'1110 1111',您必须:
- 擦除整个块,所以它将是'1111 1111'
- 将第 4 个字节向下翻转为“0”
但是如果我们想将 '1111 1110' 变成 '1010 0110' 我们只需翻转块的第 2、4 和 5 位。这样我们不需要先擦除整个块,因为块中的任何位都不需要从“0”变为“1”。 这样,由于闪存巧妙的写入处理,并非每个写入操作都意味着需要先擦除整个块。
这一点可能会显着提高闪存的使用寿命(尤其是 SLC NAND,MLC 需要一些更复杂的方法),因为块只能被擦除一定次数。 它还可能允许您使用更便宜(擦除周期更少)的闪存。
此外,您应该避免大部分擦除块不改变,而一些擦除块被擦除数千次。 为了避免这种情况,您通常会进行某种“磨损平衡”(wear leveling)。 这意味着,您可以跟踪块被擦除的次数,如果可能的话,将经常更改的块上的数据重新放置到不经常被擦除的块。为了正确完成磨损均衡,您需要有一定数量的备用块,以便能够轮换块并正确重新放置实际数据。
硬件 vs 软件
这两种技术都可以显着提高闪存的使用寿命,但需要相当复杂的算法才能获得实际收益。 裸闪存(bare flash)不处理这些问题。有硬件控制器可以用来解决这个问题(例如在高等级存储卡/U盘中),但它通常很糟糕。如果可能请在软件中进行。例如使用Linux 驱动闪存提供了相当多的可能性(见文件系统)。
NOR vs NAND
和总容量比,NOR 块比 NAND 块的相对大小大得多——这意味着NOR块:
- 写性能差(比 NAND 闪存慢几倍)
- 块被擦除得更频繁,因此更容易地超过“最大”擦除循环(此外:NOR 的擦除循环通常比 NAND 少约 1000 倍)
坏块
坏块及其处理方式决定了您最终是可以将闪存用作正常存储,还是最终陷入调试噩梦,这是由于奇怪的设备故障引起的,这些故障可能会导致闪存坏块处理不当。
坏块是当擦除块由于位翻转(bit flip)而不按预期存储数据。这意味着,块中一部分不能被擦除(不能再翻转回“1”,部分“浮动”(float)并且不能变入明确定义的状态,等等)。块只能被擦除一定次数,直到它们被损坏并因此“坏”。 一旦块可能变为坏块,应立即将其标记为坏块,并且永远不再使用。
标记坏块的意思是:把这个坏块加入坏块表中(大多设置在flash的末尾)。 由于该表位于闪存上的块内,也可能变坏块,因此该表通常有冗余。
坏块时有出现,尤其是在 NAND 闪存上。 即使从未使用过的 NAND 闪存,在出厂时也可能包含坏块。 正因为如此,NAND 闪存制造商从一开始就“预装”了有关哪些块是坏的信息。 不幸的是,这些信息如何存储在闪存上是,不同供应商/产品是不同的。 另一个用于存储此类信息的公共区域是闪存的 OOB 区域(如果存在)。
这也意味着,某些供应商和产品的闪存将具有不同数量的可用块。 这是您必须处理的一个事实——计算空间时不要太极限,因为需要备用空间来替换坏块!
硬件 vs 软件
实际上有的 NAND 芯片可以自己进行坏块管理。 虽然我自己从未使用过它们,但我听说很不错。 然而,大多数裸闪存不处理坏块。 可能有关于坏块的预装信息,也可能没有。 如果有,格式就是供应商选择的格式。 这意味着,如果使用闪存控制器来处理坏块,它需要能够读取和写入存储这些信息的格式类型。它需要知道闪存是否有 OOB 区域 以及它们的组织方式。
尽管有一些复杂且灵活的闪存控制器硬件,但同样:如果可能,请在软件中进行。有相当好的和经过验证的代码可用。
NAND vs NOR
NOR 闪存比 NAND 闪存更可预测。 NAND 闪存块可能会随时变坏(湿度、温度等等)。 读取 NAND 闪存也导致这一点——是的,读取 NAND 闪存会导致坏块! 虽然这种情况不像写操作那样经常发生(并且在 SLC 上比在 MLC 闪存上少得多),但它确实会发生,你最好考虑它!
NAND 闪存的数据表(datasheet)中提到了预期的擦除周期数,但没有读取周期数。读取周期数通常是擦除周期的 10 到 100 倍左右。 想象一下,你想从 NAND 闪存启动,而引导加载程序(boot-loader)——它还不能处理坏块——位于不可预测的块或坏块内......一场噩梦! 这就是为什么现在很普遍,前几个 NAND 闪存块保证在前 N 个擦除周期是安全的。确保您的引导加载程序在这些安全的块内!
闪存寿命
以上结论:闪存预期寿命取决于许多变量,您的使用场景可能是最相关的。 您使用闪存的次数越少,它的寿命就越长,甚至可以简单说:您写入闪存的次数越少,它的寿命就越长。
也可以看看:
- Openwrt论坛中的 闪存寿命
无关紧要的 mtdblock I/O 错误
人们可能会遇到可以安全地忽略的系统日志消息。 这些消息例如:
print_req_error: I/O error, dev mtdblock1, sector 0
解释: (引自 FS#1871, closing comment by Jonas Gorski)
可以确定NAND 闪存的前几个块是良好的,以确保存储在那里的引导加载程序永远不会被损坏,因此它写入时没有有效 ECC 数据(SoC 无论如何都不会检查 ECC)。
当块挂载扫描所有块设备时,它将尝试从这些块中读取,这些块可被作为分区扫描到,并且 NAND 驱动程序将报告ECC 检查失败(日志中的 I/O 错误)。
但其实这里都没有错误,我们也无法做什么来避免。
NAND储存和位反转
在 NAND 闪存上读取或写入原始文件时,您可能会遇到类似的输出:
root@OpenWrt:/# nanddump --file /tmp/mounts/USB-A/home/hh5a.nanddump /dev/mtd4 ECC failed: 0 ECC corrected: 0 Number of bad blocks: 0 Number of bbt blocks: 4 Block size 131072, page size 2048, OOB size 64 Dumping data starting at 0x00000000 and ending at 0x08000000... ECC: 1 corrected bitflip(s) at offset 0x01a7e000 ECC: 1 corrected bitflip(s) at offset 0x01dbf000 ECC: 1 corrected bitflip(s) at offset 0x01ddc800 ....
这些带有“ECC: 1 Corrected bitflip blablabla”的行可能听起来像是错误或问题,但事实并非如此。
请注意它说的是“已更正的位翻转(correctED bitflips)”而不是“可更正的位翻转(correctABLE bitflips)”。
您在那里看到的消息对于 NAND 闪存来说是正常的。它们意味着 ECC(错误检测和纠正)逻辑已检测到问题并使用奇偶校验位纠正读取数据。
“位翻转”错误是指位自行改变其状态,1 变为 0 或相反的情况。这不是坏块(永久性损坏),它只是在 NAND 中随机发生的事情,因为它们的设计可靠性较低(以降低成本),并通过实施 ECC 逻辑来纠正任何位翻转(这是仍然使它们在硬件级别更可靠更便宜)。
因此,在 NAND 闪存设备中,您有一些空间实际存储数据,一些空间存储奇偶校验信息,因此系统可以使用 ECC 逻辑并纠正位翻转。这是自动的。
同样的事情发生在 SSD、USB 闪存驱动器、SD 卡和智能手机(都使用 NAND 闪存)中,你只是没有看到这种情况发生,因为它全部由存储控制器处理,而在读/写速度的嵌入式设备中并不重要(如路由器)闪存是裸访问的,没有这样的控制器,因此系统本身在读取/写入时使用 ECC。
请注意,通过使用 ECC (无论通过硬件存储控制器或系统),NAND 闪存与您期望的存储设备一样可靠。
用于读取和写入裸 NAND 的特定工具
NAND 所需的 ECC 逻辑(上面讨论过)是您必须使用 `nand-aware`工具(如 nanddump 和 nandwrite)而不是更常见的 dd 工具来创建或恢复 NAND 上闪存分区的备份的主要原因。
nanddump 使用 ECC 逻辑读取 NAND,并仅在您创建的备份中存储实际数据(纠正位翻转)。同样,nandwrite 写入映像(image)时也会写入适当的奇偶校验数据,以便 ECC 逻辑在读取时工作。
dd 工具(或更高级的工具如 pv), 不知道 NAND ECC 逻辑,因此它将读取所有 NAND 分区,包括数据和奇偶校验,并将生成与实际闪存完全相同的备份,但完全没用并且不可读,因为它将包含无法在不同设备上恢复的数据和特定于硬件的奇偶校验信息。同样在写入时,它不会写入奇偶校验信息以使 ECC 逻辑工作,因此无论您写入什么,都会被当作垃圾读取。
dd 和 pv 等工具只能在块设备上使用, SSD、SD 卡、硬盘驱动器(是的,机械硬盘驱动器也集成了 ECC 逻辑)等等,其中任何 ECC 由存储控制器完成,而不是由系统完成,因此无论读取的是纯数据,没有元数据或 ECC 奇偶校验逻辑。