UBI 介绍 二

UBI-Unsorted Block Imagine

http://www.linux-mtd.infradead.org/doc/ubi.html

1、注意

很多人搞不清楚UBI到底是什么,这就是写这篇文章的原因。请认识到以下几点:

l  UBI不是一个flash转换层(FTL),并且和FTL没有任何关系。

l  UBI只支持纯粹的flash,消费flash像MMC、RS-MMC、Emmc、SD、mini-SD、CompactFlash、MemoryStick、USB flash drive等设备,UBI都是不支持的。请不要混淆这些。以下网址会介绍raw flash和FTL设备的不同。http://www.linux-mtd.infradead.org/doc/ubifs.html#L_raw_vs_ftl

2、简介

UBI(拉丁语的意思:哪?)代表UnsortedBlock Images(未分类块镜像)。对flash设备来说它是一个卷管理系统,它管理着多个逻辑卷,而这些逻辑卷是基于一个单独的flash芯片的(逻辑块和物理块请参看),并且它可以将IO加载扩展至整块芯片(比如,损耗平衡)。

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

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

UBI卷大小是在卷生成时指定的,并且是可以更改的。以下这个工具可以用来更改UBI卷的大小。

http://www.linux-mtd.infradead.org/doc/ubi.html#L_usptools

UBI的卷有两种类型:动态卷和静态卷。静态卷是只读的,并且它的内容是被CRC-32的校对总数保护的,而动态卷是可读可写的,并且上层(比如一个文件系统)负责确保数据的完整性。

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

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

以下为UBI的主要特征:

l  UBI提供可动态生成、移除或者重设大小的卷(volume)。

l  UBI对整块flash设备实现损耗均衡功能(比如,用户可以对同一块LEB进行连续的写入擦除操作,但是UBI会将这些写入擦除操作扩展到整块flash芯片的所有PEB上)。

l  UBI显式处理坏PEB。

l  UBI通过强力擦除(scrubbing)方式将数据丢失降到最小。

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

l  两个都包含的擦除块。UBI卷包含LEB,而MTD分区包含PEB。

l  都支持3种基本操作:读、写、擦除。

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

l  UBI卷没有擦除块损耗均衡限制,这样使用者就可以完全不用考虑这点,也就是说上层软件代码可以更简单。

l  UBI卷没有坏的擦除块,这个也会让上层代码更简洁。

l  UBI卷可以是动态的,这也就意味着可以对它进行动态的创建、删除、改变大小,然而MTD分区则是静态的。

l  UBI可以处理bit-flips,这同样可以使上层代码更简洁。

l  UBI提供卷更新操作(后面将会讲到),这可以使它更容易检测到软件更新中断并从中恢复。

l  UBI提供原子LEB改变操作(后面将会讲到),这可以允许去改变LEB中的内容而不需要释放掉在操作过程中由于异常重启而产生的数据。这对上层软件可能非常重要(比如对一个文件系统)。

l  UBI有取消映射(un-map)操作,它将LEB和PEB之间的映射关系取消,为了PEB的擦除和返回进行调度。这个非常的快,并且可以使上层软件不用去实现延迟擦除机制(例如,JFFS2就必须实现这个机制)。

UBI也提供一个块设备,这样一个常规的、块定向的文件系统就可以挂载在UBI卷之上了。由于UBI显式处理坏块,所以这是可能的。

有一个叫做gluebi的驱动,它可以在UBI卷之上模仿MTD设备。这看起来很怪,因为UBI是工作在MTD设备之上的,但是这确实可以实现,例如在UBI之上跑JFFS2。不管怎样,新的软件要尽量从UBI的先进的特征中获益并且让它去解决flash技术不可能解决的问题,这样才是有意义的。

3、源码

UBI是从2.6.22kernel版本开始被加入的。但是我们建议去使用最新发布的UBI,因为在UBI发布之后,我们又修改了很多bug,做了很多改进,并且增加了很多功能。从git上获取UBI的修改日志:

http://git.infradead.org/ubi-2.6.git

在git tree上有两个分支:master和linux-next。Master分支包含了最新的对不完整部分的更改,大部分其实并没有经过充分的测试,这部分会随时更新,所以UBI的开发者最好不要使用这个分支。Linux-next分支包含的是那些即将被合并到linux kernel里面的更改,所以UBI的开发者应该使用这个分支上的代码。

4、邮件列表

欢迎向我们发送使用反馈、bug报告、补丁等,地址

http://www.linux-mtd.infradead.org/mail.html

5、用户空间工具

和其它MTD的用户空间工具一样,UBI的用户空间工具也可以从以下的git仓库获取:

这个地址提供了关于怎样制作整个mtd-utils的建议。

http://www.linux-mtd.infradead.org/faq/general.html#L_compile_mtd

这个仓库包含了以下这些UBI工具:

Ubiinfo ubiattach ubidetach ubimkvolubirmwol ubiblock ubiupdatevol ubicrc32 ubinize ubiformat mtdinfo

6、UBI头

