TrueFFS原理及其在CF卡上的实现_存储器论文
关键词:trueffs 损耗均衡 闪速存储器 cf卡
闪速存储器最大的一个缺点就是寿命有限。可擦除的次数因芯片厂商而有所不同,一般都在1万~10万次左右。为了延长闪速存储器的寿命,提高使用效率,msystems公司推出了trueffs系统。它为种类繁多的闪速存储器提供了统一的块设备接口,并且具有可重入、线程安全的特点;支持大多数流行的cpu架构,如powerpc、mips、arm、x86、68k等。
由于个性鲜明的闪速存储器越来越受到嵌入式系统工程师的青睐,业界流行的嵌入式实时操作系统vxworks已将trueffs作为自身的一个可裁减的模块。目前该模块的版本为2.0,支持intel、amd、toshiba、fujitsu等厂家生产的大多数型号的闪速存储器和flash卡,用户只需要更改少量代码,甚至可直接调用;但是,该模块对如今风靡的cf卡缺乏支持。
cf卡采用了flash技术。形象地说,cf卡就是由若干片闪速存储器外加一个管理器组成;但是,cf卡具有携带方便、易于升级、存储量大、抗震性好、兼容性佳等优点。目前,cf卡标准已经达到1.4版本,容量从最早的2mb到现今的1gb。然而,有限的擦写闪数是闪速存储器遗传给cf卡的先天缺陷。本文介绍如何在cf上实现trueffs系统,硬件平台以powerpc处理器(mpc8250,motorola公司)为cpu,嵌入式操作系统是vxworks。
1 trueffs的结构
trueffs本身并不是一个文件系统,需要在trueffs之上加载dos文件系统才能使用,否则毫无意义。trueffs屏蔽了下层存储介质的差异,为开发者提供了统一的接口方式。应用程序对存储设备的读写就对像对拥有dos文件系统的磁碟设备的操作一样。
如图1所示,trueffs由1个核心层和3个功能层组成:编译层、mtd层(memory technoilogy driver)、socket层。
翻译层主要实现trueffs和dos文件系统之间的高级交互功能,管理文件系统和flash中各可擦块的关系,以及trueffs中各种智能化处理功能,例如块映射、损耗均衡(wear-leveling)等。目前有三种不同的翻译层模块可供选择。选择哪一种模块要根据使用的flash介质采用nor技术、还是nand技术,或者ssfdc技术而定。
mtd层实现对具体的flash进行读、写、擦、id识别、映射等驱动,并设置与flash密码相关的一些参数。vxworks的trueffs已经包括了支持intel、amd、toshiba等厂商的大多数flash芯片的mtd层驱动。新的器件需要编写新的mtd层驱动。
socket层提供了trueffs和硬件之间的接口服务,负责电源管理、检测设备插拔、硬件写保护、窗口管理和向系统注册socket等。
核心层将其它三层有机结合起来,处理全局问题,例如量、计时器、碎片回收和其它系统资源等。
我们最关心的是mtd层和socket层。vxworks只提供了编译后的二进制形式的核心层和翻译层驱动。在实现trueffs应用之间,先介绍一下trueffs的原理。
2 trueffs原理
2.1 损耗均衡
闪速存储器不能无限次重复使用。它的每个扇区的擦除次数虽然很大,但却有限;因此,随着使用次数的加长,它最终会变成只读状态,所以应该尽最大 可能延长它的寿命。行之有效的方法就是平衡使用所有的存储单元,而不让某一单元过度使用。这种技术被称之为损耗均衡。trueffs使用一种基于一张动态维护表的存储器——块映射的翻译系统来实现损耗均衡技术。当块数据被修改、移动或碎片回收后,这张维护表会自动调整。
然而,如果存储在flash上的一些数据本质上是静态的,就会产生静态文件锁定问题。存储这些静态数据的区域根据不会被轮循使用,其它区域就会被更频繁地使用,这将降低flash期望的生命值。trueffs通过强制转移静态区域的方法成功克服了静态文件锁定问题。因为映射表是动态的,trueffs能够以对文件系统不可见的方式转移这些静态数据区域。由于绝对强制损耗均衡方式会对性能产生一些负面影响,所以trueffs采取了一种非绝对损耗均衡算法。它保证了所有空间的使用近似平等而不影响性能。
2.2 碎片回收
块数据的修改使得flash的一些块区域中的数据不再有效,并且这些区域在擦除之前变得不可写。如果没有机制来回收这些区域,flash很快就会变成只读的状态了。不幸的是由于这些块不可能单独擦除,回收这些块就有些复杂了。单次擦除被限制在一个叫作擦除单元的较大范围内,如对于amd的am29lv065d芯片来说是64kb。
trueffs使用一种被称为碎片回收的机制来回收那些不再包含有效数据的块。该机制从一个预擦除单元内复制所有的有效数据块到另一个新的被称为转移单元的擦除单元。然后,trueffs更新映射表,再擦除这个废旧的预擦除单元。这样,原来的块出现在外界时仍然包含了原来的数据,虽然这些数据现在已经存放在flash存储器的其它空间。
碎片回收算法会找到并回收与下面标准最吻合的擦除单元:
①废块最多;
②擦除次数最少;
③最静态的区域。
2.3 块分配和关联数据集结
为了提高数据的读取效率,trueffs使用一种灵活的空间分配策略:将关联的数据(如由同一个文件的内容组成的多个块)集结到同一个单独擦除单元内的一段连续的区域中。为此,trueffs尽量在同一个擦除单元内维持一个由多个上连续的自由块组成的存储池。如果这样连续的存储池无法实现,trueffs分尽量保证池中的所有块是在同一个擦除单元内。如果连这样的情况也不可能的话,trueffs会尽量把块池分配到一个拥有最多可用空间的擦除单元内。
这种集结关联数据的途径有几个好处。首先,如果trueffs必须从一个小的存储窗口来访问flash,那么这样集结了的关联数据可以减少调用映射块到该窗口的次数,加快了文件继续访问速度。其次,这种策略可以减少碎片的产生。这是因为删除一个文件可以释放掉更容易回收的完整块,意味着碎片回收会变得更快。另外,它可以使属于静态文件的多个块存放在同一地址,这样当损耗均稀算法决定移动静态区域时,转移这些块就变得更加容易了。
2.4 错误恢复
向flash写数据有时可能会出错,比如在响应文件系统写请求时、碎片回收期间甚至在trueffs格式化或擦除flash时。在这些情况下,trueffs能够从错误中恢复过来;但在新数据第一次写入flash时如果出错就会丢失这些数据。然而,trueffs非常仔细地保证所有已经存放在flash上的数据是可恢复的,甚至能够避免用户由于不耐烦或好奇而猛地拔出flash卡而可能造成的灾难性后果。
trueffs健壮的关键是它使用了一种“先写后擦”的策略。当更新flash一个扇区的数据时,只有在更新操作完成并且新存储的数据校验成功后,先前的数据才会被允许擦掉。这样的结果是数据扇区不能处于部分写状态。操作成功的话新扇区的数据有效,否则老扇区的数据有效。很明显,这样有利于用户已经写到flash上的数据的稳定性。
3 编程
trueffs的编程主要在mtd层和socket层。首先必须在当前vxworks生成目录的配置文件(config.h)中定义:include_tffs(包含trueffs系统)、和include_tffs_show(包含trueffs系统的显示函数)。
3.1 翻译层
翻译层根据flash的实现技术来选择。设计中选用了sst公司的型号为sst49cf064的cf卡,64mb容量。它是基于nand的flash技术,所以在文件中定义include_tl_nftl;如果是nor技术,则定义include_tl_ftl。
3.2 mtd层
文件cfcardmtd.c实现了mtd层的功能。在本设计中,mtd层主要实现4个函数:读、写、擦除和id识别。
id识别函数根据读取设备的id号来选择与当前设备匹配的mtd驱动。识别函数中指定了针对当前设备的一些参数以及基本操作函数,并赋给一个叫flflash的数据结构。
flstatus cfmtdidentify(flflash*pvol);
数据结构中的主要参数赋值如下:
pvol->type=cf_id; /*器件id号*/
pvol->erasableblocksize=512;/*可擦除的最小单元是512b*/
pvol->chipsize=0x4000000;/*器件容量为64mb*/
pvol-write=cfwriteroutine;/*写函数*/
pvol->read=cfreadroutine;/*读函数*/
pvol->rease=cferaseroutine;/*擦函数*/
pvol->map=cfmap;/*将cf卡的一段区域映射到内存空间*/
cf卡的读函数比flash的读函数繁琐。它和写一样,必须根据一定的算法来读取数据,而flash只需要直接从地址中读数据。但是,cf卡的擦函数非常简单,直接返回就可以了。因为cf卡可以直接调用写命令写入数据,cf卡本身能够自动完成擦除操作。cfmap函数将cf卡的一段区域映射到存储空间,一般为4kb。因为cf卡的40mb地址空间并不映射到系统的存储空间中,映射可以加快系统访问cf卡的速度,而flash的地址空间,所以flash的mtd驱动中的该函数可以为空。
最后,识别函数必须在mtd驱动表单mtdtable[]中注册:
#ifdef include_mtd_cfcard
cfmtdidentify,
#endif
并增加函数声明:extern flstatus cfmtdidentify (flflash vol).
3.3 socket层
文件systffs.c实现了socket层的功能。systffsinit()函数是主函数,调用socket注册函数cfsocketregister(),初始化socket数据结构flsocket。
local void cfsocketregister (void){
flsocket vol=flsocketof(noofdrives);
tffssocket[noofdrives]=“f”/*socket名称*/
vol.window.baseaddress=cf_base_adrs>>12;/*窗口的基地址*/
vol.carddetected=cfcarddetected;/*检测cf卡是否存在的函数*/
vol.vccon=cfvccon;/*cf卡上电函数*/
vol.vccoff=cfvccoff;/*cf卡继电函数*/
vol.initsocket=cfinifsocket;/*cf卡初始化函数*/
vol.setmappingcontext=cfsetmappingcontext;/*cf卡映射函数*/
vol.getandclearcardchangeindicator=cfgetandclearcard changeindicator;/*设置改变函数*/
vol.writeprotected=cfwriteprotected;/*cf卡写保护判断函数*/
noofdrives++;
}
其中,映射窗口的基地址以4kb为单位。trueffs系统每100ms调用cf卡检测函数,判断cf卡是否存在。cf卡上电函数和断电函数主要用于节省系统功耗,当cf卡出于闲置状态时,trueffs就关闭cf卡的电源。cf卡初始化函数负责访问cf卡之前的所有前期工作。如果插入cf卡型号改变了,cfgetandclearcard changeindicator函数就会及时向trueffs系统报告。systffs.c中需要实现上述的所有函数。大部分情况下,开发人员不必关心flsocket数据结构,只关心它的成员函数。一旦这些成员函数实现了,开发人员不能直接调用它们,它们被trueffs系统自动调用。
4 实现与性能分析
完成trueffs的编写之后,经过编译链接,如果一切正确,vxworks运行时会调用tffsdrv()函数自动初始化trueffs系统,包括建立互斥信号量、全局变量和用来管理trueffs的数据结构,注册socket驱动程序。当trueffs需要和底层具体硬件打交道时,它使用设备号(0~4)作为索引来查找它的flsocket结构,然后用相应结构中的函数来控制它的硬件接口。成功完成socket注册之后,用户就可以调用tffsdevcreate()创建一个trueffs块设备,调用tffsdevformat格式化设备,再调用dosfsdevinit()函数加载dos文件系统。之后,用户就可以像使用磁碟设备一样使用了cf卡了,如调用open、read、write、close、creat等文件操作函数。
trueffs的简单测试方法可以从主机复制一个文件到cf卡,再将这个文件从cf卡复制到主机,然后比较原文件和最后文件的区别。用户也可以调用tffsshow()或tffsshowall()来查看trueffs的创建情况。
trueffs可以极大地延长flash设备的寿命。一般cf卡可以擦写10万次,如果不使用trueffs系统,寿命就非常短。例如,在cf卡上实现一个fat16格式的dos文件系统,簇的大小是2kb,如果要向cf卡中写入一个8mb的文件,共占用4k个簇,出于可靠性考虑,每写一个簇,fat表就更新一次,写一个8mb的文件,fat表需要更新4096次;而fat表一直位于某个固定扇区中,所以8mb的文件最多只能更新25次,一个每天需要备份的文件,那么cf卡的寿命只有25天。这种应用方式使cf卡寿命与其容量无关,其它绝大部分可用扇区白白浪费。
采用了trueffs系统之后 ,因为损耗均衡算法不允许fat表固定在某个扇区中,损耗平均分配给所有扇区。期望的cf卡寿命可以用下列公式计算:
期望寿命=(容量×总擦写次数×0.75)/每天写入字节数
其中,0.75表示文件系统和trueffs管理结构的额外消耗系数。如果同样每天备份一个8mb文件,那么期望寿命=(64mb×100 000×0.75)/8mb=600000(天)(约1643年)。
可见,trueffs惊人地延长了flash器件的寿命。vxworks自带的trueffs驱动器覆盖了业界大部分主流flash芯片,考虑了各种芯片的不同擦写算法,效率较低。对于产时性要求苛刻的系统,开发人员应该按照所用的flash器件有针对性地制作了trueffs驱动器。目前某些cf卡本身实现了一定程度的损耗均衡算法,但是没有trueffs那么高效。