Linux ubi子系统原理分析

本文思维导图总纲:
思维导图总纲

综述

关于ubi子系统,早已有比较正式的介绍,也提供非常形象的介绍ubi子系统ppt
国内的前辈 alloysystem 不辞辛劳为我们提供了部分正式介绍的中文译文,以及找不到原文的转载译文

感谢这些资料让我迅速入门ubi,进而整理出这博文

此博文是对上文的总结以及中文译文的补充

在阅读本文之前,建议先学习PPT中文译文

概念对比

UBI Vs. MTD

UBI层次

上图非常形象地描述了从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

详细参考原文链接

posted @ 2019-05-16 11:17  广漠飘羽  阅读(7443)  评论(0编辑  收藏  举报