第1章 UBIFS
UBIFS不是工作在块在设备之上,所以UBIFS不能用于MMC之类的设备。
与传统的flash文件不同,UBIFS不是工作是块设备之上。传统的flash文件系统(如Jffs2)工作在MTD设备层之上;而UBIFS工作在UBI卷层之上,UBI卷层工作在MTD设备层之上。
1.1 UBIFS特征:
可预测性:UBIFS的挂载时间、内存消耗、I/O通信时间都是不依赖于flash的大小。所以在上百GiB的flash上,UBIFS工作表现较佳。如果UBIFS的工作效能发生瓶颈,那问题是出在UBI卷层上。
快速挂载:不像jffs2在挂载时会扫描储存介质,UBIFS在几毫秒的时间内完成挂载,而不受flash大小的影响。然而UBI的初始化与储存介质的大小有关。这将影响UBI的初始化的时间。
回写:
容忍不清洁的重启:UBIFS是日志文件系统,所以容忍突然关闭和不清洁的重启。UBIFS会回放日志并从不切底的重启中恢复过来。这时挂载时间会有点慢。但由于只是需要重做日志,而不需要扫描储存介质,所以性能影响不大。
快速I/O:即使回写功能被禁能(例如在挂载时加入“-o sync”的参数),UBIFS也表现良好,接近jffs2。在同步I/O方面UBIFS是极难与jffs2相抗争的。因为jffs2在flash维护数据索引结构,不需要额外的开销,而UBIFS需要。然而UBIFS依然是很快,因为它依靠日志。它不需要把数据物理地从一个地方移动到另一个地方,仅把有关信息加入文件系统的索引以及为新日志挑选一个擦除块。
快速压缩:与jffs2相似,UBIFS支持数据压缩储存。对于单个文件的储存,UBIFS可以使能/禁能压缩功能。
自恢复功能:有数据索引有损坏的情况下,UBIFS可以恢复过来。在UBIFS每一块信息有描述整个信息的数据头。在扫描介质的情况下这数据头信息可以重建。而在FAT文件系统,发生这样的事件是致命的。
数据的完整性:UBIFS在每次写入数据时都会对数据进行核实,以确保数据的完整性。UBIFS不会容忍任何不可用的数据或元数据。然而为了加快数据的读写速度,用户可以关闭CRC检查。
1.2 可预测性
所有的UBIFS的数据都是用树来的组织的,所以定位数据的时间与flash大小的对数成正比。而UBI则是线性。目前UBI在2-16GIB大小的flash里工作表现良好。
1.3 UBIFS write-buffer
UBIFS是异步文件系统。正如其它Linux文件系统,它使用页cache。页cache是由Linux内存管理单元负责。页cache很大并可以缓存很多数据。当我们把一个文件写入文件系统时,事实上我们的数据是先写入了页cache,标记页为“脏”,然后写函数返回,过一段时间后数据正式写入储存介质。
write-buffer是UBIFS自己的缓冲区,工作于页cache和flash之间。这表示回写并不是写入flash而是写入了write-buffer。
write-buffer是为了改善nand flash性能。write-buffer的大小通常是nand flash的页大小。write-buffer的存在可以把一些零散的数据合并起来整页整页地写入到nand flash中。这不但减少了低速的与flash通信的次数,而且减少了flash中的碎片。
当然在写入大量数据时,是不需要用到write-buffer的。只在最后一次写入数据不够一个nand flash页时,最后的数据缓存起来。
当然实现同步的方法有:
sync()、fsync(fd)、open时使用O_SYNC、在挂载UBIFS时加上“-o sync”选项。
1.4 压缩
UBIFS把数据写入到储存介质之前会先压缩数据;在从储存介质读入数据之后会解压数据。UBIFS只会压缩文件中的数据。目录、设备节点之类不会被压缩。元数据和索引信息不会被压缩。
目前UBIFS仅支持LZO和ZLIB的压缩。zlib提供较高的压缩比,但LZO在压缩和解压方面速度比较快。LZO是UBIFS和mkfs.ubifs的默认压缩器。当然你可以禁用USBFI压缩,通过使用“-x none”mkfs.ubifs选项。
UBIFS是把数据分成4KiB大小的数据块然后独立压缩。然而这不是最优的方案。因为如果数据是已经压缩好了的(如mp3)就不起来作用了。
1.5 数据完整性
每一块UBIFS的数据信息写入到介质时,都会计算CRC-32,并且每当读取数据块时也会作CRC检查。
计算CRC-32会占用CPU资源,同时会使用UBIFS效率变慢。这是为数据安全而付出的代价。然而UBIFS可以允许用户在挂载时关闭CRC检查。如果UBIFS在挂载时添加了这项选项,UBIFS不会检查数据块的CRC-32。但仍要检查内部索引信息的CRC。还有这个选项只会影响读操作,而不影响写操作。因为UBIFS在写操作时总是进行CRC-32计算。
在使用MLC芯片时,如果是关闭了CRC-32要特别小心。
目前写操作还不支持CRC禁用。因为在不切底的重启中恢复过来是需要这些信息。UBIFI有能力通过这些信息检查到损坏或写到一半的节点。这些会被丢弃的。
1.6 预读取
预读是一种优化的文件读取技术。在读取文件时会比用户实际要求的多读一点数据。用户在读取文件时总是从头到尾连续地读取数据,所以文件系统尽量使用户在读取下一次的数据出现在内存中。
对于传统的文件系统,这会提高性能。但在UBIFS表现不好。UBIFS使用UBI API工作,UBI使用MTD API工作,这是同步的。MTD API不没有提供任务列队。
VFS的预读功能会降低UBIFS的性能。所以UBIFS 关闭了这项功能。而UBIFS是有自己的预取功能,称为“bulk-read”。用户可以在挂载时使用“bulk_read”选项来开启这项功能。
第2章 UBI 2.1 总概
UBI不是flash转换层(FTL),而且这与FTL无关。UBI工作的对象是原始的flash。所以它不能工作在MMC、RS-MMC、eMMC、mini-SD、micro-SD、ComoryStick、USB flash设备,等等。
UBI在一片flash上管理多个逻辑卷。
2.2 UBI headers
在每一个非坏的物理块的里,UBI都储存2个64字节的头。
l 擦除计数头(erase counter header 或 EC header):标记了该物理块被擦除了多少次。
l 卷ID头:储存卷ID以及逻辑擦除块号
这就是为什么逻辑擦除块总是比物理擦除块小的原因——这些数据头的开销。
所有的UBI头都是由CRC-32保护。
当UBI与MTD通信时,会先扫描flash,读取所有头信息,检查CRC-32,然后在RAM储存这些信息。
在UBI擦除了一个物理块后,会把擦除计数值加一,然后写入该块。这表明物理擦除块总是有EC header,除非在擦除了物理擦除块到写入EC header之前这暂短的时间。当然这段时间也有可能发生系统重启的情况,这时EC header丢失或不可用。在种情况,UBI会写入一个新的EC header,擦除计数值为所有块的平均值。
当UBI处理逻辑块时,就会写入VID header。
UBI在每个物理块都保持两个信息头是因为是不同的时间需要写入不同的内容:
l 在一个物理块擦除后,EC header必须在最快的速度写入,以期把这段时间内出现意外重启的可能性减到最小。
l 当UBI访问这个物理擦除块时,VID header就会写入。
当EC header写入一个物理擦除块后,UBI还不知道这个物理擦除块的卷ID和逻辑块号。这就是为什么UBI需要把信息分成两个头分两次写入。
2.3 UBI volume table
卷表是储存在flash的包涵该UBI设备中各个卷信息的数据表。这个卷表是由各一个卷表记录组成的数组。每个记录包涵下面的信息:
l 卷大小
l 卷名字
l 卷类型
l 卷 alignment(不知该怎样翻译)
l 更新标记
l 自重整大小旗标
l 该记录的CRC
每一个记录描述一个卷信息,同时该在卷表中的索引就是该卷的ID。卷表的大小受逻辑块大小的限制,但不能超过128。这表示一个UBI设备不能有超过128个卷。
每当一个卷建立、移除、重整大小、重命名或更新,相应卷表中的记录都会改变。UBI维护了两份卷表,以提高可靠性和防突然断电。
2.3.1 执行细节
在内部,卷表位一个专用的UBI卷中,称为layout volume(布局卷)。这个卷由2个物理擦除块组成:每一个擦除块保存一个卷表的拷贝。这个布局卷是一个内部UBI卷,用户是不可见和不可访问的。当个卷在读和写时,操作方法与其它卷一样。
UBI使用下面的算法更新一个卷表记录。
l 准备新卷表的内存缓冲区
l Un-map布局卷的第一个逻辑块。
l 把新的新卷表写入这个逻辑块。
l Un-map布局卷的第二个逻辑块。
l 把新的卷表写入第二个逻辑块。
l 刷新UBI工作列队以确保相关的un-mapped物理擦除块已经擦除。
当访问MTD设备时,UBI确保这两个卷表一致。如果由于意外重启、掉电而造成不一致,那就用第一个卷表覆盖到第二个卷表里(因为这个是最新的)。如果其中的一个卷表损坏了,会用另一个卷表恢复。
2.4 最小读/写单元
UBI使用flash的抽像模型。简单地说,在UBI看来flash是由一众多的或好或坏的可擦除块组成。每个可擦除块可读、可写、可擦除。好的可擦除块可能会变成坏的。
Flash读/写是以最小读/写单元完成的。这取决于flash的类型。
l NOR flash的最小读/写单元大多是1字节,这是因为NOR flash大都可以以单字节读/写的。
l 一些NOR flash的最小读/写单元可能是16或32字节,因为要处理ECC。
l NAND flash的最小读/写单元大都是512、2048或4096,这取决于NAND的页大小。NAND flash储存每页的ECC数据在OOB区,这意味着在写入数据时必须一次写入整页数据以计算ECC,同时在读出时也以整页的数据读出以检证ECC。
最小读/写单元是一个mtd设备的非常重要特征。这会影响的事情有:
l VID header的物理位置取决于最小读/写单元的大小。
2.5 NAND flash sub-pages
如果NAND flash可以分成子页。那么那读/写可以以子页大小读/写,同时可以计算/检证ECC。
2.6 UBI header位置
EC header在擦除块里的0领衔地址,占用64字节。VID header的位置按下面情况而定:
l 如果NOR flash的最小读/写单元是1字节,VID header在64的偏移地址。
l 如果NAND flash如果没有子页,VID header在第二个页。
l 如果NAND flash如果有子页,VID header在第二个子页。
2.7 保存擦除计数
当UBI工作时,在flash介质中保存擦除计数是相当重要的。每个物理擦除块在被擦除后,都在它的EC header更新它已经被擦除的次数。当这个计数丢失时,程序就需要把这个计数恢复。
2.8 UBI flasher是如何工作
下面列表是UBI flasher程序在擦除或flashing UBI镜像的工作:
l 首先,扫描flash收集所有擦除块的擦除计数。也是说,程序读取每个物理擦除块的EC header,检证ECC,然后保存在RAM。这时还不需要读取VID header。跳过坏块。
l 计算平均擦除计数。这是用于修复那些丢失EC header的擦除块的。造成这样的原因可能是不干净的重启。但是这样块的数目应该不多。
l 如果想擦除flash,每个块在被擦除后相应的EC header都要写到该块的起始位置。EC header应该把擦除计数加1。坏块跳过。对于NAND flash,如果在I/O或擦除时发生错误,标记这个块为坏块。
l 如果想flash一个UBI镜像,那么flasher应该按如下处理每个好的物理擦除块。
n 在UBI镜像读取该物理擦除块的内容到缓冲区。
n
n 擦除物理块
n 更改缓冲区的EC header:更新擦除计数值和重新计算ECC。
n 把缓冲区的内容写入擦除块。
通常来说,坏块只需要跳过。对于所有NAND flash,如果在擦除或读/写过程中发生I/O错误,该块需要标记为坏块。
注意,在生产线上为产品烧写UBI镜像时,flasher不需要更改UBI镜像里的EC header。因为这是新的flash所有擦除块的计数值都为0。这意味着在生产线上flasher工作可以更简单。
如果你的UBI镜像里包涵UBIFS文件系统,而你的flash是NAND,你可能需要把物理擦除块要写入数据的缓冲区中后面的“0xFF”字节丢弃。这是非常重要,虽然不是所有的NAND flash都要这样做。这有时会引起一些很难debug出来的问题。所以我们总是记住这一点。
这原因是UBIFS文件系统对待只有“OxFF”字节的NAND flash页是可用的(也是空的)。例如:假设一个物理擦除块的第一页有一些数据,第二页是空的,第三页有一些数据,而第四页和第四页之后的页面都是空的。在这种情况下,UBIFS会认为第四个页面才是第一个空的页面,所有的数据也才从这里开始写入。然而如果在这些位置,UBI已经写入了“0xFF”的字节,UBIFS会写入第二次。而很多的NAND flash型号的页在擦除后,只能写入一次,即使这些页面只有“0xFF”数据。
也是说写入“0xFF”数据有时是会有副作用的。所以把缓冲区中的数据写入物理擦除块之前要剥离这些数据。这不需要扫描整个缓冲区的数据。只需要从后面向前面扫描,找到第一个非“0xFF”的字节,从这里截断,更改缓冲区数据长度即可。参考下面代码:
/**
* calc_data_len - calculate how much real data are 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 returns
* 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;
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;
}
这个函数是在把buf里数据写入物理擦除块之前调用。
2.9 把擦除块标记为坏块
UBI在下面的情况下把擦除块标记为坏块:
1、 擦除块在写入操作时失败,这需要把个块的数据转移到另一个块里面,然后程序调度到检验程序
2、 擦除操作时发生EIO错误。
检验程序是在后台执行,目的探测该物理擦除块是否真的坏块。因为造成作操作失败有可能是其它原因,包括驱动有bug或上层文件系统有错。在检验程序UBI需要做下面工作:
l 擦除该块;
l 读取该块的内容,确认里面是否只有0xFF的数据;
l 写入测试数据;
l 然后在该块把测试数据读出并检验数据是否有错;
l 再用这类的测试数据测试:0xA5、0x5A、0x00;
如里该块在检验程序中测试通过,就不应该把个块标记为坏块。
2.10 卷自重整大小
所周知,在NAND flash里有总有一些厂商标记为坏的块。这些坏块分布随机而且位置不一。尽管厂商保证在flash的第一、二个块不会是坏块而且flash坏块总量不会超过一定的比例。例如,一片新的256MiB的oneNAND芯片是保证其坏块大小不会超过40*128KiB。也就是2%。
当在生产线为产品烧写UBI镜像时,就需要考虑所有卷的实际大小。但这很难定义,因为flash的总体存量是取决于初始坏块的大小。
一个明显的做法是考虑最坏的情况,所有芯片都以最多的坏块数为准。但在实际上,大多数据的芯片是只有极少的坏块。UBI是可以使用片的所有擦除块的。UBI在默认的情况下会预留1%存量用于管理坏块。
一般来说,如果卷标记为auto-resize,可以增加0-2%的存量。
2.11 UBI操作 2.11.1 LEB un-map
LED un-map操作:
l 首先把物理擦除块un-map成逻辑擦除块;
l 然后把物理擦除块调度去擦除再返回。这不会等待物理擦除块擦除操作完成,这擦除操作是在UBI的后台线程中执行的。
当读取一个un-map的逻辑擦除块时,会返回全是0xFF的数据。这可能认为un-map是一个极快的操作。
假设你把一个标记为L的逻辑擦除块un-map成标记为P的物理擦除块。由于P不是同步擦除的,这会在不清洁的重启中发生意外。若在P在擦除过程中发生重启,在重启后UBI访问MTD时L是仍map到P。