https://blog.csdn.net/shichaog/article/details/45932339
引言
什么是UBIFS文件系统
UBIFS是UBI file system的简称,用于裸的flash设备,作为jffs2的后继文件系统之一。UBIFS通过UBI子系统处理与MTD设备之间动作。UBIFS文件系统更适合MLCNAND FLASH。需要注意的是UBIFS并不是为SSD,MMC,SD,Compact Flash等之类的基于flash的存储设备,其是针对于裸flash设备。
裸flash有以下特点:
l 其包含的块被称为可擦除块,而对于SSD这类的设备,并无可擦除块的概念,取而代之的是扇区的概念。
l 包括读、写、擦除可擦除块三种操作。
l 硬件并不管理坏的可擦除块,而SSD之类的设备则具有专门的控制器处理坏块。
l 可擦除块的读写寿命从几千到几十万之间不等。
图0.1中给出的设备是MMC、SD类型的,该设备具有flash转换层的硬件控制器,该硬件控制器的损耗平衡算法属于商业秘密,华为的dorado 系列高端存储器的文档对其损耗平衡(动态和静态)原理讲解的非常透彻,感兴趣的可以自己找找。对于UBIFS使用的场景,通常只有NANDFLASH那一个模块,其和控制器的接口通常是专门的NAND flash接口。由于这类设备的速率比较慢,所以通常用在相对而言比较低端的嵌入式设备,追求加载速度快一点嵌入式设备通常会选择使用emmc存储器,其文件系统通常会选择UFS。
UBI/UBIFS 协议栈
MTD(Memory Technology Devices)对闪存存储器提供了一个抽象,隐藏了特定flash的独特之处,提供统一的API存取各种类型的flash。
MTD在内核层的API是struct mtd_device而用户空间的API接口是/dev/mtd0,(设备节点被创建在/dev下,是连接内核与用户层的枢纽,就是设备是接到对应哪种接口的哪个ID 上。 相当于硬盘的inode一样的东西,记录了硬件设备的位置和信息在Linux中,所有设备都以文件的形式存放在/dev目录下,都是通过文件的方式进行访问,设备节点是Linux内核对设备的抽象,一个设备节点就是一个文件。应用程序通过一组标准化的调用执行访问设备,这些调用独立于任何特定的驱动程序。而驱动程序负责将这些标准调用映射到实际硬件的特有操作。)这些接口提供了设备信息,读写可擦除块,擦除一个可擦除块,标记一个可擦除块是坏块,检查可擦除块是否是坏块。MTD的API并不隐藏坏的可擦除块也不做任何损耗平衡。
UBI(Unsorted Block Images)的内核API是include/mtd/ubi-user.h,用户空间的则是/dev/ubi0,提供损耗平衡,隐藏坏块,允许运行时容量创建、删除和修改,有点类似LVM功能。UBI线性扩展,在初始化时会读取所有的可擦除块头,所以当flash容量越大,初始化所花费的时间越多,但是就可扩展性而言比JFFS2要好很多。
LEB(logic eraseblock),PEB(physical erase block);将LEB映射到PEB,任何一个LEB可能映射到任何一个PEB,可擦除块头存储的是映射信息以及擦除计数值。
UBI坏的可擦除块处理:
l 为坏的可擦除块预留1%的PEB
l 如果一个PEB变成坏块,则相应的LEB会被映射到一个好的PEB中
l I/O错处处理对上层并未隐藏。
写错误处理
假设用户写数据到LEB0可擦除块,选择一个好的PEB可擦除块(这里假设选择PEB4)来恢复数据,恢复数据实际上就是将数据拷贝到PEB4,然后重新映射LEB0到PEB4,映射完毕后将新数据再次写入PEB4,这是恢复就完成了,将回到UBI层,后台会将PEB1标记为坏块。
原子改变LEB
这对UBIFS非常重要,假设LEB0需要被原子改变,这一过程是这样的:
首先选择一个合适的PEB块,加入这里选择了PEB0,然后向这个块写入新数据,写入完成后需要将原始映射PEB4解映射同时将LEB0映射到PEB0,这是就完成了原子更新操作返回UBI层,但是PEB4的数据会被后台擦除。
对UBI底层的操作有了一些了解后,可以开始UBIFS的介绍了,首先UBIFS并不关系可擦除坏块了,这一信息依赖于UBI层,损耗平衡依赖于UBI层而不是UBIFS,LEB的更改是原子性的。
UBIFS文件索引
索引允许查找任何数据片段的物理地址。索引采用B+2的结构,图中右上半部分,只有叶子节点包含数据,数的扇出可配,缺省值是8。UBIFS的索引在flash上存储和维护。
UBIFS日志
日志比较小,为节省功耗只扫描日志并不扫描flash所有数据,这一结果会让挂载很快完成。文件系统所有更改将被日志记录,索引信息只在内存修改,并不在flash上修改。
主节点存在于LEB1和LEB2,这两个块存储的是一样的,用于备份恢复之用,主节点指向根索引,主分区在挂载时可以很快被找到。
超级块
超级块存储在LEB0位置处,对于UBIFS文件系统只读,但是可以被用户空间工具修改,存储如索引树扇出等一些配置信息,缺省使用zlib或者LZO进行压缩,在挂载该文件系统时,UBIFS的mount方法将被调用读取该超级块。
树节点缓存(TreeNode Cache)
为了加速文件的操作,每次更新flash上的索引树的速率较慢,所以在内存中建立树节点缓存以加快索引树查找。
垃圾回收
有一个空的LEB专门为垃圾回收保留。垃圾回收的一个例子如下:
首先选择一个脏LEB,比如LEB1被选中,然后将其有效数据拷贝的到LEB5,LEB1这时就可以被擦除了,然后再选择一个脏LEB区,如LEB6,同样将数据拷贝到LEB5,拷贝完成后LEB6上的原有数据可能被擦除掉,LEB1将未垃圾回收预留,LEB6这时处于可用状态。索引处理方法,只是将Tree Node Cache的索引节点标记为脏。这样垃圾回收完成。但是还进行一个确认操作,确认操作肯定是可以完成的,会为索引预留至少3倍的空闲空间。
LPT(LEB Properites Tree)
是一个B+树,但是大小固定,比主索引树小很多,管理方法类似于主索引树。
UBIFS初始化
UBIFS的文件系统需要编译的文件如下fs/ubifs/Makefile ,最后会编译成ubifs.o这个文件,链接时会将该文件链接到镜像文件中去。
Shrinker是动态调整树大小的。Journal是UBIFS的日志功能实现的文件,file、dir、super、sb以及io是文件、目录、超级块以及io操作实现;
Tnc(tree nodecache)模块,commit是在垃圾回收确认会用到的操作。gc是垃圾回收实现源码。
为了知道该模块的注册过程,首先查找相关的init函数。
UBIFS接口函数集
Makefile中显示的各个编译后目标的文件提供的函数接口如下:
/* io.c */
void ubifs_ro_mode(struct ubifs_info *c, int err);
int ubifs_leb_read(const struct ubifs_info *c, int lnum, void *buf, int offs,
int len, int even_ebadmsg);
int ubifs_leb_write(struct ubifs_info *c, int lnum, const void *buf, int offs,
int len);
int ubifs_leb_change(struct ubifs_info *c, int lnum, const void *buf, int len);
int ubifs_leb_unmap(struct ubifs_info *c, int lnum);
int ubifs_leb_map(struct ubifs_info *c, int lnum);
int ubifs_is_mapped(const struct ubifs_info *c, int lnum);
int ubifs_wbuf_write_nolock(struct ubifs_wbuf *wbuf, void *buf, int len);
int ubifs_wbuf_seek_nolock(struct ubifs_wbuf *wbuf, int lnum, int offs);
int ubifs_wbuf_init(struct ubifs_info *c, struct ubifs_wbuf *wbuf);
int ubifs_read_node(const struct ubifs_info *c, void *buf, int type, int len,
int lnum, int offs);
int ubifs_read_node_wbuf(struct ubifs_wbuf *wbuf, void *buf, int type, int len,
int lnum, int offs);
int ubifs_write_node(struct ubifs_info *c, void *node, int len, int lnum,
int offs);
int ubifs_check_node(const struct ubifs_info *c, const void *buf, int lnum,
int offs, int quiet, int must_chk_crc);
void ubifs_prepare_node(struct ubifs_info *c, void *buf, int len, int pad);
void ubifs_prep_grp_node(struct ubifs_info *c, void *node, int len, int last);
int ubifs_io_init(struct ubifs_info *c);
void ubifs_pad(const struct ubifs_info *c, void *buf, int pad);
int ubifs_wbuf_sync_nolock(struct ubifs_wbuf *wbuf);
int ubifs_bg_wbufs_sync(struct ubifs_info *c);
void ubifs_wbuf_add_ino_nolock(struct ubifs_wbuf *wbuf, ino_t inum);
int ubifs_sync_wbufs_by_inode(struct ubifs_info *c, struct inode *inode);