UBI将在每个非坏的PEB的开始存储两个64bytes的头。

擦除计数头(erase counterheader EC header)包含了PEB的擦除计数和一些其它的并不重要的信息。

卷标识头(volumeidentifier header VID header)存储了卷ID和PEB归属于LEB的数目(这个不太理解),另外还有一些其它相对不太重要的数据。

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

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

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

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

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

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

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

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

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

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

7、UBI 卷表

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

Ø  Volume size    卷大小

Ø  Volume name  卷名

Ø  Volume type   卷类别

Ø  Volume alignment  卷对齐

Ø  Update marker   更新标签(为那些有中断更新的卷而设置的)

Ø  Auto-resize flag   自动更改大小标准

Ø  CRC-32 checksum for this record  

每一个记录对应描述一个UBI 卷,并且记录自己在卷表数组中对应VID索引。比如,UBI 卷0对应着卷表数组中的成员0,以此类推。该数组的成员数量是受LEB大小限制的,并且不能超过128.这意味着UBI设备不能拥有超过128个卷。

每次当UBI卷进行创建、移除、更改大小、重命名或者更新的时候,相应的卷表记录都将会改变。为了可靠性和异常断电等原因,UBI维护着两份UBI卷表。

7.1实现细节

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

UBI使用以下的算法来更新卷表的记录。

Ø  在内存中先准备好新卷表的内容

Ø  取消卷层(layoutvolume)中LEB0的映射

Ø  将新的卷表写入到LEB0

Ø  取消卷层(layoutvolume)中LEB1的映射

Ø  将新的卷表写入到LEB1

Ø  清除UBI工作队列以确保对应于取消映射的LEB的PEB被擦除了

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

8、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层的所有的读操作都是以最小单元来执行的。它是将数据读上来,然后放在一个缓存中,再把用户所需求的字节数拷给用户。

9、NAND flash子页

正如这里所说,所有的UBI IO操作都必须以最小IO单元来进行,如果是NANDflash,它就等于flash的页大小。然而,一些SLC NAND flash允许更小的IO单元,被称作子页(sub-page)。并不是所有的NAND flash都有子页。

(这节内容,对UBI的影响不是很大,暂不作翻译)

10、UBI头位置

EC头的位置总是在PEB的开头(offset 0)并占据64个字节,VID头存在于下一个可获得的最小IO单元或者子页,并且也占据64个字节大小。例如:

Ø  NOR flash最小单元为1字节,那么VID头就在64字节偏移处

Ø  没有子页的NANDflash,VID头存在于第二个NAND页的开头

Ø  有子页的NANDflash,VID头存在于第二个子页头

11、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)

12、保存擦除计数

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

13、UBI flasher是怎样工作的

以下这个列表是UBI flasher程序在擦除flash或者在写入UBI镜像时必须要做的工作:

Ø  首先,扫描flash并且收集擦除计数。也就是说,它将会从PEB中读取EC头,校验CRC-32的checksum,并且把擦除计数存储在RAM中。没有必要去读取VID头,坏的PEB将会被跳过。

Ø  计算擦除计数的平均值。它将用于PEB崩溃时EC头的丢失。

Ø  如果是为了擦除flash,那么PEB将会被擦除,相应的EC头也会被写入到PEB的开头。这时的擦除计数将加一。

Ø  如果是写入UBI镜像的话,flasher将对每个非坏的PEB进行以下操作:

²  将UBI镜像读入到一个buffer中

²  用0xff将最小IO单元补齐

²  擦除PEB

²  更改内存中的EC头—将擦除计数加一,重新计算CRC-32的checksum

²  将buffer写入到PEB

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

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

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

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

因为UBI文件系统是将NANDflash中那些只含有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 dataare stored in a buffer.

 * @ubi: UBI device description object

 * @buf: a buffer with the contents of thephysical eraseblock

 * @length: the buffer length

 *

 * This function calculates how much "realdata" is stored in @buf and returns

 * the length. Continuous 0xFF bytes at the endof the buffer are not

 * considered as "real data".

 */

intubi_calc_data_len(const struct ubi_device *ubi, const void *buf,

                      int length)

