嵌入式文件系统损耗平衡算法

为什么要损耗平衡(wear-leveling)?
Flash上的每一位(bit)可以被写操作置成逻辑0。 可是把逻辑 0 置成逻辑 1 却不能按位(bit)来操作,而只能按擦写块(erase block)为单位进行擦写操作。一般来说,“NOR flash擦写块的大小是128K,Nand flash擦写块的大小是8K”【2】。从上层来看,擦写所完成的功能就是把擦写块内的每一位都重设置(reset)成逻辑 1。
Flash的使用寿命是有限的。“具体来说,flash的使用寿命由擦写块的最大可擦 写次数决定。超过了最大可擦写次数,这个擦写块就成为坏块(bad block)了。因此为了避免某个擦写块被过度擦写,以至于它先于其他的擦写块达到最大可擦写次数,我们应该在尽量小的影响性能的前提下,擦写操作均匀的 分布在每个擦写块上。这个过程叫做损耗平衡(wear leveling)”【1】。按照现在的技术水平,一般来说NOR flash擦写块的最大可擦写次数在十万次左右,NAND flash擦写块的最大可擦写次数在百万次左右。
而传统文件系统对于损耗平衡,以及嵌入式系统的掉电保护都没有很好的考虑,所以需要专门的文件系统来读写FLASH。
现有的嵌入式文件系统主要有哪些?
JFFS2(The Journaling Flash File System 2), YAFFS2 (Yet Another Flash File System 2)等,对于其文件系统的具体分析,不再过多叙述,有兴趣的话可以看看我的参考文献,上面均有详细的分析。本文也就这两种算法的损耗算法进行分析。

正文
JFFS2的损耗平衡算法:

在JFFS2中,Flash被分成一个个擦写块。NOR flash 读/写操作的基本单位是字节;而 NAND flash 又把擦写块分成页(page), 页是写操作的基本单位,一般一个页的大小是 512 或 2K 字节,另外每一页还有16 字节的备用空间(SpareData, OOB)。
JFFS2 维护了几个链表来管理擦写块,根据擦写块上内容的不同情况,擦写块会在不同的链表上。具体来说,当一个擦写块上都是合法(valid)的节点时,它会在 clean_list 上;当一个擦写块包含至少一个过时(obsolete)的节点时,它会在 dirty_list 上;当一个擦写块被擦写完毕,并被写入 CLEANMARKER 节点后,它会在 free_list 上。下表表示了具体链表名:
表1 JFFS2的链表中擦除块名称一览表【3】
链表
链表中擦除块的性质
clean_list
只包含有效数据结点
very_dirty_list
所含数据结点大部分都已过时
dirty_list
至少含有一个过时数据结点
erasable_list
所有的数据结点都过时需要擦除。但尚未“调度”到erase_pending_list
erasable_pending_wbuf_list
同erase_pending_list,但擦除必须等待wbuf冲刷后
erasing_list
当前正在擦除
erase_pending_list
当前正等待擦除
erase_complete_list
擦除已完成,但尚未写入CLEANMARKER
free_list
擦除完成,且已经写入CLEANMARKER
bad_list
含有损坏单元
bad_used_list
含有损坏单元,但含有数据

       现在开始分析具体源代码,源程序文件路径为:uClinux-dist/linux-2.6.x/fs/jffs2
>jffs2/fs.c
在挂载jffs2文件系统时,在jffs2_do_fill_super函数的最后创建并启动GC内核线程,相关代码如下:
if (!(sb->s_flags & MS_RDONLY))
              jffs2_start_garbage_collect_thread(c);
       return 0;
