人们经常迷惑于UBI到底是什么,这就是我们创建这一节的理由。请记住:
1. UBI不是一个Flash Translation Layer,它和FTL没有任何关系。
2. 工作在bare flashes,它不能工作在MMC, eMMC, SD, mini-SD, micro-SD, USB flash设备上;UBI工作与raw flash devices上,大部分出现在嵌入式设备上,比如mobile phones等等
Overview
UBI全称"Unsorted Block Images"。它是工作于raw flash devices之上的volume管理系统,它管理一个单一physical flash设备上的多个logical volume,能够把I/O负载均匀的分发到flash chip上。
在一定意义上,UBI可以和Logical Volume Manager相比较。LVM映射logical sector到物理sectors,UBI映射logical eraseblcok到physical eraseblocks。但是除了映射,UBI还实现了wear-leveling和透明的I/O错误的管理。
一个UBI volume是一组连续的logical eraseblocks(LEBs)。每一个逻辑eraseblcok可以被映射为任意的physical eraseblock。这个映射是由UBI管理,并且对上层隐藏了global wear-leveling机制(记录per-physical eraseblock erase counters 以及透明的移动more worn-out数据到less worn-out上)。
UBI volume在创建时确定尺寸大小,也可以在日后改变(volume是动态re-sizable)。UBI有user-space工具可以用来管理UBI volumes。
有两种UBI volume,动态的和静态的。静态volumes是只读的,内容由CRC-32 checksums保护;而动态volume是read-write,上层负责确保数据的完整性。
UBI处理坏块,上层不需要关心坏块管理。UBI有一个保留的physical eraseblocks pool,当一个physical eraseblock变成坏块,UBI透明的用一个好physical block替换这个坏块。UBI把新出现的physical eraseblock的数据移动到好physical eraseblock。UBI volume对发生的事毫无察觉。
NAND flashes在读写操作时可能会发生bit-flips。Bit-flips通过ECC checksums纠正,但是积累到一定数据可能会发生数据丢失。UBI会移动发生bit-flips的数据到另外一个physical eraseblocks。这个过程叫scrubbing。scrubbing过程是后台的,并且对上层透明。
下面是UBI主要功能的一个短列表:
1. UBI提供动态生成,删除或re-sized的volume;
2. UBI实现全flash设备的wear-leveling(比如,你写的一个UBI volume的逻辑块,可能写入flash chip的任何physical eraseblocks);
3. UBI透明的处理所有physical eraseblocks;
4. UBI通过bit-flips使得数据丢失的可能性最小化。
下面是MTD分区和UBI volumes的比较,他们有一些相似之处:
1. 都是由eraseblocks组成的 - UBI volumes是逻辑eraseblocks,而MTD分区是物理删除块。
2. 都支持下面三个基本操作 - read, write, erase
1. UBI volumes没有eraseblock wear-leveling 限制,所以用户不需要考虑这个,上层软件实现更简单。
2. UBI volumes没有bad eraseblocks,这也使上层软件不需要做坏块管理,因此上层软件也更简单。
3. UBI volumes是动态的,可以创建,删除和动态resize,而MTD分区是静态的。
4. UBI 处理bit-flips,同样使得上层软件更简单。
5. UBI提供volume update操作which makes it easy to detect interrupted software updates and recover。
6. UBI 提供了原子逻辑块修改操作,这个操作保证在修改logical eraseblock的内容过程中,发生unclean reboots不会造成数据丢失; 这对于上层软件可能是非常有用的
7. UBI有一个un-map操作,un-maps一个逻辑eraseblock到physical eraseblock的映射。schedules the physical eraseblock for erasure and returns;这是非常快的并且使得上层软件避免实现他们自己的机制
有一个驱动叫做glubi是用来在MTD设备上模拟UBI volumes。这看起来很奇怪,因为UBI工作在MTD设备之上,而gluebi又在UBI上模拟另外一个MTD设备,
User-space tools
有下列 UBI 工具- ubiinfo - 提供系统内UBI devices和volumes的信息
- ubiattach - attacher MTD devices to UBI to UBI and creates corresponding UBI devices;
- ubidetach - detacher MTD device from UBI devices
- ubimkvol - creates UBI volumes on UBI devices;
- ubirmvol - removes UBI volumes from UBI devices;
- ubiupdatevol - updates UBI volumes; this tool used the UBI volume update feature which leaves the volume in corrupted state if the update was interrupted;
- ubicrc32 - calculates CRC-32 checksum of a file with the same initial seed as UBI would use;
- ubinize - generates UBI images;
- ubiformat - formats empty flash, erases flash and preserves erase counters, flash UBI images to MTD devices;
- mtdinfo - reports information about MTD devices found in the system
UBI headers
UBI在每一个非坏physical eraseblcok的起始位置,储存两个小的64-byte头:
- erase counter header(or EC header)包含physical eraseblock 的擦除计数和一些其它重要信息;
- volume identifier header(or VID header)储存volume ID 和l逻辑eraseblock(LEB)号,这两个信息可以确定这个PEB属于哪个volume,以及逻辑位置; VID还包含了其他的一些信息。
这就是为什么eraseblocks要小于physical eraseblock - 头占据了一部分flash空间
所有的UBI headers 使用CRC-32保护,参考drivers/mtd/ubi/ubi-media.h文件查看header的内容
当UBI attach到一个MTD device,首先扫描它,读所有的headers,检查CRC-32 checksums,存储erase counters和logical-to-physical eraseblock映射信息到RAM。
当UBI删除一个PEB,它写EC header,包含增加的erase count value。这意味着PEBs一直有EC header,除了从删除结束到EC header被写入的这个空档,如果在这时发生了unclean reboot,造成EC header丢失。在这种情况下,UBI写一个新的EC header,erase counter为MTD device扫描后的平均erase counter。
当UBI关联一个PEB和LEB时,VID头被写入PEB中。让我们考虑下面操作发生时,头部发生的变化。
- The LEB un-map操作:就是取消LEB和PEB之间的关联,PEB被erasure。当PEB被擦除,EC header被写入,但是VID header并没有被写入
- LEB map操作或者对un-mapped LEB的写操作:UBI首先找到一个 PEB然后写如VID header(EC header一定已经存在了)。注意,对于一个已经mapped LEB的写操作,直接把数据写入PEB而不会修改UBI headers。
UBI维护着两个per-PEB头因为它需要在不同的时间写不同信息到flash上:
- 在PEB删除后,EC头立刻写入PEB
- 当一个UBI关联一个PEB和LEB时,VID头写入PEB
当EC header写入PEB,UBI并不知道这个PEB要关联的volume ID和LEB number。这就是为什么UBI需要两个写操作,来写两个分离的headers
UBI volume table
volume table是on-flash数据结构,保存着这个 UBI设备上每一个volume的信息。volume table是一个volume table records的数组,每一个record包含以下信息:
- volume size;
- volume name;
- volume type;
- volume alignment;
- update maker;
- auto-resize flag;
- CRC - 32 checksum for this record.
每一个record描述一个UBI volume,记录在volume table array的index对应着这个记录的volume ID,比如UBI volume0被volume table的record0描述。volume table内的records数目受限于LEB尺寸,但是不能大于128。这就意味着最大的volume数目不能大于128
每当UBI volume被创建,删除,re-sized,re-named或者updated时,相应的volume table record被修改。UBI维护着两个volume table以维护掉电发生后的可用性。
Implementation details
内部的,volume table驻留在一个特定目标的 UBI volume中,叫做layout volume。这个volume包含两个LEBS - 每一个对应一个volume table的copy。layout volume是一个内部的 UBI volume,user不能看到也不能访问它。当读或者写layout volume时, UBI使用和正常user volumes相同的机制。UBI在updating volume table内容时,使用下列算法。
- 准备volume table在内存中的内容
- un-map layout volume的LEB0
- 写新的volume table到LEB0
- un-map layout volume的LEB1
- 写新的voume table到LEB1
- 刷新UBI工作队列,确保un-mapped LEBs对应的PEBs被删除掉。
当attaching MTD设备时,UBI确保两个volume table拷贝是相同的。unclean reboot可能会导致他们不相同,这是UBI选择LEB0的内容copy到LEB1中(因为LEB0是新的)。如果有一个volume table被损坏了,UBI会从另外一个volume table恢复过来
Minimum flash input/output unit
UBI使用flash的抽象模型。简而言之,从 UBI的角度看flash(MTD设备)包含着eraseblocks,这些eraseblocks可以是好的,也可以是坏的。没有一个好的eraseblock可以读,写或者删除。好的eraseblocks也可以被mark为bad。flash read和write是有最小I/O unit尺寸的,依赖于flash类型。
- NOR flashes通常有最小的I/O单位为1byte,因为NOR flashed通常允许读写单个字节
- 一些NOR flashes最小I/O unit可能为16或者32,比如带ECC的NOR flashes。
- NAND flashed通常有512,2048或者4096 bytes的最小I/O unit,对应着NAND page size。NAND flashed存储per-NAND page ECC到OOB area,意味着写整个NAND page以便计算ECC code,读整个NAND page以便校验ECC code
- VID header的物理位置依赖于min. I/O unit。LEB的尺寸也依赖于它;通常来说,I/O unit越大,LEB越小,UBI flash空间开销越大,因为EC header和VID header都占用一个I/O unit
- 所有对LEBs的写都应该和min. I/O unit对齐,是min. I/O unit的倍数;reads没有这个限制,但是要记住,MTD级的所有读都是min. I/O unit的倍数; 只是上层通过buffering 读书据屏蔽了这个特点。
NAND flash sub-pages
像之前所说的,所有的UBI I/O必须是min. I/O unit,也就是NAND flash的page size。然而一些SLC NAND flashes允许更小的I/O units,这在MTD术语称为sub-pages。不是所有的NAND都有sub-pages。
- MLC NANDs 没有sub-pages
- SLC NANDs通常有sub-pages。比如512-bytes NAND pages通常包含2x256-bytes的sub-pages,2048-byte NAND pages 包含4x256 bytes sub-pages。
- SLC OneNAND chips,2048 bytes NAND page size有4x512-bytes sub-pages。
如果NAND flash支持sub-pages,那么ECC codes的计算可以以sub-page为单位,而不是per-NAND。在这种情况下可以read和write sub-pages单独的。
很明显,尽管NAND chip支持sub-pages,NAND controller可能disallow。事实上,如果管理flash的controller计算ECC是per-NAND,那么就不可能进行sub-page级的I/O操作。
使用sub-pages可以减少flash 空间开销,比如对于128KiB eraseblock,page size是2048-bytes。如果没有sub-pages,EC占据第一个2048,VID herder占据第二个2048,因此LEB size变成124KiB。反之支持sub-pages,UBI把EC 和VID分别放在第一和第二个512字节,所以LEB变成了126KiB。
Sub-pages仅仅在UBI内部使用,用来存放EC VID headers,UBI API不允许用户使用sub-page I/O unit。这是因为sub-page writes可能很慢,写一个sub-page,驱动可能会写整个NAND page,但是会把和本次操作无关的sub-pages都写入0xff。这也就是说写四个sub-pages可能比写整个NAND pages慢四倍。因此,UBI仅对headers使用sub-pages,但是在UBI API中不存在这个概念。
UBI header position
EC header存放在PEB的偏移0处,占据64bytes;VID header位于下一个min. I/O unit或者sub-page处,占据64 bytes。例如:
- 对于NOR flash,1 byte min. I/O unit,VID header位于PEB 64处
- 对于没有sub-pages的NAND flash,VID header位于第二个NAND page
- 对于有sub-pages的NAND flash,VID header位于第二个sub-page
Flash space overhead
UBI使用一定数量的flash space来保存管理信息,因此减少了flash device可供UBI用户使用的空间,这些开销包括:
- 2PEB用来存储volume table
- 1 PEB保留用来wear-leveling purpose
- 1 PEB 保留用来atomic LEB change操作
- 一定数据的PEBs被用来PEB handling;这适用NAND flash,但是不适用NOR flash;保留的PEBs百分比是可配置的,缺省是1%
Saving erase counters
当使用 UBI时,储存erase counters 到flash media上很重要。也就是说,每个phusical eraseblock有一个叫做erase counter header用来存储这个physical eraseblock被删除的次数。当然,保持这个erase counter不丢失也同样重要,这意味这你删除flash和写 ubi image的工具要考虑erase counter。mtd-utils包含着ubuformat工具可以正确执行这些操作How UBI flasher should work
以下是 UBI flasher程序在删除flash或者flashing UBI images时要做的事:- 首先,扫描整个flash来收集erase counter。也就是它读每一个PEB的EC header,检查每一个header的CRC32 checksum,保留erase counter到ram中。不需要读取VID headers。忽略掉bad PEBS。
- 计算平局erase counter。当PEBs的EC header被损坏或丢失时,用平均erase counter。这样的PEBs可能是由于unclean reboots引起的,当然数量不会太多。
- 如果目标是删除flash,那么每个PEB被删除,正确的EC header被写到PEB中。EC header中应该包含增增加的erase counter。bad PEBs应该被忽略。对于NAND flashes, 当erasing或者writing时发生I/O错误时,PEB被标记为bad
- 如果目的是烧写UBI image,那么烧写工具应该对每一个PEB做如下工作。
- 从UBI image中读取这个PEB的内容到一个buffer中,buffer必须是min. I/O unit倍数
- 把buffer中未填满的部分都写入0xff
- 删除PEB
- 改变buffer中的EC header
- 写这个buffer到physical eraseblock
- 从UBI image中读取这个PEB的内容到一个buffer中,buffer必须是min. I/O unit倍数
在实践中,UBI image通常都小于UBI volume,因此flasher要正确的烧写PEBs,正确的处理删除的PEBs
注意在写UBI image时,UBI image写到哪个eraseblocks是无所谓的,比如,image的第一个eraseblock可以写入第一个PEB,或者第二个,或者对后一个。
注意,如果你实现一个产线UBI images烧写工具。那么flasher不需要改变EC headers,因为这是新的flash,所以每一个PEB的erase counter应该为0。这意味着产线flasher更简单。
如果你的UBI image 包含UBIFS file system,并且你的flash是NAND, you may have to drop 0xff bytes the end of input PEB data. this is very importtant, although not required for all NAND flashes. Sometimes a failure to do this may result in very unpleasant problems which might be diffcult to debug later. So we recommend to always do this.
原因是UBIFS对待仅包含oxff byte的NAND pages为free。例如,假定PEB的第一个NAND page有一些数据,第二个是empty,第三个有一些数据,第四个和其他的都是空的,在这种情况下,UBIFS认为从第四个 page开始的NANDpages是空闲的,并且会向这些page写数据。然而,如果flasher程序已经向这些pages写了0xff,结果导致他们被写了两次!然而,一些NAND flashes要求他们的NAND pages仅被写一次,即便这些数据包含的都是0xff bytes。
flasher不得不做的是drop掉所有到PEB结尾的empty NAND pages。没必要drop掉所有的空NAND pages,仅需要最后一个。这就意味着flasher不需要扫描整个buffer,查找0xff。只需从buffer末尾开始查找知道第一个non-0xff byte,这是非常快的。
另外一个替代方法,是在生成UBIFS文件系统时使用mkfs.ubifs增加free space fixup选项。这将允许你的flasher不需要担心PEBs末尾的0xff。这在你使用一个产线烧写程序写一个UBI image时非常有用。
Marking eraseblocks as bad
这一节是针对那些允许出现坏块的NAND flashes和其他的flashes。UBI在两种强况下mark physical eraseblocks为坏块:
1. eraseblock写操作失败,在这种情况下,UBI把这个PEB的数据移到其他的PEB(data recovery),然后调用对这个PEB的诊断
2. 删除操作出现EIO error,在这种情况下,直接把PEB标记为坏块。
诊断是在后台处理的,目的是检测这个physical eraseblock是否真的坏了。写失败可能有多种原因,包括驱动的bugs或者上层file system 的bug(比如,文件系统多次写了同一个NAND page)。整个诊断过程包括如下步骤:
- 删除eraseblock;
- 读取eraseblock,确保仅包含0xff bytes;
- 写测试模式bytes;
- 读取eraseblock,检查模式串;
- 重复几个其他的模式(0xA5, 0x5A, 0x00)
如果eraseblock通过了torture test,那么不会被标记为坏块。注意,在诊断测试过程中,bit-flip发生是把eraseblock mark为坏块的很好理由,请参考torture_prb函数。
Scalability issues
不幸的是, UBI扩展性是和flash size线性相关的。 UBI初始化时间线性依赖flash 的PEB数量。这意味着flash越大, UBI初始化的时间也越大。这个初始化时间也依赖flash的I/O速度同时轻微的依赖CPU速度,因为。- UBI在attach时会扫描MTD设备 - 它读取erase EC和VID headers从每个单个PEB;headers 很小,所以这意味对于NOR flash需要读取128 bytes per-PEB,而对NAND flash需要读取1~2NAND pages per-PEB;这当然远小于JFFS2 mount MTD设备所需的读写数据量,所有UBI attaches MTD设备要远快于JFFS2在MTD设备上 mount一个文件系统。
- UBI 为每一个EC和VID headers 计算CRC-32 checksum,这将消耗CPU,尽管这个flash I/O负载比较非常小。
下面是一些测试结果:
- Nokia N800上一个256MiB OneNAND flash需要1 sec时间attached;flash不支持sub-pages,所以UBI在扫描时不得不在每个PEB读取2KiB NAND pages
- OLPX XO-1设备的1GiB NAND flash内存需要2 seconfs时间attached;flash 是SLC NAND支持sub-pages,但是controller不支持sub-page writes,所以UBI不得不读取每个PEB的前两个2KiB NAND pages。
Implementation details
一般来说,UBI需要三个表:
- volume table:包含per-volume信息,比如volume 尺寸,类型等;
- eraseblock association(EBA) table:包含logical-tophysical eraseblock映射信息;比如,当读一个LEB时,UBI首先查找对应的PEB number,然后读取这个PEB;
- erase counter EC table:包含着每一个物理删除块的erase counter;UBI 磨损平衡子系统使用这个表来发现PEB的 erase counter。
volume table维护在flash上。它仅仅在UBI volumes被创建,删除和re-sized时改变。这种操作很少且对速度没有要求,UBI可以负担一个慢的简单的方法来管理volume table。
EBA和EC表在每次LEB被映射或者PEB被删除是都会改变,因此需要这两个表的管理方法要快而且高效。
UBI可以在flash上,维护EBA和EC tables。这无法避免的引入了journaling, journal replay, journal commit等等。换句话说,这将引入许多复杂性。但是这可以使得EBA和EC在算法上是可扩展的。
UBI的一个需求是on-flash上格式的简化,因为UBI作者不得不在boot-loader中读取UBI volumes,而在boot-loader的代码中有很严格的限制,基本上很难在boot-loader代码中加入复杂的journal scanning和replay code。
所以UBI没有在flash media上维护EBA和EC tables。而是在每次attach MTD设备时构造它。这意味着UBI不得不扫描整个flash,从每个PEB读取EC和VID header以便构造in-RAM EC和EBA tables。
这个设计的缺点是很差的可扩展性以及NAND flashes空间的负载(大约1.5~3%的flash空间,2KiB NAND page,128KiB eraseblock),优点是简单以及可靠性。
当然,可以创建UBI2,维护这些表在分离的flash area。UBI2将不和UBI兼容,因为他们有完全不同的on-flash格式,但是用户接口是完全一样的,这将保证UBI上层软件的兼容性。
Volume auto-resize
众所周知NAND chips有一定书目的physical eraseblocks被厂商mark为坏块。坏块随机的分布并且数据是不同的,尽管厂商通常保证前几个physical eraseblocks不是坏的并且坏块的总数不会超过一定的书目。比如,一个新的256MiB的oneNAND chips保证不超过40 128KiB PEBs(当然,在使用过程中,这个数量会增加)。这大概是2%的flash size当需要创建一个UBI image用来在产线上刷如终端用户设备,你应该定义所有volume的确切尺寸。但是又有total flash size是依赖于初始坏块总数的,所以很难这么做。
解决这个问题的一个明显方法是假定最坏的情况,当所有的chips都有一个bad PEBs的最大值。但是实际中,大部分chips仅仅有几个bad PEBs,远小于这个最大值。一般来说,这没什么问题,这将增加可靠性,因为UBI一直使用设备上的所有PEBs。另一方面UBI无论如何要保留一定数目的physical eraseblocks用做坏块处理,缺省情况下是1%。所以上面提到的OneNAND chip将有%1 PEB保留给UBI,0~2%PEB不可以被使用。
但是有另外一个替代办法 - 一个volume可以有auto-resize mark,这意味着当UBI第一次运行时它的尺寸可以enlarded。在volume size调整后,UBI移除auto-resize标志后这个volume不能再re-sized。auto-resize flag存储在volume table仅仅一个volume可以被mark为auto-resize。
UBI operations
LEB un-map
LEB un-map操作的实现是ubi_leb_unmap() kernel API。从2.6.29开始un-map操作通过UBI_IOCEBUNMAP ioctl命令暴露给user-space程序。这个ioctl应该被UBI volume character devices调用
LEB un-map操作:
- 首先un-maps LEB和相应的PEB
- 然后调度这个PEB的擦除并返回;un-map不会等待擦除的结束;PEB有UBI后台线程负责擦除
当读取一个un-mapped LEB时,UBI返回全0xff bytes,所以un-map操作可以被认为是一个非常快的erase operation。但是有一点需要注意。
假定你un-map LEB numL从PEB numP。因为P不是同步删除的,而是仅仅调度了删除操作,这可能在unclean reboots时给个”惊喜“;如果在P物理删除前恰好发生了reboot。在UBI再次attaches到MTD设备上时,会发现L仍然mapped到UBI。事实上,UBI将扫描整个MTD设备发现哪个P对应L,然后把这个映射加到EBA table。
但是一旦你写数据到numL,或者使用LEB map操作,numL映射一个新的PEB,老数据就变成了历史,因为即便此时发生了unclean reboot,UBI仍然会选择numL的最新映射。
Implementation details
这一节描述在发生unclean reboot后,UBI如何区分一个LEB的older和newer的PEB,假定我们un-map LEB L到PEB P1的映射,此时UBI调度了P1的erasure。然后我们向L写数据,意味着UBI找到另外一个PEB P2,映射L到P2,然后写数据到P2,如果在P1被物理删除前,发生unclean reboot,我们得到了2个PEBs(P1, P2)对应同一个LEB L
为了处理这种情况,UBI维护一个全局64-bit sequence number variable。这个可变序列号每次映射一个LEB时都会增加并且存储到PEB的VID header中,所以每一个VID header有一个唯一的序列号,序列号越大,VID越年轻。当UBI attach到MTD设备时,初始化全局sequence number为VID headers中的最大值加1
在上面的情况,很显然UBI会选择higher sequence number (P2) 然后抛弃lower sequence number(P1)
注意,如果unclean reboot发生在,磨损平衡而执行的移动一个PEB数据到另外一个PEB的过程中,或者atomic LEB change操作时发生。在这种情况下不能简单的选择新的PEB,必须确保新的数据是否已经写到new PEB。
LEB map
LEB map操作映射一个从前un-mapped 逻辑eraseblock到一个物理eraseblock。比如,如果要为LEB A映射,UBI首先发现相应的PEB,然后把VID header写入PEB,然后修正in-memory EBA table。VID header将指向LEB A,这个操作完成后,所有到LEB A的操作最终都会到mapped PEB
LEB映射操作是通过ubi_leb_map() UBI kernel API函数,或者通过UBI_IOCEBMAP ioctl
LEB map操作接受dtype类型参数,这个参数建议LEB将保存的数据类型,dtype有如下类型:
- UBI_SHORTTERM - LEB存储的是短期数据,也就是这个数据很快就会被擦除;UBI将映射LEB到一个low erase counter的PEB
- UBI_SHORTTERM - LEB存储的是长期数据,UBI映射这个LEB到high erase counter的PEB
- UBI_UNKNOWN - 在大多数情况下使用,当你无法确定是否为长期或者短期
记住dtype仅仅是一个hint。请使用UBI_UNKNOWN如果无法确定。此外UBI作者从来没有测试过UBI_SHORTERM和UBI_LONGTERM,所以不保证他们有没有效果。