linux文件系统——ubifs之ubi子系统(1)

linux文件系统——ubifs之ubi子系统简介(1)

前言

概述

本文档主要介绍Linux UBI子系统的简介及关键问题。

修订记录

日期作者版本修改说明
2023.08.01枫潇潇V1.0.0初始版本
2023.11.05枫潇潇V1.0.1增加UBI操作

1 ubi简介

1.1 ubi层简介

UBI 即 Unsorted Block Images , 其核心是一个逻辑卷管理系统,其位于 MTD RAW 层之上,对上提供平坦的线性逻辑空间,内部实现逻辑可擦除块与物理可擦除块的映射、逻辑卷的管理, FLASH 擦写的负载均衡(wear leveling)、异常断电恢复等功能。

在这里插入图片描述

UBI 逻辑卷(volume)是一系列逻辑可擦除块(LEBS)的集合,每一个逻辑可擦除块都映射到一个物理可擦除块(PEB),逻辑卷的大小是可以动态调整的。

UBI 层在设计上与 UBIFS 并不是紧耦合的, 其上可承载多种文件系统, 虽然现在仍然只有 UBIFS 在使用,但这种松耦合的方式也避免了 JFFS2 类文件系统设
计复杂度高的问题,使得代码更加清晰,方便维护。

1.2 ubi卷(ubi volume)

UBI(拉丁语:“哪里?”)代表UnsortedBlock Images(未分类的块图像)。它是原始闪存设备的卷管理系统,它管理单个物理闪存设备上的多个逻辑卷,并将 I/O 负载(即磨损均衡)分布在整个闪存芯片上。

在某种意义上,可以将UBI和LVM(LogicalVolume Manager逻辑卷管理)相比较。LVM将逻辑扇区映射到物理扇区,而UBI将逻辑擦除块映射到物理擦除块。但是除了映射之外,UBI还实现了全局的损耗平衡和透明的IO错误处理。

一个UBI卷( volume)是一系列连续的逻辑擦除块(Logical Eraseblock LEB)。每一个逻辑擦除块都可以被映射到任意一个物理擦除块(Physical Eraseblock PEB)。这种映射关系是由UBI来管理的。这种管理对使用者来说是不可见的,这也是损耗均衡实现的基础机制(损耗平衡的实现后面再说)。

UBI 卷大小在创建卷时指定,但稍后可能会更改(卷可动态调整大小)。有一些用户空间工具可用于操作 UBI 卷。

UBI 卷有两种类型:动态卷静态卷。静态卷是只读的,其内容受CRC-32校验和保护 ,而动态卷是可读写的,上层(例如文件系统)负责确保数据完整性。

静态卷通常用于内核、initramfs 和 dtb。较大的静态体积在打开时可能会导致显着的损失,因为此时 CRC-32需要计算。如果您希望将静态卷用于内核、initramfs 或 dtb 之外的任何内容,那么您可能做错了什么,最好使用动态卷代替。

/* linux-4.19.132/drivers/mtd/ubi/ubi.h */
struct ubi_volume {
	......
	struct ubi_device *ubi; // reference to the UBI device description object
	int vol_id;				// volume ID
	......
	int reserved_pebs;		// how many physical eraseblocks are reserved for this volume
	int vol_type;			// volume type (%UBI_DYNAMIC_VOLUME or %UBI_STATIC_VOLUME)
	......
	int name_len;			// volume name length
	char name[UBI_VOL_NAME_MAX + 1];	// volume name
	......
	struct ubi_eba_table *eba_tbl;	// EBA table of this volume (LEB->PEB mapping)
	......
};

ubi_volume结构体中的eba_tbl用于记录volume使用了哪些逻辑擦写块,以及逻辑擦写块到物理擦写块的映射; eba_tbl 的数据类型为struct ubi_eba_table,结构体定义如下所示:

/*
 * struct ubi_eba_entry - structure encoding a single LEB -> PEB association
 */
struct ubi_eba_entry {
	int pnum; // the physical eraseblock number attached to the LEB
};

/*
 * struct ubi_eba_table - LEB -> PEB association information
 */
struct ubi_eba_table {
	struct ubi_eba_entry *entries; // the LEB to PEB mapping (one entry per LEB)
};

ubi_eba_table结构体中的entries是个数组,数组长度为ubi_volume结构体中的reserved_pebs,即为这个volume分配的物理擦写块个数;数组元素类型为 struct ubi_eba_entry,这个结构体只包含一个成员pnum;

问题:逻辑擦写块如何映射到物理擦写块?struct ubi_eba_entry结构体中的pnum是什么?
以nand flash来举例说明,假设nand flash的erase block size=128KB,这个nand flash上有一个96MB的mtd分区,假设这个分区名称为mtd1,ubi attach到mtd1;则mtd1总共包含有 96MB/128KB=768个erase block;

如果要读取pnum=10的物理擦写块的内容,则pnum=10在mtd1分区的offset=10*(erase block size)=10*128KB=1280KB,有了offset后就可以通过mtd提供的接口把这个物理擦写块的内容读出来;

如果给定逻辑擦写块lnum=3,通过ubi_eba_table结构体中的entries[lnum].pnum 获取到对应的pnum(因为mtd1有768个erase block,这个值的取值范围:0~767),假设entries[lnum].pnum=10,则lnum=3被映射到pnum=10,即通过逻辑擦写块lnum=3可以读写物理擦写块pnum=10(mtd1分区offset=1280KB)的数据;

1.3 ubi坏块处理

UBI可以检测到坏块并且可以从任何一个坏块操作中释放上层。UBI有一个PEB池,当某一个PEB坏掉后,它就会从PEB池中用一个好的PEB去替换掉坏块PEB。UBI会将好的数据从坏块上移到好的PEB上。这样的结果就是,UBI卷的使用者根本就不会察觉到这些IO错误,因为UBI都帮他们处理掉了。

在对NAND flash进行读写操作时有可能会发生bit-flips(位翻转错误),它可以被ECC的校验总数纠正,但是有可能累积超时最后导致数据丢失。UBI是通过将发生bit-flips的物理擦除块上的数据移到其它物理擦除块的方式来解决这个问题的,这个过程叫做scrubbing(强力擦除)。当然scrubbing操作是在后台运行,并且对上层是不可见的。