如果jffs2文件系统不是以只读方式挂载的,就会有新的数据实体写入flash。而 且jffs2文件系统的特点是在写入新的数据实体时并不修改flash上原有数据实体,而只是将其内核描述符标记为“过时”。系统运行一段时间后若空白 flash擦除块的数量小于一定阈值,则GC被唤醒用于释放所有过时的数据实体【3】。
为了尽量均衡地使用flash分区上的所有擦除块,在选择有效数据实体的副本所写入的擦除块时需要考虑Wear Leveling算法,jffs2_find_gc_block()即Wear Leveling算法的关键函数:
>jffs2/gc.c
static struct jffs2_eraseblock *jffs2_find_gc_block(struct jffs2_sb_info *c)
{
        struct jffs2_eraseblock *ret;
        struct list_head *nextlist = NULL;
        int n = jiffies % 128; //0~127的随机数
        /* Pick an eraseblock to garbage collect next. This is where we‘ll
           put the clever wear-levelling algorithms. Eventually. */
        /* We possibly want to favour the dirtier blocks more when the
           number of free blocks is low. */
        if (!list_empty(&c->bad_used_list) && c->nr_free_blocks > c->resv_blocks_gcbad) {
                D1(printk(KERN_DEBUG "Picking block from bad_used_list to GC next\n"));
                nextlist = &c->bad_used_list; //含有损坏单元,但含有数据的块中先回收
        } else if (n < 50 && !list_empty(&c->erasable_list)) {
                /* Note that most of them will have gone directly to be erased.
                   So don‘t favour the erasable_list _too_ much. */
                D1(printk(KERN_DEBUG "Picking block from erasable_list to GC next\n"));
                nextlist = &c->erasable_list;// 如果有已过时,但尚未擦除的块,50/128的概率回收        } else if (n < 110 && !list_empty(&c->very_dirty_list)) {
                /* Most of the time, pick one off the very_dirty list */
                D1(printk(KERN_DEBUG "Picking block from very_dirty_list to GC next\n"));
                nextlist = &c->very_dirty_list;//如果有大部分数据已经过时的块,110/128的概率回收        } else if (n < 126 && !list_empty(&c->dirty_list)) {
                D1(printk(KERN_DEBUG "Picking block from dirty_list to GC next\n"));
                nextlist = &c->dirty_list;//如果有部分数据已经过时的块,126/128的概率回收
        } else if (!list_empty(&c->clean_list)) {
                D1(printk(KERN_DEBUG "Picking block from clean_list to GC next\n"));
                nextlist = &c->clean_list;//回收只含有有效数据的块
        } else if (!list_empty(&c->dirty_list)) {
                D1(printk(KERN_DEBUG "Picking block from dirty_list to GC next (clean_list was empty)\n"));
                nextlist = &c->dirty_list;//回收有部分数据已经过时的块
        } else if (!list_empty(&c->very_dirty_list)) {
                D1(printk(KERN_DEBUG "Picking block from very_dirty_list to GC next (clean_list and dirty_list were empty)\n"));
                nextlist = &c->very_dirty_list;//回收大部分数据已经过时的块
        } else if (!list_empty(&c->erasable_list)) {
                D1(printk(KERN_DEBUG "Picking block from erasable_list to GC next (clean_list and {very_,}dirty_list were empty)\n")
);               nextlist = &c->erasable_list;//回收已过时,但尚未擦除的块
        } else {
                /* Eep. All were empty */
                D1(printk(KERN_NOTICE "jffs2: No clean, dirty _or_ erasable blocks to GC from! Where are they all?\n"));
                return NULL;//已经无可调度的块
        }      

        ret = list_entry(nextlist->next, struct jffs2_eraseblock, list);
        list_del(&ret->list);
        c->gcblock = ret;
        ret->gc_node = ret->first_node;
        if (!ret->gc_node) {
                printk(KERN_WARNING "Eep. ret->gc_node for block at 0x%08x is NULL\n", ret->offset);
                BUG();
        }      

        /* Have we accidentally picked a clean block with wasted space ? */
        if (ret->wasted_size) {
                D1(printk(KERN_DEBUG "Converting wasted_size %08x to dirty_size\n", ret->wasted_size));
                ret->dirty_size += ret->wasted_size;
                c->wasted_size -= ret->wasted_size;
                c->dirty_size += ret->wasted_size;
                ret->wasted_size = 0;
        }            

