在嵌入式手持移动终端中,应用程序需要存储和检索大量的用户信息,这些信息在应用程序退出时应该被记录保存下来,同时这些信息要能被多个应用程序并发使用,比如电话本中的数据要能够被电话,短消息,名片等多个应用程序共享。为达到以上要求,就引出了文件的概念,把这些用户信息以文件的形式存储在外部介质上,独立于任何应用程序。
目前通用计算机上已经有很多成熟的文件系统,如DOS下的FAT文件系统、Windows NT下的NT-FS文件系统及UNIX文件系统等。但是同一般的PC使用磁盘,光盘,磁带机等存储设备不同,嵌入式手持移动终端一般只配有极少的ROM,RAM(主存)和FLASH(辅存),因此这些通用文件系统并不适合直接应用到嵌入式系统中:第一,嵌入式系统电源电压的不稳定以及突发性断电将对FLASH的存储造成灾难性的影响,通用文件系统在这一点考虑不足;第二,通用文件系统大量使用缓存技术来提高文件系统的速度特性,通常耗费比较多的系统资源。这些都与嵌入式系统中系统资源有限,FLASH存储器又相对于磁盘驱动器较快的应用情况不同。
FLASH存储器的存储特性同一般存储介质也不同,它的读操作与普通的SRAM存储器类似,可以实现完全随机的字节读取,但是它的写操作较为特殊,需要经过“擦除—写入”两个操作过程。当对FLASH存储器的某一个单元进行写操作的时候,首先必须对这个存储单元所在的AREA(我们这里使用的FLASH一次擦除AREA的大小是64 kb)执行擦除操作,擦除操作成功完成后,整个AREA的数据内容都被清空(一般被设置成0xFF);然后执行写入操作,一次性的写入数据。当需要对文件进行数据的修改,追加,删除时,都需要重复以上的两个过程。此外,FLASH存储器数据传输中的时间瓶颈不在于读操作,而在于内部的擦写操作上。因此基于FLASH的嵌入式文件系统除了要提供数据管理(数据的读取,修改,存储,删除等)之外,还要最大限度的保证文件的数据有效性,同时针对FLASH的特性提供I/O的支持,从系统角度根据数据吞吐量,从用户角度根据响应时间,优化性能,减少或消除丢失、破坏数据的可能性,提供标准的接口供应用程序调用。
下面我们就嵌入式操作系统ASIXOS中的文件系统来谈一谈嵌入式手持移动终端中文件系统的实现。
1 ASIXOS中文件系统的结构
ASIXOS中的文件系统划分为六个较为独立的功能模块,各个模块之间的具体关系如图1所示。图中自顶向下显示了模块之间的调用关系。

用户文件接口模块将整个文件系统封装成为类似ANSIC的接口函数,提供了对创建文件,打开文件,关闭文件,删除文件,创建目录,打开目录(查看目录中的文件和下一级子目录)、删除目录,读文件,写文件,文件读写指针偏移的支持。
FILE-Buffer层模块维护一个在RAM中的FILE-S结构体的数组,用来保存已打开的文件信息,数组的大小就是当前文件系统中支持同时打开文件的最大数目。在实际系统中我们用宏定义了数组的大小,便于今后修改。
FILE层模块负责所有文件I/O的打开和终止,维护设备的输入输出,调度,修改文件当前状态等工作。
FCB层模块处理主存与辅存系统之间的数据交换,提供一整套文件的FCB在FLASH和RAM之间的映射关系。所谓FCB就是file control block的缩写。
Block层模块完成在辅存中动态的申请和释放Block块的工作,同时维护BlockBitmap表。这张表记录了当前Block块使用和空闲状态。
I/O缓冲层模块直接调用驱动的读写函数,完成FLASH和RAM中数据的交互。为了增强FLASH文件系统在不同FLASH存储器上的移植能力,设计时把它独立出来。
2 文件系统基本功能的实现
从基本功能的角度来说,文件系统首先要能够实现存储空间管理,文件管理,目录管理以及多任务的支持。以下就是ASIXOS中文件系统几种功能的实现方式。
① 存储空间管理
同大多数文件系统一样,我们把文件划分为固定大小的块(Block)来存储,各个块之间不必相邻。块大小是设计时要考虑的重要因素。块越小,每个块内碎片造成的空间的浪费就越小,FLASH存储信息的效率就越高。但是时间和空间永远是一对矛盾体,随着块变小,读取文件的时间也会增加。考虑到嵌入式系统中文件的大小不会太大,初步确定每个块的大小在64~2 048 byte之间。在I/O缓冲模块完成构建后,通过进行效率方面的模拟测试,考虑到时空效率上的折中,我们用宏定义块的大小是512byte。这样我们就达到了前面提到的从系统角度根据数据吞吐量,从用户角度根据相应时间,优化性能的目的。
一旦确定了块大小,文件系统还需要能够记录块的使用情况,以便在出现新文件和现有文件增长时知道可以使用哪些块。在这里我们使用了向量位表,每一位上用1表示空闲,用0表示使用。这样对于每次擦写64 kb,每个块大小512 byte的FLASH来说,只需要128 bit,即16 byte就可以清楚记录当前所有块的使用情况。
② 文件管理
实现文件管理,最重要的一个问题是如何记录具体的文件使用到哪些块,以及这些块之间的顺序。以往的文件系统多采用:连续分配,链接表分配,使用索引的链接表分配,i节点等方式来解决。例如,MSDOS中是使用索引的链接表分配,UNIX中是i节点。
针对嵌入式系统的特点,我们汲取了UNIX中i节点的设计思想,并简化它,设计了主(辅)文件控制块的分配方式,具体结构如图2所示。它不但清楚的记录了文件数据块的地址及其之间的顺序,同时可以方便的进行文件控制块之间的索引。

