本文思维导图总纲:
综述
关于 ubi 子系统, 早已有比较正式的介绍 http://www.linux-mtd.infradead.org/doc/ubi.html , 也提供非常形象的介绍 ubi 子系统 ppt http://www.linux-mtd.infradead.org/doc/ubi.ppt
国内的前辈 alloysystem http://blog.chinaunix.net/uid/28236237.html 不辞辛劳为我们提供了部分正式介绍的中文译文, 以及找不到原文的转载译文
感谢这些资料让我迅速入门 ubi, 进而整理出这博文
此博文是对上文的总结以及中文译文的补充
在阅读本文之前, 建议先学习 PPT 和中文译文
概念对比
UBI Vs. MTD
上图非常形象地描述了从 Flash 到 UBIFS 的各个层次. 从上图我们发现, MTD 子系统在实际的 Flash 驱动之上 , 而 UBI 子系统则在 MTD 子系统之上.
要对比 UBI 和 MTD 的概念, 我们不妨问自己一个问题, UBI 和 MTD 两个不同的层次的 "使命" 分别是什么?
Flash 驱动直接操作设备, 而 MTD 在 Flash 驱动之上, 向上呈现统一的操作接口. 所以 MTD 的 "使命" 是 屏蔽不同 Flash 的操作差异, 向上提供统一的操作接口 .
UBI 基于 MTD, 那么 UBI 的目的是什么呢? 在 MTD 上实现 nand 特性的管理逻辑, 向上屏蔽 nand 的特性 .
nand 有什么特性呢?
(下文描述的 Nand 驱动, 是广义上的操作 Nand 的集合, 包括 fs/ubi/mtd 的层次, 而非纯粹的 nand 驱动)
1. 操作最小单元为页(Page)/ 块(Block)
Nand 不同于 Nor,Nor 可以以字节为单位操作 Flash, 但 Nand 的读写最小单元是页, 擦除最小单元是块.
对常见的 1Gbit 的 spinand 而言, 其页大小 2KBytes, 块大小是 128K, 表示一个块有 64 个页.
2. 擦除寿命限制
Nand 的物理性质决定了其每个块都有擦除寿命的限制, SLC 约 10W 次, MLC 约 5000 次, TLC 约 1000 次.
因此, Nand 驱动必须要做到磨损平衡.
所谓磨损平衡, 就是尽可能均衡使用每一个块, 既不让一个块太大压力, 也不让一个块太过空闲.
3. 位翻转(bit-flips)
Nand 的物理性质使其可能会在使用, 保存过程中出现位翻转的现象.
例如, 原始数据为 0xFFFC, 在存储过程中 Flash 的数据却变成了 0xFFFF.
所以要不在 nand 内部, 要不在 nand 控制器都会存在 ecc 校正模块, 在位翻转后校正.
然而, ecc 并不是万能的, 其校正能力有限, 所以驱动必须在位翻转数量进一步变多之前把数据搬移到其他块.
萌新可能会有疑问, ecc 都已经校正了为什么还要搬移? 因为 ecc 校正的是从 Flash 中读到内存中的数据,
而不是 Flash 本身存储的数据, 换句话说, 此时 Flash 中的数据依然是错的, 如果不搬移, 随着翻转的位数量积累,
ecc 就校正不了了, 此时就相当于永久丢失正确数据了.
4. 存在坏块(Bad Block)
制作工艺和 Nand 本身的物理性质, 导致在出厂和正常使用过程中都会产生坏块.
所谓坏块, 就是说这个块已经损坏, 不能再用于存储数据, 因此 Nand 驱动需要能自动跳过坏块.
关于 SLC/MLC/TLC 的比较, 可参考这篇博客
UBI Vs. UBIFS
如果说 UBI 在 MTD 之上, 在 FS 之下的中间层, 用于抽象 MTD 屏蔽 nand 差异, 那么 ubifs 就是正儿八经的文件系统.
ubifs 是基于 UBI 子系统的文件系统, 实现文件系统该有的所有基本功能, 例如文件的实现, 例如日志的实现.
这里需要特别注意的是, ubifs 跟 jffs/yaffs 相比, 并不包含 nand 特性的管理, 而是交由 ubi 来实现.
UBI Vs. Block Layer
Block Layer 是适用于常见块设备的通用块层, 其特有的概念有 bio,request, 电梯算法等, 其典型的设备有磁盘, SSD,mmc 等.
而 ubi 基于 mtd, 虽然能模拟块设备, 从本质上来讲其并不是块设备. 跟踪 UBIFS 的 IO 操作, 发现其 IO 操作并不经过通用块设备层.
UBI Vs. FTL
FTL(Flash Translation Layer)是一个 "黑盒子", 其跟 UBI 非常像, 都是对 nand 特性进行封装.
按我的理解, UBI 跟 FTL 的目标不同, 导致其实现上会有差异. UBI 屏蔽 nand 特性是为了对接 UBIFS, 而 FTL 则是为了对接 Block Layer. 例如 MMC 其实也是封装起来的 Nand, 只不过在 MMC 内部实现了 FTL, 经过 FTL 的转换就能以块设备层的方法直接操作 Nand, 就能在 mmc 上格式化常见的块文件系统, 例如 EXT,VFAT 等.
UBI Volume Vs. UBI Device
在 UBI 中还有两个概念, 分别是 UBI 卷 (UBI Volume) 和 UBI 设备(UBI Device). 这两个概念, 我们可以这么理解:
UBI 设备 相当于 磁盘设备(sda,mmcblk0)
UBI 卷 相当于 磁盘上对应分区(sda1,mmcblk0p1)
换句话说, UBI 设备是在 MTD 设备上创建出来的设备, 而 UBI 卷则是从 UBI 设备上划分出来的分区, 从设备节点名 (ubi0) 和卷名 (ubi0_3) 可以看出端倪.
上面的描述是为了方便理解 UBI 卷和 UBI 设备, 实际上 UBI 卷和分区的概念之间还是有差别的.
LEB Vs. PEB
在 UBI 子系统中, 还有 LEB 和 PEB 的概念:
LEB 指 Logical Erase Block, 即逻辑擦除块, 简称逻辑块, 表示逻辑卷中的一个块
PEB 指 Physical Erase Block, 即物理擦除块, 简称物理块, 表示物理 Nand 中的一个块
为什么要划分逻辑块和物理块? 从 PPT http://www.linux-mtd.infradead.org/doc/ubi.ppt 中我们可以发现, 物理块和逻辑块存在动态映射关系, 且由于 UBI 头的存在, 逻辑块一般会比物理块小 2 个页.
UBI 子系统扮演的角色及其作用
UBI 子系统就是 ubifs 与 mtd 之间的中间层, 其向下连接 MTD 设备, 实现 nand 特性的管理逻辑, 向上呈现无坏块的卷.
所以 UBI 子系统的作用, 主要包括两点:
屏蔽 nand 特性(坏块管理, 磨损平衡, 位翻转)
UBI 卷的实现
UBI 卷的逻辑擦除块 (LEB) 与物理擦除块 (PEB) 之间是动态映射的, 详细可以看 PPT http://www.linux-mtd.infradead.org/doc/ubi.ppt
UBI 相关的工具
ubi 的工具集成在包 mtd-utils 中, 分别有以下工具及其作用
工具 | 作用 |
---|---|
ubinfo | 提供 ubi 设备和卷的信息 |
ubiattach | 链接 MTD 设备到 UBI 并且创建相应的 UBI 设备 |
ubidetach | ubiattach 相反的操作,将 MTD 设备从 UBI 设备上去链接 |
ubimkvol | 从 UBI 设备上创建 UBI 卷 |
ubirmvol | 从 UBI 设备上删除 UBI 卷 |
ubiblock | 管理 UBI 卷上的 block |
ubiupdatevol | 更新卷,例如 OTA 直接更新某个分区镜像 |
ubicrc32 | 使用与 ubi 相同的基数计算文件的 crc32 |
ubinize | 制作 UBI 镜像 |
ubiformat | 格式化空的 Flash 设备,擦除 Flash,保存擦除计数,写入 UBI 镜像到 Flash |
mtdinfo | 报告从系统中找到的 UBI 设备的信息 |
UBI 头部
UBI 子系统需要往每个物理块的开头写入两个关键数据, 这两个关键数据就叫做 UBI 的头部.
这两个数据分别是 此物理块擦除次数头 和 此物理块的逻辑卷标记头, 也分别称为 EC 头(Erase Count) 和 VID 头(Volume IDentifier).
不管是 EC 头还是 VID 头, 都是 64Bytes, 分别记录与 Nand 块的第一个页和第二个页.
以 Q&A 的形式介绍 UBI 头:
Q: 为什么要这两个头?
A: 前文有说道, nand 每个 block 有擦除寿命限制, 因此需要记录擦除次数, 以实现磨损平衡, 因此需要 EC 头. 此外, 为了实现卷, 必须记录卷的逻辑块与物理块之间的映射关系, 因此需要 VID 头.
Q: 为什么不合并成 1 个头?
A: 两者写入的时机不一致, 导致两个头必须分开写入. EC 头在每次擦除后, 必须马上写入以避免丢失, 而 VID 头只有在映射卷后才会写入.
Q: 不管是 EC 头还是 VID 头都是 64B, 为什么要用 2 个 Page?
A: 使用 2 个 Page 是对 Nand 来说的. 前文有说过, Nor 的读写最小单元是 Byte, 而 Nand 的读写最小单元是 Page, 因此对 Nor 可以只使用 64Bytes, 对 Nand 则必须使用 2 个 Page, 就是说, 即使只有 64Bytes 有效数据, 也需要用无效数据填充满 1 个 Page 一次性写入.
Q: 在记录擦除次数时掉电等, 导致丢失实际擦除次数怎么办?
A: 取所有物理块的擦除次数的平均数
关于 UBI 头部的详细介绍, 可参考链接
UBI 卷表(UBI Volume Table)
UBI 子系统有个对用户隐藏的特殊卷, 叫层卷(layout volume), 用来记录卷表. 我们可以把卷表等价于分区表, 记录各个卷的信息. 卷表大小为 2 个逻辑擦除块, 每个逻辑擦除块记录一份卷表, 换句话说, UBI 子系统为了保证卷表的可靠性, 用 2 个逻辑记录 2 分卷标信息.
由于层卷的大小是固定的(2 个逻辑块), 导致能保存的卷信息受限, 所以最大支持的卷数量是随着逻辑块的大小改变而改变的, 但最多不超过 128 个.
卷表中每个卷都保存了什么信息?
- struct ubi_vtbl_record {
- __be32 reserved_pebs; // 物理块数量
- __be32 alignment; // 卷对齐
- __be32 data_pad;
- __u8 vol_type; // 静态卷 or 动态卷标识
- __u8 upd_marker; // 更新标识
- __be16 name_len; // 卷名长度
- __u8 name[UBI_VOL_NAME_MAX+1]; // 卷名
- __u8 flags; // 常用语自动重分配大小标记
- __u8 padding[23]; // 保留区域
- __be32 crc; // 卷信息的 CRC32 校验值
- } __packed;
由这个结构体我们可以发现, 卷信息是被 CRC32 保护着的. 比较有意思的有两个成员: vol_type 和 flags
动态卷 & 静态卷
vol_type 成员标记了卷的类型, 在创建卷时指定, 可选动态卷和静态卷. 那么什么是动态卷? 什么又是静态卷?
动态卷和静态卷是两种卷的类型, 静态卷标记此卷只读, 于是 UBI 子系统使用 CRC32 来校验保护整个卷的数据, 动态卷是可读写的卷, 数据的完整性由文件系统来保证.
关于静态卷和动态卷的介绍, 可参考链接
更新标识
flags 成员常用于标识是否自动重分配大小. 怎么样自动充分配大小呢? 在首次运行时自动 resize 卷, 让卷大小覆盖所有未使用的逻辑块.
例如 Flash 大小是 128M, 在烧录的镜像中分配的所有卷加起来只用了 100M, 如果有卷被表示为 autoresize, 那么在首次运行时, 那个卷会自动扩大, 把剩余的 28M 囊括在内.
这个功能挺实用的, 例如某个方案规划中, 除去 rootfs, 内核等必要空间外, 把剩余所有空间尽可能分配给用户数据分区.
在开发过程中加了个应用, 导致 rootfs 卷需要更大的空间, 进而需要压缩 user_data 卷的空间.
如果 user_data 空间是 autoresize 的, 那么 user_data 卷的空间就会自动压缩.
再例如旧方案用的是 128M 的 nand, 后面升级为 256M, 即使使用相同的固件, 也不用担心多出来的 128M 浪费掉了,
因为 user_data 卷自动扩大囊括多出来的 128M.
需要注意的是, 只允许 1 个卷设置 autoresize 标志
关于更新标识更多的介绍, 参考链接
坏块标记
我们知道 Nand 的物理性质, 导致在使用久之后会产生坏块, 那么 UBI 是如何判断好块是否变成了坏块的呢?
有两个场景可能会标识坏块, 分别是写失败和擦除失败. 擦除失败且返回是 EIO, 则直接标记坏块. 比较有意思的是写失败的判断逻辑.
UBI 子系统有后台进程对疑似的坏块进行 "严刑拷打"(torturing), 有 5 个步骤:
擦除嫌疑坏块
读取擦除后的值, 判断是否都是 0xFF(擦除后理应全为 0xFF)
写入特定数据
读取并校验写入的数据
以不同的数据模式重复步骤 1-4
如果 "严刑拷打" 出问题, 则标记坏块, 详细的实现逻辑可参考函数 torture_peb()
原文可参考链接
UBI 管理开销
什么是管理开销呢? 为了管理 Nand 的空间, 实现磨损平衡, 坏块管理等等功能, 必须占用一部分空间来存储关键数据, 就好像文件系统的元数据. 管理占用的空间是不会呈现给用户空间使用的, 这空间即为管理的开销.
对 Nand 来说, UBI 管理开销主要包含 5 个部分:
层卷(卷表) : 占用两个物理块
磨损平衡: 占用一个物理块
逻辑块修改原子操作: 占用一个物理块
坏块管理: 默认每 1024 个块则预留 20 个块(内核参数可配: CONFIG_MTD_UBI_BEB_LIMIT)
UBI 头:(物理块总数 * 2)个页
坏块管理预留的块数量, 也可以理解为最大能容纳多少个坏块; 再考虑坏块的存在, 管理开销计算公式为:
UBI 管理总开销 = 特性开销 + UBI 头开销
其中:
坏块预留 = MAX(坏块数量, 坏块管理预留数量)
特性开销 = (坏块预留 + 1 个磨损平衡开销 + 1 个原子操作开销 + 2 个层卷开销) * 物理块大小
UBI 头开销 = 2 * 页大小 * (含坏块的总块数 - 坏块预留 - 1 个磨损平衡开销 + 1 个原子操作开销 + 2 个层卷开销)
也就是说:
UBI 管理总开销 = (坏块预留 + 4) * 物理块大小 + 2 * 页大小 * (含坏块的总块数 - 坏块预留 - 4)
以 128M 的江波龙的 FS35ND01G-S1F1 SPI Nand 为例, 其规格为:
总大小: 128M(1Gbit)
页大小: 2K bytes
块大小: 128K
块数量: 1024
假设是完全无坏块的片子, 其管理开销为:
UBI 管理开销 = (20 + 4) * 128K + 2 * 2K * (1024 - 20 - 4) = 7072K ≈ 7M
详细参考原文链接
来源: https://www.cnblogs.com/gmpy/p/10874475.html