1.4 ubi主要特征

  • UBI提供可动态生成、移除或者重设大小的卷(volume)。
  • UBI对整块flash设备实现损耗均衡功能(比如,用户可以对同一块LEB进行连续的写入擦除操作,但是UBI会将这些写入擦除操作扩展到整块flash芯片的所有PEB上)。
  • UBI显式处理坏PEB。
  • UBI通过强力擦除(scrubbing)方式将数据丢失降到最小。

以下是对MTD的分区(partitions)和UBI的卷(volumes)的对比。他们之间存在着一定的联系,因为:

  • 两个都包含的擦除块。UBI卷包含LEB,而MTD分区包含PEB。
  • 都支持3种基本操作:读、写、擦除。

但是UBI卷相对于MTD分区具有以下优势:

  • UBI卷没有擦除块损耗均衡限制,这样使用者就可以完全不用考虑这点,也就是说上层软件代码可以更简单。
  • UBI卷没有坏的擦除块,这个也会让上层代码更简洁。
  • UBI卷可以是动态的,这也就意味着可以对它进行动态的创建、删除、改变大小,然而MTD分区则是静态的。
  • UBI可以处理bit-flips,这同样可以使上层代码更简洁。
  • UBI提供卷更新操作(后面将会讲到),这可以使它更容易检测到软件更新中断并从中恢复。UBI提供原子LEB改变操作(后面将会讲到),这可以允许去改变LEB中的内容而不需要释放掉在操作过程中由于异常重启而产生的数据。这对上层软件可能非常重要(比如对一个文件系统)。
  • UBI有取消映射(un-map)操作,它将LEB和PEB之间的映射关系取消,为了PEB的擦除和返回进行调度。这个非常的快,并且可以使上层软件不用去实现延迟擦除机制(例如,JFFS2就必须实现这个机制)。

1.4 ubi断电容忍性

UBI和UBIFS均被设计为具有对断电的容忍性。

UBI拥有一个内部调试基础设施,可以模拟断电以进行测试。模拟的优势在于它在关键点模拟断电,这些点是控制数据结构被写入设备的地方,而通过物理断电测试在这些精确时刻中断系统的概率相对较低。

2 ubi专业词语解析

2.1 UBI Vs. UBIFS

如果说UBI在MTD之上,在FS之下的中间层,用于抽象MTD屏蔽nand差异,那么ubifs就是正儿八经的文件系统。
ubifs是基于UBI子系统的文件系统,实现文件系统该有的所有基本功能,例如文件的实现,例如日志的实现。这里需要特别注意的是,ubifs跟jffs/yaffs相比,并不包含nand特性的管理,而是交由ubi来实现。

2.2 UBI Vs. Block Layer

Block Layer是适用于常见块设备的通用块层,其特有的概念有bio、request、电梯算法等,其典型的设备有磁盘、SSD、mmc等。
而ubi基于mtd,虽然能模拟块设备,从本质上来讲其并不是块设备。跟踪UBIFS的IO操作,发现其IO操作并不经过通用块设备层。

2.3 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等。

2.4 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卷和分区的概念之间还是有差别的。

2.5 LEB Vs. PEB

在UBI子系统中,还有LEB和PEB的概念:

LEB指Logical Erase Block,即逻辑擦除块,简称逻辑块,表示逻辑卷中的一个块
PEB指Physical Erase Block,即物理擦除块,简称物理块,表示物理Nand中的一个块

2.6 UBI子系统扮演的角色及其作用

UBI子系统就是ubifs与mtd之间的中间层,其向下连接MTD设备,实现nand特性的管理逻辑,向上呈现无坏块的卷。所以UBI子系统的作用,主要包括两点:

屏蔽nand特性(坏块管理、磨损平衡、位翻转)
UBI卷的实现

3 ubifs用户空间工具

ubi的工具集成在包mtd-utils中,分别有以下工具及其作用:

工具作用
ubinfo提供ubi设备和卷的信息
ubiattach链接MTD设备到UBI并且创建相应的UBI设备
ubidetachubiattach相反的操作,将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设备的信息

4 ubi头

UBI将在每个非坏的PEB的开始存储两个64bytes的头。
擦除计数头(erase counterheader EC header)包含了PEB的擦除计数和一些其它的并不重要的信息。
卷标识头(volumeidentifier header VID header)存储了卷ID和PEB归属于LEB的数目(这个不太理解),另外还有一些其它相对不太重要的数据。

  • 擦除计数头 — struct ubi_ec_hdr

    在这里插入图片描述

struct ubi_ec_hdr {
	__be32  magic;			// Erase counter header magic number (ASCII "UBI#")
	__u8    version;		// version of UBI implementation which is supposed to accept this UBI image
	__u8    padding1[3];	// reserved for future, zeroes
	__be64  ec; // the erase counter /* Warning: the current limit is 31-bit anyway! */
	__be32  vid_hdr_offset;	// where the VID header starts
	__be32  data_offset;	// where the user data start
	__be32  image_seq;		// image sequence number
	__u8    padding2[32];	// reserved for future, zeroes
	__be32  hdr_crc;		// erase counter header CRC checksum
} __packed;
  • 卷标识头 — struct ubi_vid_hdr

    在这里插入图片描述

struct ubi_vid_hdr {
	__be32  magic;		// Volume identifier header magic number (ASCII "UBI!")
	__u8    version;	// version of UBI implementation which is supposed to accept this UBI image (%UBI_VERSION)
	__u8    vol_type;	// volume type (%UBI_VID_DYNAMIC or %UBI_VID_STATIC)
	__u8    copy_flag;	// if this logical eraseblock was copied from another physical eraseblock (for wear-leveling reasons)
	__u8    compat;		// compatibility of this volume (%0, %UBI_COMPAT_DELETE,%UBI_COMPAT_IGNORE, %UBI_COMPAT_PRESERVE, or %UBI_COMPAT_REJECT)
	__be32  vol_id;		// ID of this volume
	__be32  lnum;		// ogical eraseblock number
	__u8    padding1[4];// reserved for future, zeroes
	__be32  data_size;	// how many bytes of data this logical eraseblock contains
	__be32  used_ebs;	// total number of used logical eraseblocks in this volume
	__be32  data_pad;	// how many bytes at the end of this physical eraseblock are not used
	__be32  data_crc;	// CRC checksum of the data stored in this logical eraseblock
	__u8    padding2[4];// reserved for future, zeroes
	__be64  sqnum;		// sequence number
	__u8    padding3[12];// reserved for future, zeroes
	__be32  hdr_crc;	// volume identifier header CRC checksum
} __packed;

