摘 要: 针对Flash 存储器的特点,设计了一种适合开源实时操作的嵌入式文件系统。 该系统设计依据日志文件系统原理,实现了Flash 存储器和内存中的数据结构及特殊处理机制,并基于μC/OSOII 和三星S4510B 完成了该文件系统的测试。 该嵌入式文件系统可进行断电恢复,其代码精简,占用系统资源少,执行效率高,有较高的安全性。
关键词: Flash 存储器;嵌入式;文件系统
引言
Flash 存储器( Flash Memory) 是一种高可靠性、高密度的固态存储器件。 其存储方式是完全非易失性的,掉电后可以保存数据;可以在线写入,并可按页连续字节写入,存取速度快,所以嵌入式系统通常使用Flash 存储器作为存储设备。 但Flash存储器也存在着两个主要缺陷:一是在重写之前必须进行擦除,因为Flash 存储器划分成很多擦除块(SectorOErase) ,对任何一位数据进行修改必须先擦除整个块(Sector) ;二是擦除块的擦除次数有限,当一个块提前达到擦除次数上限时, 将导致整个Flash 存储器无法使用。 所以,目前PC 机上很多成熟的基于磁盘的文件系统在Flash 存储器上使用都存在着不足。
嵌入式系统应具有的特点: 一是高可靠性,在恶劣环境下系统仍能正常工作;二是低消耗,受成本限制系统设计必须量体裁衣,去除冗余;三是高效率,在占用较少资源情况下保证功能需求,这样就要求算法简单,效率高。 而日志文件系统(Log-St ruct ured File System) 在数据更新时无需将数据写入原存储区域,适应Flash 存储器无法进行重写这一特点。 目前,针对Flash 存储器的缺陷而设计的Linux 下的J FFS 文件系统,就是采用简化的日志文件系统。 J FFS 文件系统将磨损均衡集成于清除机制之中,在带来掉电可恢复功能的同时,大大减少了块擦除的次数,提高了文件系统的存取速度和效率。 但是,J FFS 文件系统无法单独使用,或者使用于其它实时操作系统中。 对由于受成本和实时性限制而无法使用Linux 的一些嵌入式系统,也就无法使用J FFS 文件系统。基于上述分析,该嵌入式文件系统适合在开源实时操作系统(如μC/OS-II) 和无操作系统的情况下使用。
嵌入式文件系统原理
在日志文件系统中,一个文件被修改后不是被写入到原来的存储空间,而是被加到所有内容的后面,象日志一样被更新,这就是日志文件系统的基本原理。 由于同一个文件在文件系统中会留下不同的版本,所以系统需要设置一张表标注文件的最新与以前的版本。 在内容不断添加时为不将存储空间占满,系统设计了一种回收机制,回收无效内容占用的空间。
日志文件系统在文件更新时不用将文件写回原来的地址,这对Flash 存储器这种存储介质最为适合。 文中所设计的嵌入式文件系统采用了日志文件系统的设计原理,以及J FFS 文件系统将磨损均衡集成于清除机制之中的方法。 该系统将一个可擦写块平分为多个簇,文件的读写以簇为单位进行。簇的状态有3 种:脏、干净和空。 脏表示所存内容已被置为无效;干净表示所存数据有效;空表示可以写入数据。 文件和目录在该系统中被作为节点,一个节点占用若干个簇,节点中的内容连续存储,但不能越过块边界存储。 该系统设置一个索引节点,保存整个系统的信息,其中包含保存有各簇状态的簇状态表。
每一次文件更新后内容都将被添加至末尾处,索引节点也被更新,总是占用最末尾的干净簇。 回收脏簇时,将所要擦除块中的干净簇重写到空簇中,再进行块擦除。 当内容写至存储体末端,则从头部重新开始循环存储。 所设计的文件系统的操作过程见图1。
嵌入式文件系统设计
Flash 存储器中的存储结构
Flash 存储器中的存储结构见图2。 该存储器中每个簇的第一个字作为簇的状态字,表示此簇是否为一个节点的首簇或空簇。 每个节点的首部存放此节点属性(文件/目录/索引节点) 和节点标识号。
索引节点
索引节点存放该文件系统的大部分信息。 包括32 位的索引节点更新号、一张簇状态表、下一个要被擦除块的块号、给下一个新建节点(文件或目录) 的节点编号、系统根目录信息表。系统每一次更新都会产生新的索引节点,索引节点更新号加1。 按照Flash 存储器的使用寿命10 年计算,需要每秒更新136 次以上,才能达到索引节点更新号的上限,所以认为拥有最大更新号的索引节点为最新的索引节点。 簇状态表中对应每一个簇有两个Bit 位,表示各个簇的状态(干净01 ,脏11 ,空00) 。 根目录信息表存放根目录下的各个目录项,每个目录项包括:属性(文件0x1/目录0x0) 、文件名或目录名、节点编号、此文件(或目录) 对应节点的起始簇地址、根目录表的大小可变。
目录节点
目录节点存放的内容有目录名,目录项个数,及所有目录项信息。 文件节点存放文件名,文件大小,文件属性及文件内容,内存中的目录结构见图3。
内存数据结构及基本操作
该文件系统载入(Mount ) 后,会在内存中建立一个系统的映象。 该映象包括:索引节点中的信息、目录及文件信息、每个可擦写块中包含的节点信息、未存盘的节点信息。 簇状态表、索引节点更新号、新节点编号、下一擦除块号等索引节点中的内容,在内存中均作为不同的变量。 内存中为每个文件和目录都建立了映象,数据结构见图4 和图5。
内存中的文件节点不包含文件真正的数据,而使用指针。 文件被打开时,在内存中创建一块新存储区域存放数据,数据指针便指向此存储区,未被打开时,此指针指向空。 对于每个目录有1 个目录层数,表示此目录的深度,如根目录的目录层数为0 ,根目录的下一级目录则为1 ,依此类推。 存储地址保存文件或目录在Flash 中的地址。 文件和目录都被存在上一级目录下,所属目录指针即指向上一级目录在内存中的数据结构,根目录的所属目录指针即为空。 对于同目录下的不同节点,在内存中使用链表将其串联,同目录文件指针即联成链表。 链表的首指针保存在上一级目录中,首目录项指针即指向链表的首项。 为提高块擦写的效率,存储在同一个可擦写块中的各个节点在内存中也建立一个链表,块队列指针即用于连成此链表。 为标识被修改的节点,利用一个未保存队列,未保存队列指针即用来建立此队列。
该文件系统载入(mount ) 时,首先顺序扫描Flash 中的每个索引节点,查找出最大的索引节点更新号,此更新号对应的索引节点即为最新的索引节点。 查找到最新索引节点后,将簇状态表等信息映射到内存的数据结构中。 依据索引节点中的根目录信息,遍历所有节点,建立内存中的目录文件结构,并将节点添加到对应的擦写块队列中。 对一个文件编辑并保存的过程见图6。
文件打开时,先在内存中分配一块空间作为数据区,将内容写入,并定位文件节点的数据指针指向该内存中的数据区。 如果文件内容被修改,就将文件节点添加到未存盘队列,依次写入Flash 存储器中,并修改簇状态表。 保存时将内存中数据区内容写入Flash 中,释放申请的内存空间,修改节点中的数据指针和簇状态表,再将文件的所有上级目录重新写入Flash ,最后将更新后的索引节点内容写入Flash。 如果文件未被修改,则只需修改数据指针即可。
节点加入未存盘队列的顺序按照目录层数的大小排列,文件节点排在队列首,目录层数最大的排在其后,目录层数为1 的排在队列末尾,根目录不加入未存盘队列。
嵌入式文件系统特殊处理机制
均衡擦写机制
为了避免任意一个可擦除块因擦写次数过多而过早报废,文件系统对Flash擦写时采用了均衡擦写机制。 考虑到系统的精简性,擦写在整片Flash 的各块中依次进行,一块擦写完后,下一个被擦写的块即为后一个块,在系统的索引节点中保存了下一个要擦除的块号。 当文件系统中的剩余空间减少到设定值时,系统会擦除此块,以回收脏簇占用的空间。 对应每个可擦写块都有一个节点队列,此块中包含的节点都加入其中。块擦除的流程见图7。
首先,将未保存于队列中的节点保存,清未保存队列。 然后将块队列中的所有文件节点转移到空簇中,同时将文件路径上的各级目录加入到未存盘队列中。 对于块队列中的目录节点,则将它和其路径上的各级目录加入未存盘队列中,按照未保存队列的顺序,依次将各个目录写入Flash 中,最后写入最新的索引节点。 因为目录节点加入未存盘队列时,按照目录层数的大小排列,所以按照未保存队列的顺序写入时,可以保证当一个目录要被写入Flash 时,它的所有下级目录已被写入Flash 中。 所有下级目录在Flash 中的存储地址都已确定。当该文件系统的空间将达到存储上限时,可能会出现特殊情况,即废簇回收时,空簇的空间不足,无法将所有干净簇重写。 文件系统为此建立了应急机制,先将文件节点内容存在内存中,这时新建一个临时未保存队列,专门保存文件节点,在块擦写完成后,将剩余的文件节点写入新的空簇中,其算法与图7 所示流程大致相同。 但是,一旦在擦写时断电,会导致该块上的所有数据丢失。
断电错误处理机制
当系统遭遇断电重新启动后,索引节点中的信息会与系统中的状态不符,这时便需要错误处理机制。 错误一般是索引节点中标注的空簇已被写入了数据,错误处理就是将此簇标志为脏簇,并查找下一个空簇重新写入。
多任务处理机制
该文件系统允许同时打开多个文件,在多任务操作系统下,为了避免冲突建立了多任务处理机制。 系统允许打开的多个文件在内存中同时被编辑修改,但是对Flash 写入操作有限制。 处理方法是设立Flash 写入保护区,在此区中只允许当前正在执行的任务执行Flash 写入操作。 实现Flash 写入保护区的方法是建立一个初始
值为1 的信号量,当一个节点需要Flash 写入时,首先申请信号量,完成后再释放信号量。 Flash 写入保护区见图6 、图7。在图6 中,空操作语句是用来对多个文件的保存进行同步。 例如,有文件1 和文件2 需要保存,先将文件1 的内容写入Flash 中,文件1 路径下的目录节点被添加到未保存队列中,再将文件2 的内容写入Flash 中,文件2 路径下的目录节点也被添加到未保存队列中,最后将未保存队列中的所有节点都写入Flash 中。 这样,如果同一路径下的两个文件同时存盘,可避免路径下的相同目录节点被写入两次,从而提高了效率。 不足之处在于,如果很多文件同时存盘,会导致索引节点在一段时间内都无法写入Flash 存储器,有断电丢失的危险。 但对于一般嵌入式系统来说,很少会碰到这种情况。 当进行Flash存储器擦写时,在取块队列首节点至索引节点写入完成这段时间内都不允许进行其他Flash 存储器的写入操作,这是为了保证数据的完整性,同时也提高了文件系统的稳定性。
无目录文件系统的优化
许多嵌入式系统设计中虽没有目录管理的要求,但是对执行效率和资源消耗的要求较高。 对于不要求有目录管理的精简文件系统,在设计时也进行了优化。 精简文件系统在Flash 中的存储格式与上述设计相同,文件系统中的所有文件信息都保存在索引节点的根目录信息表中。 精简文件系统在内存中的映象则要简单很多,只包含索引节点中的信息,包括簇状态表、下一个擦除块、下一个新节点的标号和根目录信息,而不用为每个文件都建立内存中的映象,节省大量的内存空间。 文件的编辑存盘过程简化为:打开文件、编辑、将文件写入Flash 存储器、将修改后的索引节点写入Flash 存储器。 擦写则只需通过查询根目录信息表中的各个目录项,将块中的所有文件节点写入空簇即可。在无目录管理的情况下,精简文件系统占用的内存资源可以减少,操作也可便捷,提高了效率。 对于大量只需要按名存取的简单文件管理的小型嵌入式系统而言,针对Flash 存储器的简单文件系统将占用资源少,执行效率高,有很大的应用价值。
嵌入式文件系统实现及性能分析
该文件系统的实现采用了分层方法,分为3 层4 个部分:应用程序接口、文件系统核心、操作系统调用接口、Flash 存储器驱动,实现结构见图8。
实现平台中RTOS 为μC/OSOII 实时操作系统,CPU 使用三星S4510B作为处理器,Flash 存储器芯片为FUJ ITSU 的29LV160 TE。 针对不同的实时操作系统和Flash 存储器芯片需要实现不同的操作系统接口和Flash 存储器驱动。
针对μC/ OSOII 编写操作系统调用接口,包括5个函数: ①系统调用接口初始化FS_Sys_Interface_Init ( ) ,创建互斥信号量和内存分区; ② Flash 写入关闭FS_Sys_Write_Lock ( ) ,禁止Flash 写入操作,调用μC/OS-II 中OSMutePend ( ) ; ③ Flash写入打开FS_Sys_Write_Unlock ( ) ,重新允许Flash 写入操作,调用μC/OS-II 中OSMutePost() ; ④内存空间申请FS_Sys_Mem_Alloc( ) 和内存空间添加FS_Sys_Mem_Add ( ) , 都调用OSMemGet ( ) 来完成; ⑤内存空间释放FS_Sys_Mem_Free ( ) ,调用OSMemPut ( ) 完成,将申请的内存块全部释放。针对29LV160 TE 这款Flash 存储器芯片,定义一个FlashDef 结构体的全局变量, 用于存储Flash 器件信息,并且编写针对此款Flash 的块擦写函数FS_Device_Sector_Erase ( ) 和数据写入函数FJ FS_Device_Write ( ) 。
完成这两部分的实现后,该系统就可运行调试。 测试应用程序接口(API) 。 应该提供的各部分功能,并在突然断电情况下,测试文件系统的恢复情况。无目录管理的精简文件系统的载入,可在2μs内完成,文件写入耗时主要为闪存的等待时间,系统本身只占用不到200 个字节的内存,产生的代码段大小为7 K。 完整的文件系统载入时,需要建立内存中映象,耗时根据文件数量的多少而不同,一般为10μs ,产生的代码段大小为11 K。 系统写入效率较高,在无目录管理的配置下尤其明显。 试验中系统在多次断电的情况下,系统仍能恢复至上次存盘的状态,虽会导致个别文件未更新,但不会导致文件系统崩溃。
结 语
针对Flash 存储器的固有缺陷设计了一种基于Flash 存储器的嵌入式文件系统。 在文件读写时,极大减少了Flash 存储器擦写的次数,提高了效率。 此系统代码精简,运行时占用内存资源少,运行效率高,而且有断电保护,有较高的安全性。
阅读(854) | 评论(0) | 转发(0) |