{

        int i;

        for (i = length - 1; i >= 0; i--)

                if (((const uint8_t *)buf)[i]!= 0xFF)

                        break;

        /* The resulting length must be alignedto 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的大小,然后再写入到我们的NANDflash。Flasher就不会被跳过空页而烦恼,当JFFS被挂载时,写NAND页时也不会失败,但是后来我们发现了怪异的ECC错误。花了相当长的时间才找出这个错误,换句话说,这也是和JFFS2镜像有关的。

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

http://www.linux-mtd.infradead.org/faq/ubifs.html#L_free_space_fixup

14、标记擦除块为坏块

UBI在以下两种情况下会将PEB标记为坏块:

Ø  擦除块的写操作失败,在这种情况下UBI将该PEB中的数据拷贝到其它PEB中(数据恢复),以后将会调用该坏PEB。

Ø  有EIO错误的擦除操作失败,在这种情况下,擦除块将会立马被标注为坏块。

调用坏PEB的目的是为了检测该PEB是否真的坏掉。写入失败的原因很多,包括驱动中的bug或者上层比如文件系统的错误(比如,文件系统错误的向同一个NAND页写入很多次)。调用该PEB时,UBI完成以下工作:

Ø  擦除该擦除块

Ø  然后读出其内容,确保里面所有的数据都为OxFF

Ø  写入测试模式字节

Ø  读出里面的数据,并和测试模式的字节进行比较

Ø  这样测试多个测试模式(0Xa5,0x5a,0x00)

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

15、扩展性问题

不幸的是,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的开销,但好处是简单的二进制格式和稳健性。

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

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

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

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

17、卷自动调整大小

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

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

18、UBI操作

取消LEB映射(LEB un-map)

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

LEB的取消映射操作如下:

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

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

UBI将返回全0xFF当它去读取一个取消映射的LEB,所以取消映射操作,可以被认为是一个非常快速的擦除操作。但是有个方面,UBI编码者必须要注意。

假设LEB L被映射到PEB P,现在取消这个映射,既然P是被线程后台擦除的,并不是被同步擦除的,这样就可能在异常重启时就会出现“惊喜”:假设异常重启发生在P被物理擦除之前,重启之后当UBI关联MTD设备时,L又被映射到了P。的确会这样,UBI将扫描MTD设备,并且会发现P关联着L,这样它就会把这个映射信息重新加入到EBA表。

但是一旦你向L写数据,或者使用LEB映射操作对L进行映射,它将会被映射到一个新的PEB,旧的数据将会一直存在,因为即使在异常重启的情况下,UBI也将会为L选择一个新的映射。

实现细节

这一节描述UBI是怎样在异常重启的情况下区分老的和新的LEB。假设我们取消LEB L和PEB 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的数据。

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进行un-mapped操作时,它所对应的PEB并不是立马被擦除的。并且此时发生了异常重启,当UBI关联MTD设备时,这个LEB将会再一次被映射到这个PEB。所以,当你对一个LEB进行了取消映射操作,然后又对其进行映射操作,将确保老的LEB内容将不会被获取到,换句话说,你这时只能获取到0xFF,即使发生了异常重启。

请谨慎使用LEB映射操作,如非必须请不要使用,因为相对于LEB的取消映射操作,映射操作将在UBI的损耗平衡系统上增加更多的开销。的确是这样,当LEB取消映射后,将不会有PEB再去保存它的数据,这样损耗均衡子系统也不用去搬运数据来维持损耗均衡了。相反,如果将一个LEB映射到PEB,那么损耗均衡子系统就要去多维护一个PEB。

卷更新

对于设备软件更新,卷更新是非常有用的。该项操作将用新的内容改变所有的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才会被清除。

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,所以更新卷将会比原子更改所有的擦除块要快。这一额外的开销要被记住,也就是说如非必要,不要使用该操作。

实现细节

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

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

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

19、快速映射(Fastmap)

快速映射是一个试验的可选的UBI特征,可以将CONFIG_MTD_UBI_FASTMAP设置为y来使能该功能。一旦被使能,UBI将评估模块参数”fm_autoconvert”。如果它被设置为1(默认为0),UBI将为每一个被关联镜像(attachedimage)自动使能快速映射。这意味着UBI用快速映射的数据创建了一个新的内部卷,以便下次快速关联模式可以使用这些数据。

在默认配置下,UBI将会使用存储在快速映射卷里面的信息,以此来加速关联(attach)过程。如果想测试快速映射,设置fm_autoconver为1并将其关联(attach)到一个卷。

以下是可进行的配置:

CONFIG_MTD_UBI_FASTMAP

Fm_autoconvert

result

n

0

快速映射完全被屏蔽

y

0

如果镜像上存在一个快速映射,它将附在(attach)UBI上,但是如果没有快速映射,也就不会有快速映射会被安装在镜像上。

y

1

如果镜像上存在一个快速映射,它将附在(attach)UBI上,快速映射会自动的被安装在所有依附(attach)的镜像上。

向后兼容

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

技术实现

一个磁盘存储的快速映射包含所有的需要附加到整个镜像的信息,也就是说所有的擦除计数值,所有的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的时候不写一个新的快速映射,所有的擦除计数将发生修改当上一个快速映射写入已丢失。

开销

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

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

20、在UBI卷之上的只读块设备

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

Ø  只读操作

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

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

用法

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

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

ubi.mtd=5 ubi.block=0,0root=/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

21、更多的文档

Ubidesign.pdf 虽有些过时但仍十分有用:

http://www.linux-mtd.infradead.org/doc/ubidesign/ubidesign.pdf

FAQ网页

http://www.linux-mtd.infradead.org/faq/ubi.html

用户空间接口定义:

include/mtd/ubi-user.h 

内核API定义和实现:

include/linux/mtd/ubi.h 

drivers/mtd/ubi/kapi.c

猜你喜欢

转载自blog.csdn.net/yexiangcsdn/article/details/81133527
今日推荐