        D1(jffs2_dump_block_lists(c));
        return ret;
}

由上可见,JFFS2的处理方式是以 50/128的概率回收erasable_list,以110/128概率回收 very_dirty_list,以126/128概率回收 dirty_list,剩下概率回收 clean_list。当然从程序中也可以看出,回收后一条链表块,是要在一定概率以及前面的块链表为空的情况下。
JFFS2 对损耗平衡是用概率的方法来解决的,这很难保证损耗平衡的确定性。在某些情况下,可能造成对擦写块不必要的擦写操作;在某些情况下,又会引起对损耗平衡调整的不及时。
“据说后来,JFFS2有改进的损耗平衡补丁程序,这个补丁程序的基本思想是,记录每 个擦写块的擦写次数,当flash上各个擦写块的擦写次数的差距超过某个预定的阀值,开始进行损耗平衡的调整。调整的策略是,在垃圾回收时将擦写次数小的 擦写块上的数据迁移到擦写次数大的擦写块上。这样一来我们提高了损耗平衡的确定性,我们可以知道什么时候开始损耗平衡的调整,也可以知道选取哪些擦写块进 行损耗平衡的调整”【1】。
现在,JFFS3也正在开发中,在垃圾回收一章中,作者有 这样的话:“JFFS3 Garbage Collection is currently under development and this chapter may be changed. Any suggestions and ideas are welcome.”【4】,还没有发布具体的设计(05年写得啊。。。)。我期待Artem B. Bityutskiy能设计出更加巧妙而高效的平衡算法。

YAFFS2的损耗平衡算法:

YAFFS是专门针对Nand flash的文件系统,YAFFS1只能支持每页为512B。而YAFFS2则另外也支持2K的页面, 其他方面变化见网页介绍:www.aleph1.co.uk/node/38。
YAFFS 对文件系统上的所有内容(比如正常文件,目录,链接,设备文件等等)都统一当作文件来处理,每个文件都有一个页面专门存放文件头,文件头保存了文件的模 式、所有者id、组id、长度、文件名、Parent Object ID 等信息。因为需要在一页内放下这些内容,所以对文件名的长度,符号链接对象的路径名等长度都有限制。
前面说到对于Nand flash上的每一页数据,都有额外的空间用来存储附加信息,通常Nand flash 驱动只使用了这些空间的一部分,YAFFS正是利用了这部分空间中剩余的部分来存储文件系统相关的内容。以512+16B为一个PAGE 的Nand flash芯片为例, YAFFS 文件系统数据的存储布局如下所示:
表2  YAFFS的存储布局【5】
0-511
Data
512-515
YAFFS TAG
516
Data status byte
517
Block status byte
518-519
YAFFS TAG
520-522
后256字节的ECC
523-524
YAFFS TAG
525-527
前256字节的ECC
可以看到在这里YAFFS 一共使用8个BYTE 用来存放文件系统相关的信息(yaffs_Tags)。这8 个Byte 的具体使用情况按顺序如下:
表3  yaffs_Tags的具体使用情况【5】
位
属性
20
ChunkID,该page 在一个文件内的索引号
2
2 bits serial number
10
ByteCount 该page 内的有效字节数
18
ObjectID 对象ID 号,用来唯一标示一个文件
12
Ecc, Yaffs_Tags 本身的ECC 校验和
2
保留