由于每个头会占用一些flash空间,所以LEB会比PEB小。

当UBI和MTD设备关联(attach)时,UBI会对它进行扫描,读取所有的头,检测CRC-32 checksums,存储擦除计数,在RAM中建立逻辑到物理擦除块的映射信息。

当UBI擦除一块PEB之后,它将把该EC header中的计数加一。这意味着PEBs中总是会有EC header,除了在擦除EC header之后到写入新的EC header之前这段非常短的时间内。如果在这段时间内系统发生了意外重启,EC header将会丢失或者被毁坏。在这种情况下,UBI将在MTD设备扫描完成后,将会向该PEB中重新写入一个EC header,它的擦除计数将取所有擦除计数的平均值。

当UBI将PEB和LEB建立联系的时候,VID header将会被写入PEB。在以下的操作中,UBI头相应的改变情况:

  • LEB解除映射操作(un-map),将解除LEB和PEB之间的映射,并且会调用PEB进行擦除。当PEB被擦除后,EC header将会被立刻写入,但VID header将不会被写入。

  • LEB映射操作(map)或者对一个未映射的PEB进行写操作,将使得UBI找到一个合适的PEB并向其中写入VID header(EC header必须已经被写入)。注意,如果是向一个已经映射的PEB进行写操作,只将数据写入其中而不会去改变UBI的头。

UBI维护着两个per-PEB header,因为它需要在不同的时刻向flash写入不同的信息。

  • 当PEB被擦除后,ECheader将会被立马写入,以此来降低由于系统意外重启而造成的擦除计数的丢失。

  • 当UBI将PEB和LEB之间建立连接时,VIDheader将会被写入PEB。

当EC header被写入PEB的时候,UBI还不知道这个PEB的VID,也不知道这个PEB将和哪个LEB建立联系。这就是为什么UBI需要做两次独立的写操作,和有两个分离的头。

以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:取所有物理块的擦除次数的平均数

5 UBI 卷表

volume又分内部volume用户创建的volume;内部volume对外不可见,比如layer volume

卷表(Volume table)是一个芯片上的数据结构,该结构体包含了在这个UBI设备上的每一个卷信息。这个卷表是卷记录的一个数组(volume table records),每一个记录(数组成员)包含以下信息:

//linux-5.4 drivers/mtd/ubi/ubi-media.h
struct ubi_vtbl_record {
    __be32 reserved_pebs;   //物理块数量 
    __be32 alignment;       //卷对齐
    __be32 data_pad; 		//为满足对齐要求,每个PEB末尾未使用的字节数大小
    __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;

每一个记录对应描述一个UBI 卷,并且记录自己在卷表数组中对应VID索引。比如,UBI 卷0对应着卷表数组中的成员0,以此类推。该数组的成员数量是受LEB大小限制的,并且不能超过128.这意味着UBI设备不能拥有超过128个卷。每次当UBI卷进行创建、移除、更改大小、重命名或者更新的时候,相应的卷表记录都将会改变。为了可靠性和异常断电等原因,UBI维护着两份UBI卷表。

UBI子系统有个对用户隐藏的特殊卷,叫层卷(layout volume),用来记录卷表。我们可以把卷表等价于分区表,记录各个卷的信息。卷表大小为2个逻辑擦除块,每个逻辑擦除块记录一份卷表,换句话说,UBI子系统为了保证卷表的可靠性,用2个逻辑记录2分卷标信息。layer volume也是用struct ubi_volume结构体来表示,只不过这个volume内部物理擦写块记录的是用户创建了多少个volume,每个volume的名称,volume type,volume的容量等,在layer volume中使用 struct ubi_vtbl_record 来描述一个volume的信息。

由于层卷的大小是固定的(2个逻辑块),导致能保存的卷信息受限,所以最大支持的卷数量是随着逻辑块的大小改变而改变的,但最多不超过128个。

由上面结构体我们可以发现,卷信息是被CRC32保护着的。比较有意思的有两个成员:vol_typeflags

5.1 vol_type(卷类型标识)

vol_type成员标记了卷的类型,在创建卷时指定,可选动态卷和静态卷。

5.2 flags(更新标识)

更新标识#flags成员常用于标识是否自动重分配大小。怎么样自动充分配大小呢?在首次运行时autoresize卷,让卷大小覆盖所有未使用的逻辑块。

例如Flash大小是128M,在烧录的镜像中分配的所有卷加起来只用了100M,如果有卷被表示为autoresize,那么在首次运行时,那个卷会自动扩大,把剩余的28M囊括在内。这个功能挺实用的,例如某个方案规划中,除去rootfs、内核等必要空间外,把剩余所有空间尽可能分配给用户数据分区。

在开发过程中加了个应用,导致rootfs卷需要更大的空间,进而需要压缩user_data卷的空间。

如果user_data空间是autoresize的,那么user_data卷的空间就会自动压缩。再例如旧方案用的是128M的nand,后面升级为256M,即使使用相同的固件,也不用担心多出来的128M浪费掉了,因为user_data卷自动扩大囊括多出来的128M。

需要注意的是,只允许1个卷设置autoresize标志

5.3 实现细节

在内部,卷表是存在于卷层(layout volume)里的,卷层是一个具有特殊目的的UBI卷,这个卷包含了两个LEB,一个用于卷表的每次拷贝。卷层是一个内部的UBI卷,使用者是无法访问和获取到它的。UBI使用和普通用户卷同样的机制对卷层进行读写。

在这里插入图片描述

UBI使用以下的算法来更新卷表的记录。
Ø 在内存中先准备好新卷表的内容
Ø 取消卷层(layoutvolume)中LEB0的映射
Ø 将新的卷表写入到LEB0
Ø 取消卷层(layoutvolume)中LEB1的映射
Ø 将新的卷表写入到LEB1
Ø 清除UBI工作队列以确保对应于取消映射的LEB的PEB被擦除了

当和MTD设备建立关联时,UBI将确保这两个卷表中的内容是完全相同的。如果异常重启导致了这两个表不相同,UBI将把卷层中的LEB0的内容拷贝到LEB1。如果在卷表的拷贝过程中发生崩溃,UBI将从另一个卷表中拷贝数据,以此来恢复卷表。

6 Flash最小输入/输出单元

UBI使用的是flash的一个抽象模型,简而言之,从UBI的角度来看,flash(或者MTD设备)是由擦除块组成的,这些擦除块可能是好的也可能是坏的。好的擦除块我们可以进行读写和擦除,好的擦除块也有可能变成坏的然后被标记。
Flash的读写只能以最小输入输出单元的大小来进行,这个单元大小因flash类别不同而不同。
Ø NOR flash一般是一个字节(实际上,甚至可能对个别的bit位进行改变)。
Ø NAND flash最小单元一般是512、2048或者4096,这个对应与NAND flash的页大小。NAND flash将每一页的ECC存放在OOB区域,这就意味着NAND flash的页必须一次性的被整体写入,以此来计算ECC,读取的时候也必须是整页的被读出,以此来校验ECC。

最小IO单元大小是MTD设备一个非常重要的参数,它影响着很多方面,比如:
Ø VID header的物理存储位置,同样LEB的大小也和它有关。一般来说,最小IO单元越大,LEB越少,UBI空间越大。
Ø 写入LEB的数据都必须以此来对齐。读数据并没有要求这样,但是请记住,在MTD层的所有的读操作都是以最小单元来执行的。它是将数据读上来,然后放在一个缓存中,再把用户所需求的字节数拷给用户。

7 UBI头位置

EC头的位置总是在PEB的开头(offset 0)并占据64个字节,VID头存在于下一个可获得的最小IO单元或者子页,并且也占据64个字节大小。例如:
Ø NOR flash最小单元为1字节,那么VID头就在64字节偏移处
Ø 没有子页的NANDflash,VID头存在于第二个NAND页的开头
Ø 有子页的NANDflash,VID头存在于第二个子页头

8 Flash空间开销(overhead)

UBI使用了一部分的flash空间用于它自身功能的实现,因此UBI用户所获得的空间会比实际的flash空间要少。也就是说:
Ø 两个PEB用来存储卷表
Ø 一个PEB被保留,用以损耗均衡
Ø 一个PEB被保留,用以原子LEB改变操作
Ø 一定数量的PEB被保留,用以处理坏PEB;这个是用于NANDflash而不是NOR flash;保留的数量是可配置的,默认情况是每1024块保留20块。
Ø 在每个PEB的开头存储EC头和VID头;

这个所占用的字节数因flash类型的不同而不同,接下来将会进行解释。
符号解释:
Ø W–flash芯片上的PEB总数(注意:是整块芯片,而不是MTD分区)
Ø P—MTD分区上PEB总数
Ø SP–PEB大小
Ø SL –LEB大小
Ø BB –MTD分区坏块数
Ø BR –为处理坏PEB而预留的PEB数。对于NANDflash默认等于20*W/1024,NOR flash为0
Ø B—MAX(BR,BB)
Ø O—存储EC和VID头的开销,单位为字节。例如O = SP – SL这样UBI的开销为(B +4) * SP + O * (P – B – 4)。这就是用户所不能获得的总字节数。O因flash的类型的不同而不同。

