UBI全称Unsorted Block Images,是一种原始flash设备的卷管理系统。这个系统能在一个物理的flash设备上管理多个卷并且能在整个flash芯片上实现损耗均衡。
从某种意义上说,UBI和LVM(Logical Volume Manager)有点相似,LVM将逻辑扇区映射到物理扇区上面,UBI映射逻辑擦除块到物理擦除块。但是除了映射,UBI还实现了全局的损耗均衡和透明的I/O错误处理。
一个UBI卷就是一串连续的逻辑擦除块(LEBs)。每一个逻辑擦除块可以被映射到任何一个物理擦除块(PEB)上面。这个映射是由UBI管理,这种映射对用户是透明的,同时这种映射也是UBI实现全局的损耗均衡的基础。通过每一个物理擦除块记录的擦除计数,可以将数据从损耗严重的物理块转移到损耗较少的擦除块。
UBIFS 是诺基亚工程师在塞格德大学的帮助下开发的一种新的闪存文件系统。 在某种程度上,UBIFS 可以被视为 JFFS2 文件系统的下一代。
UBIFS工作在UBI卷之上,不能在MTD设备之上运行,也不能在block设备上运行,因为ubifs的设计就是raw flash设备之上的文件系统。
这里为了便于理解,提到了一点ubusfs的概念,本文将着重介绍ubi,ubifs不是本文的重点内容。
UBI和UBIFS的关系如下图所示:

以W25N01GV这款NAND Flash为例 (size 128MB, page size 2048, block size 128K)


PEB: 物理擦除块,和NAND FLASH中的一个物理block是一一对应的,大小也是一致的。 LEB:逻辑擦除块,实际可用大小比PEB少2个page(第一个page用于储存EC头,第二个page用于储存VID 头),而且LEB和PEB的映射关系不确定。
UBI的主要功能如下:
一个UBI卷就是一串连续的逻辑擦除块(LEBs)。
UBI卷大小在创建卷时指定,但以后可能会更改(卷可动态调整大小)。UBI提供用户空间工具用于操作UBI卷。
UBI卷有两种类型:
静态卷通常用于内核、initramfs 和 dtb。 较大的静态卷在打开时可能会损失部分性能,因为需要计算 CRC-32。
动态卷可以动态创建、删除、调整大小。内核、initramfs或dtb之外的其他镜像最好使用动态卷。
MTD分区和UBI卷的相同点:
但与 MTD分区相比,UBI卷具有以下优点:
除此之外,UBI还提供了一个块设备,允许将常规的、面向块的文件系统挂载在UBI卷的顶部。这是可能的,因为UBI透明地处理坏块。
UBI在每个非坏物理擦除块的开头存储2个小的64 字节headers:
这就是逻辑擦除块小于物理擦除块的原因——header占用一些闪存空间
所有 UBI header都受 CRC-32 校验和保护。请参阅linux内核中的drivers/mtd/ubi/ubi-media.h 文件以获取有关header内容的更多信息。
/* Erase counter header magic number (ASCII "UBI#") */
#define UBI_EC_HDR_MAGIC 0x55424923
/* Volume identifier header magic number (ASCII "UBI!") */
#define UBI_VID_HDR_MAGIC 0x55424921
struct ubi_ec_hdr {
__be32 magic;// 固定值 UBI_EC_HDR_MAGIC 0x55424923
__u8 version;// 支持当前UBI image的UBI发行版本 默认为 #define UBI_VERSION 1
__u8 padding1[3];//保留数据,默认为0
__be64 ec; /*擦除计数器,目前只使用了31bit,其余bit保留 */
__be32 vid_hdr_offset;// vid 头所在的偏移地址
__be32 data_offset;//存储用户数据的起始地址
__be32 image_seq;//image 序列号
__u8 padding2[32];//填充字节
__be32 hdr_crc;// ubi_ec_hdr 的CRC校验和
} __packed;
struct ubi_vid_hdr {
__be32 magic;// 固定值 UBI_VID_HDR_MAGIC 0x55424921
__u8 version;// 支持当前UBI image的UBI发行版本 默认为 #define UBI_VERSION 1
__u8 vol_type;// 卷类型 动态卷和静态卷
__u8 copy_flag;//该逻辑擦除块是否从另一个物理擦除块复制过来的(出于损耗平衡的原因)
__u8 compat;//当前卷的兼容性
__be32 vol_id;//当前卷id
__be32 lnum;//逻辑块擦除计数
__u8 padding1[4];//填充字节
__be32 data_size;//当前LEB包含多少字节的数据
__be32 used_ebs;//当前卷已经使用的LEB数量
__be32 data_pad;//该物理擦除块末尾有多少字节未被使用
__be32 data_crc;//当前LEB存储数据的CRC校验和
__u8 padding2[4];//填充字节
__be64 sqnum;// 序列号
__u8 padding3[12];//填充字节
__be32 hdr_crc;// ubi_vid_hdr 的CRC校验和
} __packed;