其中Serial Number 在文件系统创建时都为0,以后每次写具有同一ObjectID 和ChunkID 的page 的时候都     加1, 因为YAFFS 在更新一个PAGE 的时候总是在一个新的物理Page 上写入数据,再将原先的物理Page 删除,所以该serial number 可以在断电等特殊情况下,当新的page 已经写入但老的page 还没有被删除的时候用来识别正确的Page,保证数据的正确性。这对保存数据的完整性是很有用的。
YAFFS按顺序分配当前块中的页。当该块中所有的页都用光后,另外一个干净的块将会 被选择分配。至少保留2到3个块用于垃圾块收集。当没有足够的干净块的时候,脏块(比如只包含丢弃数据)将会被擦除成干净块。如果没有脏块,那些特别脏的 块(含有丢弃数据特别多的块)也会被垃圾块回收算法选中。
作者也指出,YAFFS的损耗平衡是块分配策略的副产品,数据总是被写在下一个空闲块 中,所有的块都是被平等对待的。但是包含那些永久数据的块却不会再回收到空闲块中,所以他所说的损耗平衡也仅仅指那些已经空闲或者有可能变空闲的块。假设 在一块8M的NAND器件上,有2M空间用来存放相对固定,不经常修改的数据文件,则经常修改的文件只能在剩下的6M空间上重复擦写。只在这6M空间上做 到均匀损耗,而没有包括剩余的2M。“对整个器件来说,系统并没有合适的搬移策略对固定文件进行搬移,整个器件做不到均匀损耗,在实时记录信息量比较大的 应用环境中,应编写相应的搬移策略函数,对固定文件进行定期的搬移,以确保整个NAND器件的均匀损耗”【7】。
下面开始分析源代码,其公司的主页上可以下到最新源代码:www.aleph1.co.uk/node/
可以看下它回收算法的关键函数yaffs_CheckGarbageCollection():
>fs/yaffs/yaffs_guts.c
/*
如果可用的擦除块很少,我们就采用“主动模式”来收集垃圾块,否则我们就采用“被动模式”。“主动模式”中将会查看整个Flash的块,就算只有少量过时数据的块也会被回收。“被动模式”只会查收少量区域,然后回收那些过时数据特别多的块。
 */
static int yaffs_CheckGarbageCollection(yaffs_Device * dev)
{
       int block;
       int aggressive;
       int gcOk = YAFFS_OK;
       int maxTries = 0;
       int checkpointBlockAdjust;
       if (dev->isDoingGC) {
              return YAFFS_OK;
       }    

       do {
              maxTries++;
              checkpointBlockAdjust = (dev->nCheckpointReservedBlocks - dev->blocksInCheckpoint);
              if(checkpointBlockAdjust < 0)
                     checkpointBlockAdjust = 0;

              if (dev->nErasedBlocks < (dev->nReservedBlocks + checkpointBlockAdjust)) {
/* 跟需要保留的块数和自定义校准块的和作比较,如果少于这个值,就直接选择主动模式*/
                     aggressive = 1;
              } else {
                     /* 否则,选择被动模式 */
                     aggressive = 0;
              }
/*根据模式查找,回收相应的块*/
              block = yaffs_FindBlockForGarbageCollection(dev, aggressive);
              if (block > 0) {
                     dev->garbageCollections++;
                     if (!aggressive) {
                            dev->passiveGarbageCollections++;
                     }

                     T(YAFFS_TRACE_GC,
                      (TSTR
                        ("yaffs: GC erasedBlocks %d aggressive %d" TENDSTR),
                        dev->nErasedBlocks, aggressive));

                     gcOk = yaffs_GarbageCollectBlock(dev, block);
              }
              if (dev->nErasedBlocks < (dev->nReservedBlocks) && block > 0) {
                     T(YAFFS_TRACE_GC,
                      (TSTR
                        ("yaffs: GC !!!no reclaim!!! erasedBlocks %d after try %d block %d"
                         TENDSTR), dev->nErasedBlocks, maxTries, block));
              }
       } while ((dev->nErasedBlocks < dev->nReservedBlocks) && (block > 0)
               && (maxTries < 2));

       return aggressive ? gcOk : YAFFS_OK;
}

从上面的算法也可以看出,其实YAFFS的损耗平衡算法也没什么可讲的,主要是由于设计的原因,导致其平等地回收每一个空闲块。如果空闲块比较少,就加紧回收稍微有空闲的块。因为设计简单,所以效率还是不错的,下表是其测试结果,结果还是相当不错:
表4 Times for 1MB of garbage collection at 50% dirty (单位1uS).【6】
Operation
YAFFS1
YAFFS2(512b pages)
YAFFS2 (2kB pages)
YAFFS2(2kB pages, x16)
Delete 1MB
558080
128000
16000
16000
Write 0.5MB
337920
337920
168960
112640
Total
896000
465920
184960
128640
MB/s
1.1
2.1
5.4
7.7
Relative speed
1
1.9
4.9
7