  • 对于NOR flash来说,O是128字节
  • 对于没有子页的NANDflash,O是两个NAND页,如果一个NAND页是2k,那么O就等于4k
  • 对于有子页的NANDflash,UBI将优化它的flash层,EC头和VID头将被放在同一个NAND页里面,但是不同的子页里面。这种情况下,O等于一个NAND页的字节数

注意:以上的公式是将坏块也作为了UBI的开销,实际上UBI的开销其实是:(B - BB + 4) * SP + O * (P - B - 4)

为了管理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

9 保存擦除计数

当使用UBI时,注意到UBI会将擦除计数存储在flash媒介上对我们来说非常重要。也就是说,每一个PEB中都有一个EC头,它里面存储了该PEB被擦除的总次数。当然,确保这些数据的不丢失是非常重要的,也就意味着,我们使用的擦除和写入UBI镜像的工具必须是了解UBI特性的(UBI-aware)。Mtd-utils仓库中有一个ubiformat工具,它就能很好的实现UBI的擦除和写入操作。

10 UBI flasher是怎样工作的

以下这个列表是UBI flasher程序在擦除flash或者在写入UBI镜像时必须要做的工作:
Ø 首先,扫描flash并且收集擦除计数。也就是说,它将会从PEB中读取EC头,校验CRC-32的checksum,并且把擦除计数存储在RAM中。没有必要去读取VID头,坏的PEB将会被跳过。
Ø 接下来,计算平均擦除计数器。这将用于具有损坏或缺失EC头的PEB。由于意外重启,这样的PEB可能会发生,但它们不应该太多。
Ø 如果是为了擦除flash,那么每个PEB将会被擦除,相应的EC头也会被写入到PEB的开头。**这时的擦除计数将加一。坏的PEB应该被跳过。**对于NAND闪存,在擦除或写入时发生I/O错误的情况下,应将PEB标记为坏块。
Ø 如果是写入UBI镜像的话,flasher将对每个非坏的PEB进行以下操作:

