Linux ubi子系统原理分析
本文思维导图总纲:
综述
关于ubi子系统,早已有,也提供非常形象的
国内的 不辞辛劳为我们提供了,以及
感谢这些资料让我迅速入门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中我们可以发现,物理块和逻辑块存在动态映射关系,且由于ubi头的存在,逻辑块一般会比物理块小2个页。
ubi子系统扮演的角色及其作用
ubi子系统就是ubifs与mtd之间的中间层,其向下连接mtd设备,实现nand特性的管理逻辑,向上呈现无坏块的卷。
所以ubi子系统的作用,主要包括两点:
1. 屏蔽nand特性(坏块管理、磨损平衡、位翻转) 2. ubi卷的实现
ubi卷的逻辑擦除块(leb)与物理擦除块(peb)之间是动态映射的,详细可以看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个步骤:
1. 擦除嫌疑坏块 2. 读取擦除后的值,判断是否都是0xff(擦除后理应全为0xff) 3. 写入特定数据 4. 读取并校验写入的数据 5. 以不同的数据模式重复步骤1-4
如果"严刑拷打"出问题,则标记坏块,详细的实现逻辑可参考函数torture_peb()
原文可参考链接
ubi管理开销
什么是管理开销呢?为了管理nand的空间,实现磨损平衡、坏块管理等等功能,必须占用一部分空间来存储关键数据,就好像文件系统的元数据。管理占用的空间是不会呈现给用户空间使用的,这空间即为管理的开销。
对nand来说,ubi管理开销主要包含5个部分:
1. 层卷(卷表) : 占用两个物理块 2. 磨损平衡:占用一个物理块 3. 逻辑块修改原子操作:占用一个物理块 4. 坏块管理:默认每1024个块则预留20个块(内核参数可配:config_mtd_ubi_beb_limit) 5. 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
详细参考原文链接