总结及展望:
通过阅读JFFS2和YAFFS的文献的代码,我觉得谁优谁劣很难讲。JFFS的文件 系统开发要比YAFFS要早得多,现在应用范围也比YAFFS广。可以说YAFFS就是相当于JFFS的“另一种文件系统”,所以YAFFS一开始就是从 借鉴JFFS开始的,避免了JFFS启动速度过慢和损耗不平衡等明显的缺点,当然后来JFFS2也进行了改进,如把数据移动到页的末尾(而YAFFS是放 在页的前面),而不再是链表式在页中随便乱放,导致装载速度巨慢。在YAFFS公司网页的开发内容中也可以看到,YAFFS和JFSS2的开发人员经常进 行交流。从我个人的角度来看,YAFFS2要比JFFS2好些,当然这是仅限于Nand flash来说。在阅读YAFFS2的源代码时我发现,最新的更新日期是07年3月,而就我所看到的JFFS2代码是04年5月,而JFFS3的设计草案 【4】的发布日期也是05年11月。两种文件系统的维护速度可见一斑。
以上的分析我也只能定性得说下,如果有Flash芯片可以测试,那就可以定量的分析了。如果有时间,我想再深入看下两类文件系统的源代码,顺便推荐下,参考文献【3】确实写得很认真,值得一看。

参考文献:
【1】、JFFS2文件系统及新特性介绍:www.sqlus.com/Columns/LinuxSkill/012108669.html
【2】、David Woodhouse,  JFFS : The Journalling Flash File System
【3】、shrek2(www.linuxforum.net的一个ID),《JFFS2源代码情景分析(Beta2)》
【4】、Artem B. Bityutskiy,  JFFS3 design issues(Version 0.32 (draft))
【5】、Raymond([email protected]),《Yaffs文件系统结构及应用》
【6】、YAFFS Development Notes:http://www.aleph1.co.uk/node/39
【7】、胡一飞,徐中伟,谢世环,《NAND flash上均匀损耗与掉电恢复在线测试》
时间: 2024-10-15 09:10:26

嵌入式文件系统损耗平衡算法的相关文章

TQFS文件系统架构说明(又一个最适合Nandflash的轻量级嵌入式文件系统)

前言  根据NandFlash的一些特性,读写速度快,生产工艺问题或多或少存在坏块,最小擦除单位为块,最小读取单位为字节,并且不能频繁对其进行擦写操作,最先对NandFlash支持最好的就是Yaffs文件系统,但Yaffs文件系统最大支持的页大小为512Byte,Yaffs2支持页大小可以达到2048Byte 但针对那些像linux的大型操作系统不管是Rom和Ram的开销确实不算什么,但如果只是把它应用在ram只有几十Kbyte的单片机上也是经过裁剪再裁剪才能放进去,这就需要诞生一种集Yaffs

宿主机挂载和使用嵌入式文件系统

一般的文件系统都是可以在宿主机上先挂载看看文件系统是否有错误,但是一些特殊的文件系统如:为flash而生的jffs2,yaffs2文件系统没法进行挂载,只能进行烧录测试. 使用loseup命令用来设置循环设备.循环设备可把文件虚拟成块设备,籍此来模拟整个文件系统,让用户得以将其视为硬盘驱动器,光驱或软驱等设备,并挂入当作目录来使用. 使用格式: losetup [ -e encryption ] [ -o offset ] loop_device file losetup [ -d ] loop

嵌入式 Linux根文件系统移植(二)——根文件系统简介

