FLASH日志文件系统设计

写在前面

大家都知道,目前主要流利的日志文件系统有JFFS,YAFFS等,这些都是目前在Linux中应用较多的日志文件系统。前期在做嵌入式方面的开发工作时,发现原来同事使用FLASH存储数据时使用了非常多的全局变量来保存FLASH的状态,同时在数据写入、读取和删除代码中又引用了大量的代码,感觉很乱。当时想,难道在ARM7上就没有一个好用的FLASH文件系统吧,当时也有人研究FAT32,但基本上是应用于SD卡的。其实操作FLASH并写入想要写的数据,并不难,所以这方面的封装库不多见,也属正常。再者,由于转入到编写嵌入式代码工作时间也不长,因此对于此行业的状态也了解不多。作为监控领域的嵌入式行业,最重要的两点就是数据采集和数据历史存储。数据采集与监控的目标对象有关,数据历史存储,目前在低端的ARM7或者Cortex-M3系统的架构上,基本上使用外部FLASH来做存储。随着目前芯片集成技术的推进,目前在一片CPU上也有好几十上百K的FLASH空间,这样一来,实现廉价的监控方案就方便了。然后,在软件行业,面向对象、封装已经风行多年,由于单片系统资源有限,在此领域内封装的库不多,但随着芯片制造技术的飞跃,32位单片机已经非常常见,而且便宜。因此在单片机系统上,进行封装开发也会越来越多。像目前Cortex-M3提供的外围设备库已经属于标配,因此在单片系统开发已经不是懂硬件人的专利了。当我看到外围设备库,看到原来混乱的数据处理代码,我决定对这些代码进行良好封装,以提高自己的开发效率。


FLASH日志文件系统在应用中需要解决的问题主要有两个:

1、FLASH平均擦写能力

2、FLASH在掉电或者异常时恢复问题

由于在单片系统(裸奔)中,大部分使用的都是NOR FLASH,此类FLASH可靠性高,因此NAND FLASH的坏块问题不多见,只要处理好基本的异常情况,基本上可敷使用。

然后,在使用FLASH存储数据时,需要解析的问题也有两个:

1、操作接口方便、抽象,无需考虑过多细节

2、异常时,不需要过多操心


基于自己对封装的理解,设计出了自己的FLASH日志文件系统,该系统功能有限,但对于单片系统而言,对数据的写入、读取和删除都可以很好的支持,最关键的就是支持裸奔、代码简洁(5K左右)。


设计 思路:

FLASH一般按扇区组织,最小的擦除单位也是扇区,因此此文件系统按扇区建立最小的操作单位。按一般文件系统设计理念,针对每扇区记录分区表,以索引记录。

FLASH日志文件系统设计_第1张图片

本文件系统将所有日志记录形成一个链表记录,即指定头和尾,即可找到记录位置,删除只能头删除,添加只在尾添加,如此可保证日志文件系统的有序性(是监控系统必须保证的)。如果将各扇区有组织的统一管理起来,需要在扇区中包含一部分索引信息,以方便初始化和操作。为保证数据的正确性,数据写入时,要求写入校验和。同时为支持不定长记录,数据写入时还包括数据的写入长度。

各扇区形成链表,需要在各扇区中保存链信息,由于数据的写入和读出,会导致链表信息更新,因此需要合理利用扇区的物理特性来记录各扇区的状态。

定义的扇区状态要如下:

FLASH日志文件系统设计_第2张图片

扇区分区由扇区头,扇区数据标识区和数据区三部分组成,前二部分构成扇区分区信息。

扇区区定义了扇区的必要信息,结构如下:

typedef struct _sector_lfs
{
	unsigned char flag; //标志字节:未初始化为:0xFFFF
	unsigned char type;
	unsigned short id;
	unsigned short logsize;
	unsigned short mapbytes;
}sector_lfs_t, *psector_lfs;
此结构为FLASH中结构标识,此结构为外部FLASH的标识,通过SPI总线的NOR FLASH具有写0有效的编程特性,因此利用此特性,可以将扇区的标志状态使用一字节表示。

type表示此文件的唯一标识,如果与文件描述的不相同时会重新初始化引扇区。

id暂时未使用

logsize表示此日志的最大记录大小

mapbytes为本扇区数据标识区占用的大小,此结构之后为数据标识区,数据标识区按扇区最大可容量量计算

FLASH日志文件系统设计_第3张图片

扇区数据标识区为位图表示方式,同时利用FLASH的物理特性,利用两个位表示一条记录的状态(写入,删除)。


日志文件表示结构:

// 扇区字节数  0x10000
#define SECTOR_SIZE 0x10000
// 扇区个数(支持最大扇区数65535)
#define SECTOR_CNT 	128

typedef struct _lfs
{
	unsigned int    start;   // 开始扇区号 , 范围范围为[start, end)
	unsigned int    end;     // 结束的扇区号
	unsigned char   type;    // 该文件系统的类型标识
	unsigned short  logsize; // 记录的日志最大大小
	         int    head;	   // 用于内部表示开始的头(写的位置),初始化时需start
			 int    tail;    // 用于内部表示开始的尾(读的位置)
	unsigned int    count;   // 可用日志记录条数
	unsigned int    rd_id;   // 当前读的头位置(扇区的ID位置)
}lfs_t;


通过指定扇区开始和结束号,就相当于指定了此文件可使用的扇区数量及位置,通过此结构可以在内存中表示一个文件,并实时反映了此文件的物理状态。


文件系统的加载

文件系统的加载过程是必须的步骤,在系统准备使用此日志文件前,必须进行文件加载。

通过指定lfs_t结构的start,end, type, logsize四个参数后,使用lfs_load即可加载此文件。加载文件的过程会扫描指定的扇区,并试图扫描扇区链表的前、尾,和记录数量。对于不属于此文件或者与扇区保留信息不相同的扇区时,将重新初始化此扇区。


文件系统的操作

文件系统的操作在文件系统加载之后,可以对记录进行读、写和删除。读可以从有效记录的任意位置进行读,写将在尾部写入。删除只能在链表头删除。


你可能感兴趣的:(日志文件系统设计,FLASH日志文件系统)