  • 将UBI镜像读入到一个buffer中
  • 用0xff将最小IO单元补齐
  • 擦除PEB更改内存中的EC头—将擦除计数加一,重新计算CRC-32的checksum
  • 将buffer写入到PEB

通常情况下,坏PEB是会被跳过的。对于NAND flash,在进行擦除和写操作时,如果发生了IO错误,该PEB应该被标记为坏块。

在实际当中,写入的UBI镜像长度通常是小于flash的长度的,所以flasher就应该合理的使用PEB,并且合理的擦除那些未使用的PEB。

注意,当写入一个UBI镜像时,镜像的头被写入到哪个PEB中是没有影响的。比如,镜像头有可能被写入到第一个PEB,或者第二个或者最后一个PEB,这都没有关系。

如果你的UBI镜像中包含UBI文件系统(UBIFS),并且你的flash是NAND flash,你必须在最后一个PEB中补齐0xFF。尽管并不是所有的NANDflash都需要进行这项动作,但补齐动作仍然是非常重要的。有些时候不进行这个补齐动作,将会产生一些非常奇怪的bug,调试也很难去定位解决。建议每次写完都进行补齐操作(难道这个补齐动作,要开发者去完成?那怎么去进行这个操作呢?)。

因为UBI文件系统是将NAND flash中那些只含有0xFF的页视为空闲页的(free),所以我们必须要进行补齐操作。比如,假设NAND flash中的一个PEB,其第一页中有一些数据,第二页为空,第三页也有一些数据,第四页和剩下的页都为空。在这种情况下UBIFS将认为从第四页开始为空,写入数据时也会从第四页开始。然而,如果flasher程序已经向这些页写入了0xFF,那么他们将会被写入两次。但是,很多NAND flash芯片要求页只能被写入一次,即使被写入的数据只含有0xFF。(这里我理解就是,每一页只能被连续写入一次,但这样也不太可能啊,OTP操作时就会对一页进行多次写操作,难道是OTP的特殊性?)

换句话说,写入0xFF可能会产生副作用。Flasher必须要做的就是,在写入之前,从PEB缓存(buffer)的尾开始丢弃所有的空页。没有必要丢弃所有的空页,仅仅只需要丢弃最后一个。这意味着flasher没有必要为了0xFF去扫描所有的缓存。只需要从缓存的结尾扫描就足够了,当遇到第一个非0xFF的字节就结束扫描,这将快很多,以下是来自UBI中的代码:

/**
 * calc_data_len - calculate how much real data is stored in a buffer.
 * @ubi: UBI device description object
 * @buf: a buffer with the contents of the physical eraseblock
 * @length: the buffer length
 *
 * This function calculates how much "real data" is stored in @buf and returnes
 * the length. Continuous 0xFF bytes at the end of the buffer are not
 * considered as "real data".
 */
int ubi_calc_data_len(const struct ubi_device *ubi, const void *buf,
		      int length)
{
	int i;

	ubi_assert(!(length & (ubi->min_io_size - 1)));

	for (i = length - 1; i >= 0; i--)
		if (((const uint8_t *)buf)[i] != 0xFF)
			break;

	/* The resulting length must be aligned to the minimum flash I/O size */
	length = ALIGN(i + 1, ubi->min_io_size);
	return length;
}

这个函数将在把缓存中的内容写入PEB之前调用。这个函数的作用就是从尾部开始丢弃0xFF,并且防止以上的情况发生。Ubi min_io_size就是最小的输入输出单元,也就是等于一个页大小。

顺便说一下,在JFFS2中我们也遇到过相似的问题,JFFS2镜像先填补到PEB的大小,然后再写入到我们的NAND flash。Flasher就不会被跳过空页而烦恼,当JFFS被挂载时,写NAND页时也不会失败,但是后来我们发现了怪异的ECC错误。花了相当长的时间才找出这个错误,换句话说,这也是和JFFS2镜像有关的。

在使用mkfs.ubifs工具制作UBIFS的时候,可以使能“空闲空间确定(free space fixup)”选项,这将使你的flasher不用去担心PEB后面的0xFF字节,如果你需要使用一个工业级flash程序来写UBI镜像,这将非常有用。

11 标记擦除块为坏块

UBI在以下两种情况下会将PEB标记为坏块:
Ø 擦除块的写操作失败,在这种情况下UBI将该PEB中的数据拷贝到其它PEB中(数据恢复),以后将会调用该坏PEB。
Ø 有EIO错误的擦除操作失败,在这种情况下,擦除块将会立马被标注为坏块。

调用坏PEB的目的是为了检测该PEB是否真的坏掉。写入失败的原因很多,包括驱动中的bug或者上层比如文件系统的错误(比如,文件系统错误的向同一个NAND页写入很多次)。调用该PEB时,UBI完成以下工作:
Ø 擦除该擦除块Ø 然后读出其内容,确保里面所有的数据都为OxFF
Ø 写入测试模式字节
Ø 读出里面的数据,并和测试模式的字节进行比较
Ø 这样测试多个测试模式(0Xa5,0x5a,0x00)

如果擦除块通过了以上的测试,那么它不会被标记为坏块。具体的可参考torture_peb()函数。

12 扩展性问题

不幸的是,UBI对flash大小是进行线性量化的。UBI的初始化时间是和flash的PEB数成线性相关的。这意味着,flash越大,UBI初始化(比如,和MTD设备的关联)也将花更多的时间。注意,从linux的3.7版本开始,UBI提供了一个可选的试验的特征“快速映射 (fastmap) ”,这将使得连接几乎是一个固定的时间,可参看fastmap章节。初始化时间主要依赖于flash的IO速度,CPU的速度对它也有一定的影响,因为:
Ø 在 attaching 的时候UBI会去扫描MTD设备,它将会读取每一个PEB的EC头和VID头;每个头有64个字节,这就意味着从每一个PEB中将会读取128字节;不管怎样这都比JFFS2挂载在MTD设备上读取的内容少很多,所以UBI挂载在MTD设备上时要比JFFS2要快很多。
Ø UBI计算每一个EC头和VID头的CRC-32的checksum,这也将消耗一定的CPU,尽管和flash IO开销相比,这会小很多。

实现细节
一般情况下,UBI需要操作3个表
Ø 卷表(volumetable)里面包含了每个卷的信息,比如卷的大小类型等
Ø 擦除块关联表(eraseblockassociation(EBA) table),它里面包含了逻辑到物理块的映射信息;比如,在读一个LEB时,UBI首先去查找这个表找到对应的PEB号,然后再从PEB中将内容读取上来。
Ø 擦除计数表(erasecounter(EC) table),它里面记录着每一个PEB的擦除数;UBI的损耗均衡子系统在需要查找时会用到这个表,比如,一个被过度使用的LEB。

卷表是保存在flash上的。仅当UBI卷被创建、删除和改变大小时,这个表才会被改变。这些操作对时间的要求都不高,UBI可以提供一个慢的和简单的方法来管理卷表。

每次将LEB映射到PEB,或者将PEB擦除,都会改变EBA和EC表,这些操作经常进行,所以需要提供快速的管理方法。

这也就意味着UBI必须扫描整个flash芯片,并且从每个PEB中读取EC和VID头信息,以此在RAM中建立EC和EBA表。

这样做的缺点就是较差的扩展性,并且相对的增加了NAND flash的开销,但好处是简单的二进制格式和稳健性。

13 为处理坏块而预留的块(仅对于NAND flash芯片)

众所周知,NAND flash芯片会被制造商标记一定数量的坏块,在NAND flash芯片的使用周期内也会有新的坏块产生。尽管,制造商保证前几块PEB不是坏的,并且PEB的坏块数量不会超过一定值。

**20/1024这个比例,是为UBI设备预留的一个默认的PEB数量。**比如,在一个有 4096 PEB块的NAND flash上有两个UBI设备,那么将会为每一个UBI设备预留40个PEB。这看起来是对空间的一种浪费,但鉴于坏块会产生,这是一个相对安全的处理方式。所以相对于在一块NAND flash芯片上使用多个UBI设备,使用一个UBI设备和多个UBI卷,空间利用率将会更高。

**每1024个PEB预留20个PEB,这个默认值是一个内核配置选项。**对于每一个UBI设备来说,这个值可以通过内核参数进行调整,或者通过ubiattach参数进行调整(3.7内核版本以后)。

14 卷自动调整大小

当需要创建一个需要烧写到终端用户设备上的UBI镜像时,应该为每个卷确定精确的大小(这个大小被保存在UBI卷表里)。但通常,在嵌入式的世界中,我们更倾向于用一个只读的卷来存放根文件系统,一个可读可写的卷用来存放其它的(比如,日志、用户数据等)。如果根文件系统的大小是确定的,那么第二个卷的大小我们就是可变的,一般是使用剩下的所有空间。

如果卷有auto-resize标志,当UBI第一次运行的时候,它的大小将被扩大。当卷大小被调整过以后,UBI将移除auto-resize标志,并且这个卷将不会再被重新设定大小(re-sized)。Auto-resize标志被存储在卷表中,并且只能一个卷被标记为auto-resize。

15 UBI操作

15.1 取消LEB映射(LEB un-map)

LEB的取消映射操作是由内核的接口函数 ubi_leb_unmap() 函数实现的。从内核的2.6.29版本以后,用户空间的程序可以通过 ioctl 的 UBI_IOCEBUNMAP 命令来获取该函数操作。Ioctl调用必须是针对UBI卷的字符设备的。

LEB的取消映射操作如下:

Ø 首先取消LEB和PEB之间的对应关系

Ø 然后擦除PEB并返回;它并不等待PEB擦除的完成;PEB是由UBI的后台线程擦除完成的。

当读取未映射的LEB时, UBI返回所有0xFF字节,因此未映射操作可以被认为是非常快的擦除操作。

但是有个方面,UBI使用者必须要注意。

假设你取消映射了映射到PEB P的LEB L。由于P并没有同步擦除,而只是被安排了擦除,如果在P被物理擦除之前发生非正常的重新启动,可能会出现一些“意外”情况:如果在下一次启动时UBI attach MTD设备之前发生重新启动,L将再次被映射到P。确实,UBI将扫描MTD设备,找到引用L的P,然后将此映射信息添加到EBA表中。

然而,一旦你向LEB L写入任何数据,或者使用LEB映射操作将其映射到新的PEB,旧的内容就会永远消失。因为即使在非正常重新启动的情况下,UBI也会选择L的较新映射。

15.2 实现细节

这一节描述UBI是怎样在异常重启的情况下区分LEB的新旧。假设我们取消LEB LPEB P1之间的映射,也就是说UBI调用后台线程去擦除P1,然后我们向L写入数据,这意味着UBI将会找到另外一个PEB P2,并将L映射到P2,把数据写入到P2。如果在P1物理擦除完成之前,但在写入操作之后,系统发生了异常重启,这样程序结束的时候,其实我们是将L映射到了两个PEB(P1和P2)

为了处理这种情况,UBI维护着一个全局的64位序列号变量。当每次PEB被映射到LEB的时候,这个序列号都会增加。并且它的值被保存在PEB中的VID头中,因此每一个VID头都有一个独一无二的序列号。并且序列号值越大,这个VID头就越新。当UBI和MTD设备建立关联的时候,UBI会找到存储在所有存储在VID头中的该序列号的最大值,然后将其加一后赋给这个全局的序列号。

在以上的情况中,UBI将会选择P1和P2中序列号大的PEB进行映射。

当UBI出于损耗均衡的目的将一个PEB的内容拷贝到另一个PEB时,或者在原子LEB更改操作时,发生异常重启,这些情况比以上的情况要复杂的多。在这种情况下,仅仅选择较新的PEB是不够的,还必须确保获取的新PEB的数据。

15.3 LEB 映射

LEB的映射操作将先前未映射的LEB映射到PEB。例如,在对LEB A进行映射操作时,UBI将会找到一个合适的PEB,然后将VID头写入到PEB,然后将此修改加入到内存中的EBA表。这个VID头就会指向LEB A,这样在对LEB A进行IO操作时,实际上都会映射到PEB上。

UBI内核API函数ubi_leb_map()将完成LEB的映射功能,或者通过ioctl函数的UBI_IOCEBMAP命令,ioctl函数接口在2.6.29版本以后才有。

LEB映射操作的功能之一是确保删除旧的LEB内容。就如这一章节解释的一样,当对一个LEB进行取消映射时,它所对应的PEB并不是立马被擦除的。并且此时发生了异常重启,LEB可能会在UBI attach MTD设备后再次映射到同一个PEB。因此,如果在取消映射后立即映射LEB,则可确保删除旧的LEB内容。换句话说,即使在非正常重新启动的情况下,LEB映射操作返回后,LEB也保证只包含0xFF字节。

请谨慎使用LEB映射操作。除非真正需要,否则不要使用它,因为映射的LEB会对UBI磨损均衡子系统增加更多开销,与未映射的LEB相比。实际上,如果LEB取消映射,就没有包含此LEB数据的PEB,磨损均衡子系统不必移动任何数据来维持磨损均衡。相反,如果LEB映射到PEB,磨损均衡子系统就需要关心一个额外的PEB,如果当前PEB的擦除计数太低,则需要将LEB重新映射到另一个PEB(然后将LEB重新映射到擦除计数较高的PEB,旧的PEB用于其他操作)。

15.4 卷更新

对于设备软件更新,卷更新是非常有用的。该项操作将用新的内容改变所有的UBI卷。但是,如果在更新操作过程被中断,卷将进入崩溃(corrupted)状态,并且在该卷上的进一步的操作都将以EBADF错误而结束。唯一的解决办法就是,将该卷再进行一次更新操作,并将它顺利完成。

卷更新操作可以从用户空间的UBI接口获得,但是不能从UBI的内核API获取。为了更新卷,首先要打开UBI字符设备,调用ioctl的UBI_IOCVOLUP命令,然后传递一个64位的值,它就是新卷的长度(单位为字节)。然后这个总字节数将被写入到卷字符设备,一旦最后一个字节被写入到字符设备,那么这次更新操作就算完成了。操作流程如下:

fd = open("/dev/my_volume");
ioctl(fd, UBI_IOCVOLUP, &image_size);
write(fd, buf, image_size);
close(fd);

include/mtd/ubi-user.h获取更多细节。请记住,卷中的老的内容将不会被保存,除非卷更新操作被中断。另外,也不必一次性将所有新数据写入,可以调用write()函数任意多次,然后每次传入任意长度的数据。当所有数据被写入后,更新操作才算完成。如果最后一次写操作,写入的数据比UBI期望的数据多,那么多余的数据将被忽略掉。

卷截断(volume truncation)是卷更新操作的一个特例,如果数据长度为0,它将调用相同的ioctl命令,在这种情况下,卷里的内容将会被清空,全部变为0xFF(所有的LEB也将被取消映射)。

注意, /sys/class/ubi/ubiX_X/corrupted文件系统文件反映卷状态为“corrupted(崩溃)”:如果卷是好的话,它包含“0\n”的ASCII码,如果崩溃的话包含“1\n”

卷更新的执行得助于updatemarker。一旦用户调用ioctl的UBI_IOCVOLUP命令,UBI就将会设置UBI卷表中对应于该卷的update marker标志。然后卷将会被清空等待用户空间传入数据。一旦所有的数据被传入并被写入了flash,update marker标志也将会被清除。但是在更新卷操作被中断的情况下,update marker将不会被清除,该卷也会被当做“崩溃”来对待。直到一次新的成功的卷更新操作完成后,update marker才会被清除。

15.5 LEB原子更改(Atomic LEB change)

LEB的原子更改操作,是为了在对LEB内容进行更改时,以防中断发生,造成老数据的丢失。也就是说,执行该操作,LEB中的数据要么是老的要么就是新的。

这项操作可以通过内核API调用函数ubi_leb_change()来完成。从内核的2.6.25版本开始,这项操作对用户空间也提供了接口。通过ioctl的UBI_IOCEBCH命令来实现。在调用该命令时,首先要对struct ubi_leb_change_req的结构体进行适当的填充,该结构体存储了需要修改的LEB号和新内容的长度,然后将其指针传递给调用函数。然后向卷字符设备写入指定数量的字节数。该操作和卷更新操作有些类似,其操作步骤如下:

struct ubi_leb_change_req req;
 
req.lnum = lnum_to_change;
req.len = data_len;
fd = open("/dev/my_volume");
ioctl(fd, UBI_IOCEBCH, &req);
write(fd, data_buf, data_len);
close(fd);

如果由于某种原因,用户还没有写完所需求数量的字节,而此时文件却被关闭,那么这项操作将被取消,老的数据将会被保存到LEB中。

和卷更新操作一样,对write()的调用次数和每次写入数据的多少都是没有限制的。一旦最后一个字节被写入到LEB,该次LEB原子更改操作即完成。

LEB的原子更改操作对文件系统来说将非常有用,比如对UBIFS文件系统,它就是用此操作来提交文件系统的索引的。

需要记住的是,LEB的原子更改操作会去计算新数据的CRC-32的checksum,所以相对于LEB的擦除再加上写入操作,LEB原子更改操作会有一些开销。由于卷更新操作不会去计算数据的CRC-32,所以更新卷将会比原子更改所有的擦除块要快。这一额外的开销要被记住,也就是说如非必要,不要使用该操作。

15.6 实现细节

假设UBI需要去改变LEB 0,L是被映射到PEB P0。首先UBI总会保留一块空闲的PEB来支持LEB的原子更改操作,这里我们称它为P2。在进行该操作之前,P0存储着LEB 0的内容,而P2为空(里面只有EC头,其余全为0xFF)。新的数据会被写入到P2,而不是P0,所以如果该操作过程中发生了异常,LEB的老数据总是在那里的。

在这里插入图片描述

如果在LEB原子更改操作进行到一半的时候,发生了异常重启,UBI再次和MTD设备进行关联的时候,明显,UBI应该保存L->P0的映射,并且擦除P2的内容。但是当异常重启发生在,LEB原子更改刚刚完成,但是P0的物理擦除还没有完成,明显,UBI应该保存L->P2的映射,然后把P0擦除。

为了解决以上的情况,UBI会在新数据写入flash之前,计算新数据的CRC-32的checksum,并且将其存放在VID头里面(数据长度也会被存放在里面)。在初始化的时候,当UBI发现同一个LEB被映射到两个PEB(P0和P2)的时候,仅当数据的CRC-32是正确的时候(也就意味着所有的数据已经被写入到flash了),UBI才会去选择拥有较高序列号(前面有介绍)的P2,否则UBI将会选择较低序列号的P1。当然,为了校验CRC-32的checksum,UBI必须读取LEB中的内容。

16 快速映射(Fastmap)

快速映射是一个试验的可选的UBI特征,可以将CONFIG_MTD_UBI_FASTMAP设置为y来使能该功能。一旦被使能,UBI将评估模块参数”fm_autoconvert”。如果它被设置为1(默认为0),UBI将为每一个被关联镜像(attachedimage)自动使能快速映射。这意味着UBI用快速映射的数据创建了一个新的内部卷,以便下次快速关联模式可以使用这些数据。
在默认配置下,UBI将会使用存储在快速映射卷里面的信息,以此来加速关联(attach)过程。如果想测试快速映射,设置fm_autoconver为1并将其关联(attach)到一个卷。

以下是可进行的配置:

CONFIG_MTD_UBI_FASTMAPFm_autoconvertresultn
n0快速映射完全被屏蔽
y0如果镜像上存在一个快速映射,它将附在(attach)UBI上,但是如果没有快速映射,也就不会有快速映射会被安装在镜像上。
y1如果镜像上存在一个快速映射,它将附在(attach)UBI上,快速映射会自动的被安装在所有依附(attach)的镜像上。

16.1 向后兼容

快速映射存在在磁盘上的数据结构,利用删除兼容卷,因此快速映射使得镜像可以完全向后兼容UBI中那些不支持快速映射的实现版本。

16.2 技术实现

一个磁盘存储的快速映射包含所有的需要附加到整个镜像的信息,也就是说所有的擦除计数值,所有的PEB列表和他们的状态,所有卷的列表和他们当前的EBA……为了避免对快速映射的太多次的写入,快速映射有一个PEB列表,这些PEB是已经更改过的或者需要在附加时进行全扫描的。这个表叫作快速映射池,并且有一个固定的大小,PEB总数的5%。仅当UBI对快速映射进行写入,并且快速映射池里没有空闲的PEB时,才会使用到这个技术。否则,每当一个卷的EBA改变时,UBI都会写入到快速映射。

快速映射有一个超级块(也叫作PEB锚),并且可以负载任何PEB上的数据。PEB锚必须在MTD设备的前64个PEB中,它有一个指向其它剩余PEB的指针,而这些PEB才是真正的装载实际的快速映射数据。在现在的NAND flash芯片中,整个快速映射是放在一个单独的PEB中的,因此,PEB锚指向它自己。在加载快速映射数据以后,UBI将据此来生成信息结构。

attach 流程如下
Ø UBI试图找到快速映射PEB锚,如果没有找到PEB锚,UBI将进行一个传统的全扫描
Ø 根据存储在PEB锚中的指针读取快速映射的负载数据
Ø 仅把池中的PEB执行一个传统的扫描,而不是所有的PEB。

如果UBI检测到使用的快速映射是无效的,它将自动退回扫描模式并执行一个全扫描。使用CRC32的checksum和一致性来校验内部的UBI结构体,以此来判断快速映射是否有效。

每当快速映射池满,快速映射将会被写入设备,卷层将改变或者镜像被detach。也许会疑问,为什么在detach的时候需要被写入,如果UBI在detach的时候不写一个新的快速映射,所有的擦除计数将发生修改当上一个快速映射写入已丢失。

16.3 开销

如果使能了快速映射,UBI将存储足够的PEB来装载两个完整的快速映射。在实际当中,NAND flash芯片有两个PEB是为快速映射预留的。

也有一些运行时的开销,为了确保新的快速映射是有效的,UBI将管理所有那些将导致EBA更改的IO,这将消耗一秒。所以,快速映射在大flash芯片,也就是进行一次全扫描需要较长时间的芯片上使用才会有意义。比如一个4G的NAND flash芯片进行一次全扫描需要好几秒,而一个快速的attach仅需要不到一秒的时间。

16.4 备注

启用Fastmap并不保证每个attach过程都会在最短时间内完成。在某些情况下,仍然需要进行完整扫描。这可能发生在两种情况下:

(i) 如果在写入Fastmap到闪存时发生意外重启;

(ii) 在写入Fastmap时UBI用尽了PEBs。

后一种情况可能发生在写入时发生大量I/O错误,并且UBI找不到足够可用的PEBs。

17 在UBI卷之上的只读块设备

UBI允许在卷之上创建块设备,但是有以下限制:

Ø 只读操作

Ø 连续的IO操作,请记住NAND驱动核心已经将所有的IO连续了。

尽管有这么多的限制,挂载一个只读的块设备仍然非常有作用。比如被压缩的文件系统,它就可以作为一个轻量级的只读根文件系统放在NAND设备上。UBI层将负责管理像bit-flips和损耗均衡这一类的事情。

用法

生成和销毁UBI卷之上的块设备和将MTD设备和UBI关联起来有些相似。既可以使用UBI模块参数block,也可以使用用户空间工具“ubiblock”.

为了在启动的时候就创建一个块设备,可以指定block参数为内核启动参数:

ubi.mtd=5 ubi.block=0,0 root=/dev/ubiblock0_0

如果指定了一个卷,有一些方式:

Ø 使用UBI卷路径

ubi.block=/dev/ubi0_0

Ø 使用UBI设备和卷名:

ubi.block=0,rootfs

Ø 使用UBI设备号和UBI卷号:

ubi.block=0,0

如果已经将UBI创建为一个模块,在模块加载时可以使用以下参数:

$ modprobe ubi mtd=/dev/mtd5 block=/dev/ubi0_0

使用用户空间工具ubiblock,块设备也可以在运行时被动态的创建和移除

$ ubiblock --create /dev/ubi0_0
$ ubiblock --remove /dev/ubi0_0

18 UBI压力测试

如果在配置时(在构建代码之前)启用,mtd-utils 包含可用于对 UBI 堆栈进行压力测试的用户空间工具。如果您想测试特定 UBI 堆栈实现的稳定性和正确性,这非常有用。

示例:运行各种 UBI 测试:

$ flash_erase /dev/mtd3 0 0
$ ubiattach --mtdn 3
$ /usr/libexec/mtd-utils/runubitests.sh /dev/ubi0

19 参考资料

UBI - Unsorted Block Images

ubi文件系统

Linux ubi子系统原理分析

UBI 常见问题解答和操作指南

UBIFS 常见问题解答和操作指南

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UBIFS是一种支持压缩的文件系统,可以在存储文件系统时进行压缩,从而减小文件系统的大小,提高系统的性能。UBIFS支持多种压缩算法,包括LZO、LZ4和ZLIB等。以下是在UBIFS中使用压缩文件系统的一些步骤: 1.在内核配置中开启UBIFS压缩选项。需要在内核配置中开启CONFIG_UBIFS_FS_LZO和CONFIG_UBIFS_FS_ZLIB选项,以支持LZO和ZLIB压缩算法。 2.在ubinize.conf文件中设置压缩选项。ubinize.conf是一个ubinize命令的配置文件,用于将多个文件或文件系统打包成一个UBI镜像文件。在ubinize.conf文件中,可以通过设置compress选项来指定压缩算法和压缩级别,如下所示: ``` [fs] mode=ubi image=my_image.ubi vol_id=0 vol_type=dynamic vol_name=rootfs vol_flags=autoresize filesystem=ubifs image_size=100MiB compress=lzo compress_level=6 ``` 在上面的示例中,使用LZO压缩算法,并设置压缩级别为6。 3.在生成UBI镜像文件时使用压缩选项。使用ubinize命令生成UBI镜像文件时,需要使用-c选项指定ubinize.conf配置文件,并使用-m选项指定压缩选项,如下所示: ``` ubinize -c ubinize.conf -m ``` 在上面的示例中,使用ubinize.conf文件中的压缩选项进行压缩。 需要注意的是,在使用UBIFS压缩文件系统时,需要根据实际需求和NAND Flash的容量来确定文件系统的大小和压缩算法,以确保系统的正常运行。同时,还需要对文件系统进行特殊的处理和配置,避免数据的丢失和损坏。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值