当 UBI attach一个 MTD 设备时,它必须扫描这个MTD设备,读取所有header,检查 CRC-32 校验和,并将擦除计数器和逻辑到物理擦除块映射信息存储在 RAM 中。
在 UBI 擦除了一个 PEB 之后,它会增加擦除计数器的值并将其写入 EC header。这意味着 PEB 总是有一个有效的 EC header,除了擦除之后和写入 EC 头之前的一小段时间。如果在这短时间内发生意外重启,EC header会丢失或损坏。在这种情况下,UBI 在 MTD 设备扫描完成后立即写入一个带有平均擦除计数器的新 EC header。
当 UBI 将其与 LEB 关联时,将 VID header写入 PEB。让我们考虑一下在某些 UBI 操作期间header会发生什么。
UBI针对每个PEB需要维护两个header,因为它需要在不同的时间将不同的信息写入闪存:
当 EC header写入 PEB 时,UBI 还不知道卷 ID 也不知道该 PEB 将关联到的 LEB 编号。这就是为什么 UBI 需要做两个独立的写操作并且有两个独立的headers。
EC 标头始终位于偏移量0的位置并占用 64 字节,VID header位于下一个可用的最小 I/O 单元或sub-page处,也占用 64 字节。 例如:
卷表是一个闪存数据结构,其中包含有关此 UBI 设备上每个卷的信息。卷表是卷表记录的数组。每条记录都包含以下信息:
每条记录描述一个UBI 卷。卷表数组中的记录索引对应于它所描述的卷ID。即,UBI卷0由卷表中的记录 0描述,依此类推。卷表中的记录总数受 LEB 大小的限制,不能大于 128。这意味着 UBI 设备的卷不能超过 128 个。
每次创建、删除、调整大小、重命名或更新UBI卷时,都会更改相应的卷表记录。出于可靠性和断电容错的原因,UBI 维护了卷表的两个副本。
在内部,卷表位于特殊用途的UBI卷中,称为layout volume。该卷由 2个LEB 组成(LEB0,LEB1)- 每个卷表和副本各占一个。layout volume是“内部”UBI 卷,用户看不到也无法访问它。在读取或写入layout volume时,UBI 使用与普通用户卷相同的机制。
UBI 在更新卷表记录时使用以下算法:
attach MTD 设备时,UBI确保2个卷表副本是等效的。如果它们不相等,可能是由于不干净的重启导致,UBI 从 LEB0 中选择一个并将其复制到布局卷的 LEB1(因为根据上面指定的算法,LEB0 是最先更新的,因此被认为拥有最新的信息)。如果其中一个卷表副本损坏,UBI 会从另一个卷表副本中恢复它。
UBI 使用闪存的抽象模型。简而言之,从 UBI 的角度来看,闪存(或 MTD 设备)由擦除块组成,这些擦除块可能是好的也可能是坏的。每个好的擦除块都可以被读取、写入或擦除。好的擦除块也可能被标记为坏的。
闪存读取和写入只能以最小输入/输出单元大小的倍数进行,这取决于闪存类型。
最小I/O单元大小是MTD设备的一个非常重要的特性。它会影响很多事情,例如:
UBI 将一些闪存空间用于自己的目的,从而减少了可供 UBI 用户使用的闪存空间量。 即:
UBI 开销为 (B + 4) * SP + O * (P - B - 4),即用户无法访问此字节数。 O对于不同的Flash是不同的:
注意:上面的公式将坏块计为 UBI 开销。 真正的 UBI 开销是:(B - BB + 4) * SP + O * (P - B - 4)。
众所周知,NAND 芯片有一些被制造商标记为坏的物理擦除块。在 NAND 设备的生命周期中,可能会出现其他坏块。尽管如此,制造商通常会保证前几个物理擦除块没有坏,坏PEB的总数不会超过一定数量。例如,一个 256MiB(2048 个 128KiB PEB)的三星 OneNAND 芯片保证在其使用寿命期间不超过 40 个 128KiB PEB。这是 NAND 设备的一个非常常见的值:20/1024 PEB,大约是闪存大小的 2%。
这个 20/1024 的比率是UBI为每个UBI设备保留的默认块数。这意味着如果一个 4096 PEB NAND 上有 2 个 UBI 设备,每个UBI 设备将保留 80 个 PEB。这似乎是在浪费空间,但是,鉴于坏块可以出现在 NAND 闪存的任何位置,并且在整个设备上分布不均,这是更安全的方法。因此,与其在 NAND 闪存上使用多个UBI 设备,不如仅使用一个包含多个UBI卷的UBI设备更节省空间。
每 1024 个 PEB 保留 20 个PEB 的默认值是内核配置选项。对于每个 UBI 设备,可以通过内核参数或 ubiattach 参数(自内核 3.7 起)调整此值。
本节与 NAND 闪存以及其他具有坏擦除块的闪存相关。 UBI 在以下两种情况下将物理擦除块标记为坏:
“折磨”是在后台进行的,目的是检测物理擦除块是否实际上是坏的。写入失败可能是由于多种原因之一发生的,包括驱动程序中的错误或文件系统等上层内容中的错误(例如,FS 错误地多次错误地写入同一个 NAND 页)。在“折磨” UBI 期间,UBI 会执行以下操作:
如果擦除块在“酷刑”测试中幸存下来,则不会将其标记为坏块。然而,“酷刑”测试期间的位翻转是将擦除块标记为坏的一个很好的理由。有关详细信息,请参阅torture_peb() 函数。
to be continue
ubi tools官方仓库
http://git.infradead.org/mtd-utils.gittools | 说明 | 备注 |
|---|---|---|
ubinfo | 提供有关在系统中找到的UBI设备和卷的信息 | |
ubiattach | 链接MTD设备(原始Flash设备)到UBI并且创建相应的UBI设备 | |
ubidetach | 将MTD设备与UBI设备分离(与ubiattach的作用相反); | |
ubimkvol | 在 UBI 设备上创建 UBI 卷 | |
ubirmvol | 从 UBI 设备中删除 UBI 卷 | |
ubiblock | 管理 UBI 卷的块接口 | |
ubiupdatevol | 更新ubi卷,也可以用来清空ubi卷 | |
ubicrc32 | 计算与 UBI使用的初始种子相同的文件的 CRC-32校验和 | |
ubinize | 生成ubi镜像 | 注意是ubi镜像不是ubifs镜像 |
ubiformat | 格式化空flash,擦除flash并保留擦除计数器,将UBI镜像写入flash | |
mtdinfo | 查看系统中所有的mtd设备信息 |
UBI镜像可以使用 ubinize 工具生成。 该工具在输入时获取配置文件并在输出时生成 UBI镜像。 输入配置文件用于描述需要生成的UBI镜像,且必须包含的所有的UBI卷。
Usage:ubinize [options] <ini-file>options | 说明 | 备注 |
|---|---|---|
-o, --output= | 输出到文件 | |
-p, --peb-size= | 创建此UBI镜像的闪存的物理擦除块的大小,以字节、千字节 (KiB) 或兆字节 (MiB) 为单位(强制参数) | |
-m, --min-io-size= | Flash的最小输入/输出单元大小(以字节为单位) | |
-s, --sub-page-size= | 用于 UBI headers的最小输入/输出单元,例如NAND flash 情况下的子页面大小(默认等于最小输入/输出单元大小) | |
-O, --vid-hdr-offset= | 偏移量,如果 VID header 从物理擦除块的开始(默认是 EC header之后的下一个最小 I/O 单元或子页面) | |
-e, --erase-counter= | 放入EC header的擦除计数器值(默认为 0) | |
-x, --ubi-ver= | 放入 EC header的UBI版本号(默认为 1) | |
-Q, --image-seq= | 要使用的32位UBI镜像序列号(默认选择随机数) |
配置文件具有ini-file语法。
option | 说明 | 备注 |
|---|---|---|
mode | 固定为ubi | 必填 |
image | 使用的ubifs镜像文件 | 静态卷必须指定ubifs镜像文件 |
vol_id | 卷ID | 不填则自动分配 |
vol_size | 卷大小 | 不填则自动分配一个能够容纳当前镜像的最小值 |
vol_type | 卷类型(static、dynamic) | 必填 |
vol_name | 卷名字 | 必填 |
vol_flags | 卷标志位(autoresize,skip-check) | 选填 |
下面是一个配置文件示例:
[configuration-data-volume]
mode=ubi
image=config_data.img
vol_id=0
vol_size=512KiB
vol_type=static
vol_name=configuration
[rootfs-volume]
mode=ubi
image=rootfs.img
vol_id=1
vol_size=220MiB
vol_type=dynamic
vol_name=rootfs
vol_flags=autoresize上述配置文件包含2个卷:
configuration;卷的内容应取自config_data.img 文件;rootfs; 卷的内容应取自 rootfs.img文件; 这个卷还应该有auto-resize标志,这意味着这个卷的大小将在 UBI 第一次运行时被修改;UBI将通过提供可用的擦除块来使该卷变大;这在 NAND 闪存的情况下可能非常有用。Volume flag auto-resize 当 UBI 映像在生产过程中被刷新时,应该为所有卷指定确切的大小(大小存储在 UBI 卷表中)。 然而,在嵌入式世界中,我们喜欢为根文件系统设置一个只读卷,为剩余的空间(日志、用户数据等)设置一个读/写卷。 如果根文件系统的大小是固定的,那么第二个文件系统的大小可能因产品而异(给定不同的闪存大小)。 这是自动调整大小标志的目的。 如果卷启用了autoresize标志,则在第一次运行 UBI 时,其大小将扩大以填充剩余的未使用空间。 调整卷大小后,UBI 删除自动调整大小标志,不再调整卷大小。 自动调整大小标志存储在volume table中,并且只有一个卷可以标记为自动调整大小。
Volume flag skip-check 为静态卷保留的flag
所以在上面的例子中,ubinize 基本上读取了 3 个输入文件:
ubinize需要一个配置文件的原因是: 一个UBI 映像可能包含许多具有不同特征的UBI 卷,并且很难发明一个好的命令行界面来指定所有这些特征,因此使用了配置文件。
ubiattach工具用于链接MTD设备(raw Flash设备)到UBI并且创建相应的UBI设备。
Usage: ubiattach [<UBI control device node file name>]
[-m <MTD device number>] [-d <UBI device number>] [-p <path to device>]
[--mtdn=<MTD device number>] [--devn=<UBI device number>]
[--dev-path=<path to device>]
[--max-beb-per1024=<maximum bad block number per 1024 blocks>]option | 说明 | 选项 |
|---|---|---|
-d, --devn= | 分配给新创建的 UBI 设备的编号(如果未指定,则自动分配) | |
-p, --dev-path= | 要附着的MTD设备节点的路径 | |
-m, --mtdn= | 要附着的MTD设备号(例如/dev/mtdX X即MTD设备号) | |
-O, --vid-hdr-offset | VID header 偏移(不要指定这个除非你真的知道你在做什么,默认应该是最优的) | |
-b, --max-beb-per1024 | 每1024个擦除块的最大预期坏块数。 对于大多数 NAND 设备,默认值是正确的。 允许的范围是 0-768,0 表示默认内核值。 |
如果<UBI control device node file name>未指定,就使用默认值/dev/ubi_ctrl
-p选项用于指定更具体的MTD设备节点,例如/dev/mtd19
-m选项和-p类似,也是指定MTD设备节点,但是更简洁,只需要MTD设备号即可
-b选项指定坏块冗余数量,但不是具体的数量。而是每1024个block需要多少个冗余块。
下面是一些使用实例:
Example 1: ubiattach -p /dev/mtd0
attach /dev/mtd0 to UBI
Example 2: ubiattach -m 0
attach MTD device 0 (mtd0) to UBI
Example 3: ubiattach -m 0 -d 3
attach MTD device 0 (mtd0) to UBI and create UBI device number 3 (ubi3)
Example 4: ubiattach -m 1 -b 25
attach /dev/mtd1 to UBI and reserve 25*C/1024 (如果FLASH共有4096个块, 则预留100个)ubiattach的mtd设备可以是空设备,也可以是已经烧写了ubi镜像的设备;如果是前者,还需要手动创建卷之后才能使用。如果是后者,则会自动识别里面的ubi镜像信息,并根据ubi镜像的配置文件()自动创建卷,最后由用户手动挂载就可以使用了。
ubimkvol工具用于在UBI设备上创建UBI卷
Usage: ubimkvol <UBI device node file name> [-h] [-a <alignment>] [-n <volume ID>] [-N <name>]
[-s <bytes>] [-S <LEBs>] [-t <static|dynamic>] [-V] [-m] [-k]
[--alignment=<alignment>][--vol_id=<volume ID>] [--name=<name>]
[--size=<bytes>] [--lebs=<LEBs>] [--type=<static|dynamic>] [--help]
[--version] [--maxavsize] [--skipcheck]option | 说明 | 备注 |
|---|---|---|
-a, --alignment= | 卷对齐(默认为1) | |
-n, --vol_id= | UBI卷ID,如果不指定,卷ID会自动分配 | |
-N, --name= | 卷名字 | 必填 |
-s, --size= | 卷大小。卷大小以字节、千字节 (KiB) 或兆字节 (MiB) 为单位 | 必填 |
-S, --lebs= | 在逻辑擦除块中给出卷大小的替代方法 | |
-m, --maxavsize | 将卷大小设置为最大可用大小 | |
-t, --type=<static/dynamic> | 卷类型(动态、静态),默认为动态 | |
-k, --skipcheck | 跳过在卷打开时完成的 CRC 校验 |
下面是使用示例:
Example: ubimkvol /dev/ubi0 -s 20MiB -N config_data
create a 20 Megabytes volume named "config_data" on UBI device /dev/ubi0.ubiformat工具 格式化MTD设备和烧写ubi镜像
Usage: ubiformat <MTD device node file name> [-s <bytes>] [-O <offs>] [-n]
[-Q <num>] [-f <file>] [-S <bytes>] [-e <value>] [-x <num>] [-y] [-q] [-v] [-h]
[--sub-page-size=<bytes>] [--vid-hdr-offset=<offs>] [--no-volume-table]
[--flash-image=<file>] [--image-size=<bytes>] [--erase-counter=<value>]
[--image-seq=<num>] [--ubi-ver=<num>] [--yes] [--quiet] [--verbose]
[--help] [--version]option | 说明 | 备注 |
|---|---|---|
-s, --sub-page-size= | 用于UBI header的最小输入/输出单元,例如 NAND flash 情况下的子页面大小(默认等于最小输入/输出单元大小) | |
-O, --vid-hdr-offset= | 偏移量,如果 VID header从物理擦除块的开始(默认是 EC 标头之后的下一个最小 I/O 单元或子页面) | |
-f, --flash-image= | 指定ubi镜像文件,如果为’-'表示从stdin读取 | |
-S, --image-size= | 未指定具体的镜像文件时,从stdin读取的字节数 | |
-e, --erase-counter= | 使用 作为所有擦除块的擦除计数器值 | |
-x, --ubi-ver= | 放入EC header 的 UBI 版本号(默认为 1) | |
-Q, --image-seq= | 要使用的 32 位 UBI镜像序列号(默认选择随机数) | |
-y, --yes | 假设这个程序本来会问的所有问题的答案都是“yes” | |
-q, --quiet | 抑制进度百分比信息 |
ubiformat工具示例如下:
Example 1: ubiformat /dev/mtd0 -y
format MTD device number 0 and do not ask questions.
Example 2: ubiformat /dev/mtd0 -y -f ubi.image
format MTD device number 0 and do not ask questions.write ubi.image to /dev/mtd0
Example 3: ubiformat /dev/mtd0 -q -e 0
format MTD device number 0,be quiet and force erase counter value 0.使用ubiformat擦除flash的好处是它可以保留擦除计数器。 另外ubiformat还可以用来烧写ubi镜像(镜像烧写后,需要使用ubiattach工具进行连接后再挂载使用)
UBI 允许在 UBI 卷上创建只读块设备,该卷适用于只读的、面向块的文件系统,例如 squashfs。 UBI允许在 UBI 卷上创建块设备,但有以下限制:
Usage: ubiblock [-c,-r] <UBI volume node file name>option | 说明 | 备注 |
|---|---|---|
-c, --create | 基于UBI卷创建块设备 | |
-r, --remove | 删除基于UBI卷的块设备 |
必须基于UBI卷创建或者删除ubi块设备
$ubiblock -c /dev/ubi0_0
$ubiblock -r /dev/ubi0_0ubi只读块设备被成功创建
# ls /dev/ubiblock* -l
brw------- 1 root root 254, 0 Jun 16 15:19 /dev/ubiblock0_0Linux 配置菜单中
"Device Drivers"
-> "Memory Technology Devices (MTD)"
-> "UBI - Unsorted block images"