采用以上的结构,对于尺寸超出主文件控制块记录范围的文件,可以很方便的进行管理,如图3所示。

③ 目录管理
对文件操作前,系统首先要利用用户给出的路径找到相应的目录,在目录中查找文件的信息。有关目录的实现,我们也借鉴了文件的实现思想。文件系统中的每个目录控制块占512 byte,存放目录控制信息以及目录子项的标识信息。目录控制信息借用了主文件控制块结构描述,目录子项的标识信息就是下一级子目录和文件的信息。具体结构如图4、图5所示。

从上面的描述,可以很清楚的看出已经实现了目录树的结构。

④ 多任务的实现
在每一个任务控制块中加上T-FSCB结构体,对每个任务所能打开的文件个数作出限制。这样在创建任务的时候,对T-FSCB结构体进行初始化,全部为0。在结构体T-FSCB中定义了T-FSCB-ITEM类型的数组,数组的大小就是每个任务所能打开的文件个数,同时定义了一个int型的计数器opened-num,记录此任务当前打开的文件个数。在结构体T-FSCB-ITEM中,定义了两个unsigned int型的变量,fp用来记录文件的FILE-S,curp用来记录文件的读写指针的位置。这样设计使得文件的读写位置只和具体任务相关,保证了每个文件可被多个任务打开,也就达到了一开始提出的文件可以被多个应用程序并发的使用的目的。
具体的结构体定义如下:

具体到每一个文件,在删除文件或目录,打开文件,创建文件或目录,关闭文件,读写文件的时候都会加上锁机制,使得同一个文件在同一时刻只会被一个任务修改。
3 针对FLASH的优化设计
考虑到FLASH存储器的特殊读写特性,ASIXOS的文件系统做了特殊优化,在主存中动态的开辟一段空间作为缓冲区,将被打开的文件映射到主存中,同时在主存中构造写缓冲的链表,存放待写入的数据。这样做可以避免频繁擦写FLASH存储器,节约系统的响应时间,延长FLASH存储器的使用寿命。针对这种优化,我们设计了下面几个概念:
① 文件句柄(FILE-S)的概念
对应ANSIC中的FILE结构,我们构造了类似的FILE-S结构,也就是文件的基本信息数据。其中存放:文件的状态标志;文件的判断魔术数;指向主文件控制块的指针;指向主存的写缓冲区的双向链表首结点;文件的绝对路径;以及记录文件当前被打开次数的任务计数器。
这样通过这个FILE-S结构就可以清楚的指向文件在主存中的映像以及待写入数据的在写缓冲区的位置。
② AREA的概念
针对嵌入式系统中可能出现的不同物理存储介质(诸如Flash存储器,IC卡),我们首先设计了AREA-DSP-S的结构,如图6所示。

光有这些还不够,每个AREA的第一个块里还存放着一张向量位表(BlockBitmap),记录该AREA中每个块的使用情况,以便于进行块的分配和释放。针对AREA的概念,我们设计AREA-S的结构,如图7所示。

每次新修改的Block被加入写缓存的链表中去,直到新修改的块数(nblknum)超过在结构体中预定义的max-nblknum时,写缓存链表上的数据将一次性的写入到该AREA对应的物理存储设备中,同时清空写缓存链表,这样做既提高了安全性,相当于自动保存,减少了突发事件(诸如断电)带来的损失,达到了前面提出的减少丢失数据的可能,也提高了存储在时间上的效率。当然,在用户选择关闭(保存)文件时,写缓存链表上的数据也能立刻写到该AREA对应的物理存储设备中,同时清空写缓存链表。
③ 分区(PARTITION)的概念
为了减少对某一个文件修改时,引起擦写多个AREA的情况,我们设计了分区的概念,使得每一个文件的物理存储位置集中在一个分区内部,每一个分区都是AREA的整数倍。对于小文件,它们可以共用一个AREA,这时一个AREA就是一个分区。对于大文件,它可以独占若干个AREA,这几个AREA共同组成一个分区。一个隐含的条件就是应用程序都是我们自己写的,我们可以确定相应文件的大小,初始化好每个分区的大小。这样处理的好处是一方面节约了主存中缓冲区的大小,另一方面节约了擦除写入的时间,再次提高了系统的响应时间,优化了系统的性能。
4 展 望
这套文件系统已经得到应用,和上层的数据库系统实现对接。当然还有不够完善的地方,例如目录设计上只能向下搜索,不能回溯(缺少回指);和上层应用的联系太紧密,要事先分区等。这些问题相信会在后续的版本中得到改善。