嵌入式 Linux根文件系统移植(二)--根文件系统简介 根文件系统是内核启动时挂载的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行. 一.嵌入式设备文件系统 在嵌入式Linux应用中,主要的存储设备为 RAM(DRAM, SDRAM)和ROM(常采用FLASH存储器),常用的基于存储设备的文件系统类型包括:jffs2, yaffs, cramfs, romfs, ramdisk, ramfs/tm

(转) 嵌入式根文件系统的移植和制作(一)

转自 http://www.mcuos.com/thread-3822-1-10.html 一.文件系统简介 理论上说一个嵌入式设备如果内核能够运行起来,且不需要运行用户进程的话,是不需要文件系统的,文件系统简单的说就是一种目录结构,由于linux操作系统的设备在系统中是以文件的形式存在,将这些文件进行分类管理以及提供和内核交互的接口,就形成一定的目录结构也就是文件系统,文件系统是为用户反映系统的一种形式,为用户提供一个检测控制系统的接口. 根文件系统,我认为根文件系统就是一种特殊的文件系统,那

阅读根文件系统论文笔记

uClinux下JFFS2文件系统的实现.pdf 2004 JFFS文件系统是瑞典Axis通信公司开发的一种基于Flash的日志文件系统,JFFS2是它的第2版,由Redhat公司开发.JFFS2以其优异的性能在嵌入式系统中被越来越广泛地使用. 嵌入式Linux文件系统研究与应用.pdf 2010 在嵌入式系统中,文件系统的类型和文件的存储介质密切相关.通常,嵌入式系统外围存储器使用FLASH存储器,针对FLASH存储器的文件系统类型有 CRAMFS. ROMFS. JFFS/ JFFS2和 Y

JFFS2文件系统的移植

Linux文件系统的移植-JFFS2 JFFS2是JFFS的后继者,由Red Hat重新改写而成.JFFS2的全名为JournallingFlash File System Version 2(闪存日志型文件系统第2版),其功能就是管理在MTD设备上实现的日志型文件系统.与其他的存储设备存储方案相比,JFFS2并不准备提供让传统文件系统也可以使用此类设备的转换层.它只会直接在MTD设备上实现日志结构的文件系统.JFFS2会在安装的时候,扫描MTD设备的日志内容,并在RAM中重新建立文件系统结构本

Linux文件系统详解(文件系统层次、分类、存储结构、存储介质、文件节点inode)

从操作系统的角度详解Linux文件系统层次.文件系统分类.文件系统的存储结构.不同存储介质的区别(RAM.ROM.Flash).存储节点inode. 本文参考:http://blog.chinaunix.net/uid-8698570-id-1763151.html http://www.iteye.com/topic/816268 http://soft.chinabyte.com/os/142/12315142.shtml http://www.ibm.com/developerworks/

嵌入式: jffs2,yaffs2,logfs,ubifs文件系统性能分析

在嵌入式领域,FLASH是一种常用的存储介质,由于其特殊的硬件结构,所以普通的文件系统如ext2,ext3等都不适合在其上使用,于是就出现了专门针对FLASH的文件系统,比较常用的有jffs2,yaffs2,logfs,ubifs.那么对于这几个文件系统,如何选择一个针对自己的硬件最合适的呢?他们各自的特点是什么?性能优劣如何? 下面一个PDF为我找到的一篇专门针对这几个文件系统做的性能分析的文档,就这些文件系统的挂载时间,I/O性能,内存使用,掉电恢复,FLASH寿命等方面进行了详细的对比分析

Linux根文件系统裁剪 论文阅读笔记

Linux裁剪方法研究 2006 2 Linux裁剪原理: 2 Linux嵌入式系统根文件系统的选择与制作 2006 3 Linux嵌入式系统根文件系统的选择与制作 2006 3 基于ARM的嵌入式Linux操作系统移植的研究 2006 5 基于ARM的嵌入式文件系统研究与设计 2010 6 基于嵌入式Linux的Ext2根文件系统制作分析 2015 6 嵌入式Linux裁剪研究 2009 7 嵌入式Linux根文件系统的构建与分析 2015 8 嵌入式Linux共享库裁剪技术分析与改进 200