文件系统:操作系统用于明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构;即在存储设备上组织文件的方法。操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。
简单来说,就是组织存储设备中的数据,使其不再成为分立,无意义的数据,而是组合起来形成一个个文件,以最简单的计算机为例,本质上显示在屏幕上的文件夹、文件在磁盘上都是1和0,但通过组织则形成了docx、exe等文件形式,并且不会和其它文件混淆,这就是文件系统的作用。
Windows下常见的文件系统格式包括FAT32、NTFS、exFAT等,下面主要介绍FatFs文件系统。
FatFs文件系统是一种面向小型嵌入式系统的通用FAT文件系统。该系统完全由ANSIC语言(即标准C语言)编写并且完全独立于底层的I/O介质,可以很容易地移植到其他的处理器。FatFs文件系统支持多个存储媒介,具有独立的缓冲区,可以读写多个文件,并且该系统特别根据8位和16位微控制器进行了优化。文件系统实际上有点类似NAND Flash管理中的FTL层和内存管理中的内存管理表,当然文件系统的实际内容更加复杂,为了方便使用,一般只需要为其提供相应的接口函数,即可使用文件系统。
下面以SD卡作为存储设备,介绍基于SD卡文件系统的使用。
从文件系统的使用上来说,文件系统实际上就是用于管理存储块的程序,该程序包括但不限于ff.c、diskio.c等文件,这些文件组成了FatFs组件,并为应用程序提供控制文件的函数。
文件系统向上提供控制函数的同时,向下通过相应函数完成对存储块的读写,这部分读写函数则需要用户提供,作为接口,如下图的sd_block_read函数即是向SD卡写入数据的函数。
关于FatFs组件的各个文件描述如下:
与内存管理中的内存管理表一样,文件系统同样会形成1个表来管理各个文件,当然这里的表不再是简单的数组,而是在存储器中形成文件分配表以及目录,如下图所示,文件分配表用于记录各个文件的存储位置。目录用于记录文件系统中各个文件的开始簇和文件大小等文件信息。A.TXT、B.TXT和C.TXT等为文件。
其中“簇”是文件系统操作的最小单位,每个文件至少占用一个簇的空间。簇的大小在格式化时被确定,“簇”越小,读写速度越慢;“簇”越大,读写速度越快,但对于小文件来说存在浪费空间的问题(所以特殊情况下需要对“簇”的大小进行权衡)。FatFs文件系统通常使用的“簇”的大小为4KB。
文件分配表的内容如下图所示,用于记录各个文件的存储位置,其中第一行为对应的簇号,第二行为该文件存储的下一簇的簇号,当第二行为FF时表示对应文件存储已经结束。对照上图可得,簇号为1的存储空间存放着目录,簇号2 ~ 11存储着1个文件,簇号12 ~ 15、66 ~ 86同,而簇号87 ~ 99则未用于存储(第二行为00)。
目录的内容如下图所示,用于记录文件系统中各个文件的开始簇和文件大小等文件信息。以下图为例,每个文件都需要100字节的空间存储相应的信息,即0.1KB。
没错,本文介绍的是文件系统的移植步骤而不是搭建步骤,文件系统实际上是由十分复杂的程序构成,不是一朝一夕能吃透、搭建出来的,庆幸的是已经有大佬创造并分享了十分容易移植的文件系统,文件系统的下载可通过FatFs,该网址中除了FatFs的各个版本外,还有其基础知识、用户论坛以及用于FatFs入门的程序节点等,并且还有各个文件操作函数(f_open、f_read等)的解释,在移植完文件系统后可以通过相应函数进行操作文件。接下来介绍文件系统的移植步骤。
路径添加不用多说,几乎FatFs文件夹及子文件夹都需要加进去,源文件(.c文件)只需要添加diskio.c和ff.c文件即可。
#ifndef _FFCONF
…
#define _USE_MKFS 1 /*设置 _USE_MKFS为1以使能f_mkfs()函数*/
…
#define _CODE_PAGE 936 /*设置_CODE_PAGE为936以使用中文编码而不是932日文编码*/
…
#define _VOLUMES 3 /*支持3个盘符*/
…
#define _FS_LOCK 3 /*设置_FS_LOCK为3,支持同时打开3个文件*/
…
#define _WORD_ACCESS 1 /*设置_WORD_ACCESS为1,支持WORD*/
…
#include "diskio.h"
#endif
需要完善的底层设备驱动函数包括设备状态获取函数(disk_status)、设备初始化函数(disk_initialize)、扇区读取函数(disk_read)、扇区写入函数(disk_write)和其他控制函数(disk_ioctl),这些函数用于文件系统向下完成对存储块的读写,需要读者实现。
通过上述步骤即可完成对文件系统的移植,在程序运行时通过f_mount函数即可为相应存储介质挂载文件系统,并通过f_open、f_close和f_read等函数操作文件。
与文件系统操作有关的函数约有40个,具体可参考FatFs中的内容,下面简单介绍其中的几个函数。
f_mount函数用于为存储介质挂载一个文件系统,文件系统的挂载是指将这个文件系统放在全局文件系统树的某个目录下,完成挂载后才能访问文件系统中的文件,该函数的参数及解释如下。
FRESULT f_mount (
FATFS* fs , /* [IN] 文件系统对象 */
const TCHAR* path , /* [IN] 逻辑驱动器号 */
BYTE opt /* [IN] 初始化选项 */
);
SD卡挂载文件系统示例:其中FS_VOLUME_SD在diskio.h中定义,1表示强制挂载以检查其是否准备好工作。
FATFS fs;
f_mount(&fs, FS_VOLUME_SD, 1);
f_open函数用于打开或创建一个文件,该函数的参数及解释如下。
FRESULT f_open (
FIL* fp, /* [OUT] 指向文件对象结构的指针 */
const TCHAR* path, /* [IN] 文件名 */
BYTE mode /* [IN] 模式标志 */
);
其中fp为FTL类型的指针变量,后续对该文件的操作都通过该指针。path为包含文件名的完整路径。mode为对该文件的操作模式,在ff.h文件中定义,各个操作模式的作用如下:
SD卡打开文件示例:创建a.txt文件并开启其读权限,若该文件已创建则返回FR_EXIST。
FIL fil; /* 文件对象 */
f_open(&fil, "0:/a.txt", FA_CREATE_NEW | FA_READ);
f_close函数用于关闭已打开的文件,该函数的参数及解释如下。
FRESULT f_close (
FIL* fp /* [IN] 指向文件对象的指针 */
);
其中fp为FTL类型的指针变量,即f_open函数打开文件时使用的指针。
f_write函数用于向文件写入相应长度的数据,该函数的参数及解释如下。
FRESULT f_write (
FIL* fp, /* [IN] 指向文件对象结构的指针 */
const void* buff, /* [IN] 指向要写入的数据的指针 */
UINT btw, /* [IN] 要写入的字节数 */
UINT* bw /* [OUT] 指向变量的指针,返回写入的字节数 */
);
其中fp为FTL类型的指针变量,即f_open函数打开文件时使用的指针。
f_readf函数用于向文件写入相应长度的数据,该函数的参数及解释如下。
FRESULT f_read (
FIL* fp, /* [IN] 指向文件对象结构的指针 */
void* buff, /* [IN] 存储读取数据的缓冲区 */
UINT btr, /* [IN] 要读取的字节数 */
UINT* br /* [OUT] 指向变量的指针,返回读取的字节数 */
);
其中fp为FTL类型的指针变量,即f_open函数打开文件时使用的指针。