主要介绍专门为小型的嵌入式系统而设计的基于FAT32的fatfs文件系统,。它完全用标准C 语言编写,可以轻松移植到8051、SH、 Z80、H8 和ARM 等系列单片机上。
前言
FAT结构和原理不会细讲,具体可以参考这一篇详解FAT32文件系统
本文的文件系统会裁剪掉LFN,多分区,PAGE CODE等,源码解析的主要是核心源码,源码都有很详细的注释
原理
一 FAT文件系统的组织形式
MBR:存储各个分区信息。如一共由多少个分区,分区有多大,起始地址多少等。
DBR:存储当前分区信息。如FAT表的的大小、簇大小、扇区大小等等
如下是FATfs中定义的DBR扇区(0-512字节)各字节数组所代表的意义。
FAT1/FAT2:都是文件分配表,其中FAT2是1的备份。FAT将数据用蔟来划分,文件分配表的每一项都对应一个蔟。若文件分配表的项用16字节表示,则称为FAT16,用32字节表示则称为FAT32。一个蔟可以由多个扇区构成,对于小文件来说,一个文件就是一个蔟,而对于大文件,一个文件,由多个蔟相连构成蔟链,若文件结束,则分配表中存储0x 0F FF FF FF,否则存储下一个表项的序号。下图是简单的小文件映射关系。
数据区:由根目录,子目录和文件数据构成。其中0,1号蔟由系统使用,2号簇被分配给根目录使用,广义上讲,目录也是文件,是用来记录文件的特殊文件。
文件系统分层结构
FATfs为了可移植,有较好的分层结构
底层:就是diskio.c,由于底层硬件的不同,需要程序员自己实现对硬件的读,写,初始化等操作。
DSTATUS disk_initialize (BYTE pdrv);//c初始化
DSTATUS disk_status (BYTE pdrv);//状态
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);//读
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);写
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);//杂项
中间层:就是 ff.c,文件系统的实现
应用层:就是文件系统对外的接口f_open()等,实现各种应用。
FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
FRESULT f_close (FIL* fp); /* Close an open file object */
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from a file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to a file */
一些宏的简单介绍
获取文件大小,文件错误等
子类型标识
#define FS_FAT12 1
#define FS_FAT16 2
#define FS_FAT32 3
选择字访问还是字节(==1)访问数据。
比如存储 0x 00 11 22 33 44 ptr指向00
LD_WORD(ptr) 则得到 00
LD_WORD(ptr) 则得到 11 00
#if _WORD_ACCESS == 1 /* Enable word access to the FAT structure */
#define LD_WORD(ptr) (WORD)(*(WORD*)(BYTE*)(ptr))
#define LD_DWORD(ptr) (DWORD)(*(DWORD*)(BYTE*)(ptr))
#define ST_WORD(ptr,val) *(WORD*)(BYTE*)(ptr)=(WORD)(val)
#define ST_DWORD(ptr,val) *(DWORD*)(BYTE*)(ptr)=(DWORD)(val)
#else /* Use byte-by-byte access to the FAT structure */
#define LD_WORD(ptr) (WORD)(((WORD)*((BYTE*)(ptr)+1)<<8)|(WORD)*(BYTE*)(ptr))
#define LD_DWORD(ptr) (DWORD)(((DWORD)*((BYTE*)(ptr)+3)<<24)|((DWORD)*((BYTE*)(ptr)+2)<<16)|((WORD)*((BYTE*)(ptr)+1)<<8)|*(BYTE*)(ptr)) 44 33 22 11
#define ST_WORD(ptr,val) *(BYTE*)(ptr)=(BYTE)(val); *((BYTE*)(ptr)+1)=(BYTE)((WORD)(val)>>8) 11 22
#define ST_DWORD(ptr,val) *(BYTE*)(ptr)=(BYTE)(val); *((BYTE*)(ptr)+1)=(BYTE)((WORD)(val)>>8); *((BYTE*)(ptr)+2)=(BYTE)((DWORD)(val)>>16); *((BYTE*)(ptr)+3)=(BYTE)((DWORD)(val)>>24)
#endif
重要结构
FATFS
FATFS 结构体,主要记录DBR和目录的数据,通过该结构体中对扇区,簇,表等的大小,地址的定义,就能轻松访问磁盘的任意地址。下面每个成员都有作用的注释。
FAT子类型:就是FAT16还是FAT32,通过该位判断是否挂载(==0)
win缓冲区的标识符,代表win缓冲区是否正在使用,当为1时候,需要读取新扇区,就需要先刷新win缓冲区(写入旧数据到磁盘)
win缓冲区:它是一个文件系统公共的缓冲区,比如读取DBR,MBR或者FAT分配表也或是文件的信息甚至目录等都需要它读取一个扇区,进行操作。
typedef struct {
BYTE fs_type;//FAT子类型 /* FAT sub-type (0:Not mounted) */
BYTE drv;//物理设备号 /* Physical drive number */
BYTE csize;//每个簇的扇区数量 /* Sectors per cluster (1,2,4...128) */
BYTE n_fats;//fat表的个数 /* Number of FAT copies (1 or 2) */
BYTE wflag;//win缓冲区的标识符 /* win[] flag (b0:dirty) */
BYTE fsi_flag;//FSINFO标识 /* FSINFO flags (b7:disabled, b0:dirty) */
WORD id;//文件系统装载id /* File system mount ID */
WORD n_rootdir;//¸根目录的目录项项数 /* Number of root directory entries (FAT12/16) */
#if _MAX_SS != _MIN_SS
WORD ssize;//每个扇区的字节数 /* Bytes per sector (512, 1024, 2048 or 4096) */
#endif
#if _FS_REENTRANT
_SYNC_t sobj; //同步对象的标识符 /* Identifier of sync object */
#endif
#if !_FS_READONLY
DWORD last_clust;//上次分配的蔟 /* Last allocated cluster */
DWORD free_clust;//空闲的簇数量 /* Number of free clusters */
#endif
#if _FS_RPATH
DWORD cdir;//当前目录的开始簇 /* Current directory start cluster (0:root) */
#endif
DWORD n_fatent;//FAT项的数目 /* Number of FAT entries (= number of clusters + 2) */
DWORD fsize;//文件分配表的扇区数 /* Sectors per FAT */
DWORD volbase;//卷的开始扇区 /* Volume start sector */
DWORD fatbase;//FAT的开始扇区 /* FAT start sector */
DWORD dirbase;//¸根目录的开始扇区 /* Root directory start sector (FAT32:Cluster#) */
DWORD database;//数据区的开始扇区 /* Data start sector */
DWORD winsect;//保存再win中的当前扇区地址 /* Current sector appearing in the win[] */
BYTE win[_MAX_SS];//win缓冲区 /* Disk access window for Directory, FAT (and file data at tiny cfg) */
} FATFS;
FIL
该结构保存一个文件的详细信息,通过簇,扇区等地址,以及文件读写指针就能知道文件再哪里,读到了哪里。
fs:指向对应的文件系统对象指针,当一个扇区读取完成后,方便查找下一个簇等
文件状态标记:
/* File access control and file status flags (FIL.flag) */
#define FA_READ 0x01
#define FA_OPEN_EXISTING 0x00
#if !_FS_READONLY
#define FA_WRITE 0x02
#define FA_CREATE_NEW 0x04
#define FA_CREATE_ALWAYS 0x08
#define FA_OPEN_ALWAYS 0x10
#define FA__WRITTEN 0x20
#define FA__DIRTY 0x40
#endif
buff:某个文件的专用缓冲区,用来读取一个扇区的数据,进行偏移得到需要的数据,也预存了数据,减少读写次数。
typedef struct {
FATFS* fs;//指向相关文件系统对象的指针 /* Pointer to the related file system object (**do not change order**) */
WORD id;//所有者文件系统装载ID /* Owner file system mount ID (**do not change order**) */
BYTE flag;//文件状态标记 /* File status flags */
BYTE err;//异常终止标记 /* Abort flag (error code) */
DWORD fptr;//文件读写指针 /* File read/write pointer (Zeroed on file open) */
DWORD fsize;//文件大小 /* File size */
DWORD sclust;//文件数据开始簇 /* File data start cluster (0:no data cluster, always 0 when fsize is 0) */
DWORD clust;//当前读写指针的簇 /* Current cluster of fpter */
DWORD dsect;//当前读写指针的扇区 /* Current data sector of fpter */
#if !_FS_READONLY
DWORD dir_sect;//包含目录项的扇区 /* Sector containing the directory entry */
BYTE* dir_ptr;//目录项再WIn中的入口地址· /* Pointer to the directory entry in the window */
#endif
#if _USE_FASTSEEK
DWORD* cltbl;//蔟链指针 /* Pointer to the cluster link map table (Nulled on file open) */
#endif
#if _FS_LOCK
UINT lockid;//文件锁id /* File lock ID (index of file semaphore table Files[]) */
#endif
#if !_FS_TINY
BYTE buf[_MAX_SS];//文件读写缓冲区 /* File data read/write buffer */
#endif
} FIL;
DIR
主要保存目录的数据,当前目录的地址以及某个文件再目录中的地址
typedef struct {
FATFS* fs;//指向所有者文件系统的指针 /* Pointer to the owner file system object (**do not change order**) */
WORD id;//文件系统挂载的id /* Owner file system mount ID (**do not change order**) */
WORD index;//当前索引指针 /* Current read/write index number */
DWORD sclust;//目录开始簇号 /* Table start cluster (0:Root dir) */
DWORD clust;//当前簇号 /* Current cluster */
DWORD sect;//当前扇区地址 /* Current sector */
BYTE* dir;//指向win中的SFN的指针 /* Pointer to the current SFN entry in the win[] */
BYTE* fn; //指向sfn的指针 /* Pointer to the SFN (in/out) {file[8],ext[3],status[1]} */
#if _FS_LOCK
UINT lockid;//文件锁号 /* File lock ID (index of file semaphore table Files[]) */
#endif
#if _USE_LFN
WCHAR* lfn;//指向lfn的工作缓冲区 /* Pointer to the LFN working buffer */
WORD lfn_idx;//上次匹配的LFN索引号 /* Last matched LFN index number (0xFFFF:No LFN) */
#endif
} DIR;
FILINFO
用来存储文件的属性
typedef struct {
DWORD fsize;//文件大小 /* File size */
WORD fdate;//日期 /* Last modified date */
WORD ftime;//时间 /* Last modified time */
BYTE fattrib;//属性 /* Attribute */
TCHAR fname[13];//短文件名 /* Short file name (8.3 format) */
#if _USE_LFN
TCHAR* lfname;//LFN指针 /* Pointer to the LFN buffer */
UINT lfsize;//³LFN长度 /* Size of LFN buffer in TCHAR */
#endif
} FILINFO;
函数整体逻辑
采用函数调用缩进结构,相对一个函数缩进代表为该函数内的操作。
创建文件系统
CreateFatFs()是应用层写的,对文件系统的封装,用于创建一个文件系统
首先通过写好 的内存分配,开辟公共的文件系统对象FS[0],只有一个0,代表逻辑驱动器只有1个,id为0,对应的也只能挂载一个物理设备,通常逻辑设备和物理设备的ID应该相同。然后用f_mount挂载,通过get_ldnumber()搜索逻辑ID,也就是0,然后讲FATFS[0]这个全局指针执行该文件系统对象。期间,可以通过find_volume()强制安装卷,也可以再f_open哪里安装。接着可以调用f_mkfs()创建文件系统,主要功能是通过disk_read()读取0扇区存储在win缓冲区中,通过判断win中的数据进行文件对象的填充和错误返回等再将计算好的值写入磁盘。
CreateFatFs()
fs[0]=(FATFS*)mymalloc(SRAMIN,sizeof(FATFS));
f_mount(fs[0],"0:",0)挂载逻辑设备
get_ldnumber("0:");从路径获得逻辑ID-- 0,也就是FatFs下标
cfs = FatFs[0];清空老指针和新指针文件对象类型
FatFs[0] = fs[0];将开辟的对象注册
find_volume(fs[0],"0:", 0);可以强制安装卷
f_mkfs("0:",0, 4096);在逻辑设备上创建文件系统
get_ldnumber("0:");从路径获得逻辑ID-- 0,也就是FatFs下标
FatFs[vol] = fs[0];将开辟的对象注册
disk_initialize(0);//获得物理磁盘状态,逻辑/物理一一对应
是否支持多分区,是否自动分区
计算每个蔟的扇区数 4096/512;8扇区一个蔟
disk_read();
计算确定FAT类型
disk_write()写入
确定FAT结构和偏移量
disk_write()写入
对齐数据
disk_write()写入
确定蔟数量检查FAT
disk_write()写入
确定分区表系统ID
disk_write()写入
创建BPB等
disk_write()写入
初始化FAT区域
disk_write()写入
初始化根目录
disk_write()写入
putdata是自己写的写入一个文件,运行文件系统的逻辑结构
putData(“0:/1 ”)
DIR dj;
f_open(&fil, “0:/1”, FA_OPEN_ALWAYS|FA_WRITE|FA_READ);
find_volume(&dj.fs,“0:/1”, 0);查找逻辑驱动器是否装载并装载
get_ldnumber(“0:/1”);从路径获得逻辑ID-- 0
fs = FatFs[0];
通过fs->fs_type判断卷0是否装载
##若装载
disk_status(fs->drv)//传入物理设备号判断物理设备状态
##若未装载,尝试装载
fs->fs_type = 0;清空文件系统对象类型
disk_initialize(fs->drv)查看硬盘初始化状态
check_fs(fs, 0);加载0扇区并核实0扇区是否为FAT引导扇区
初始化fs->win的标识符
move_window(fs, 0)将0扇区数据读到fs->win[]中
LD_WORD(&fs->win[BS_55AA]) != 0xAA55根据读的数据对应值判断是否是引导扇区
##不是引导扇区
## 强制分区
## return
##是引导扇区
判断引导分区是否正常
fs->n_fats = fs->win[BPB_NumFATs];更具引导分区数据对文件系统对象进行初始化
fs 文件系统初始化完成,返回
为DIR对象dj开辟空间
follow_path(dj,"0:/1")追踪路径
##如果路径小于' '
dir_sdi(dj, 0);设置目录索引
##静态表
##动态表
get_fat(dj->fs, 根目录的开始扇区)循环蔟链获得FAT项的值
##子类型==FAT12
move_window()
返回对应的蔟的值(从2开始,蔟链为下一个蔟号)
判断蔟链是否正确,修改当前蔟号,当前扇区,sfn指针等
##如果路径>=' '
create_name(dj,"0:/1")
##LFN
##SFN
初始化dj->fn
return
dir_find("0:/1")在目录中查找对象
dir_sdi(dp, 0);设置目录索引
##静态表
##动态表
get_fat(dj->fs, 根目录的开始扇区)循环蔟链获得FAT项的值
##子类型==FAT12
move_window()
返回对应的蔟的值(从2开始,蔟链为下一个蔟号)
##while
move_window(dj->fs, dj->sect)
c = dir[DIR_Name];//获取文件名
dir_next()将目录表移动到下一个
##静态表
##动态表
get_fat(dj->fs, 根目录的开始扇区)循环蔟链获得FAT项的值
设置当前索引,win中当前项
循环结束
##如果没有发现目录
设置返回值 返回
##如果发现目录
ld_clust(dj->fs, dj->dir)加载存储目录开始蔟号
##如果创建
##如果没有文件
##如果不是文件
dir_register(&dj);在目录中注册一个对象
dir_alloc()保留目录项
dir_sdi(dp, 0);
##while
move_window()
dir_next(dp, 1);
##如果有文件
move_window()
mem_cpy(dp->dir, dp->fn, 11);
win[]标识置1
## 如果要覆盖文件
##打开存在的文件
设置目录对象成员
f_lseek()移动指针
f_write()写数据
validate(fp);核实文件系统是否有效
循环写
getdata是自己写的读取一个文件,运行文件系统的逻辑结构
getdata()
f_opendir(&dir,(const TCHAR*)"0:");创建目录对象
find_volume(&fs, "0:", 0)查找逻辑驱动器,并核实是否装载
get_ldnumber(“0:”);从路径获得逻辑ID-- 0
fs = FatFs[0];
通过fs->fs_type判断卷0是否装载
##若装载
disk_status(fs->drv)//传入物理设备号判断物理设备状态
##若未装载,尝试装载
fs->fs_type = 0;清空文件系统对象类型
disk_initialize(fs->drv)查看硬盘初始化状态
check_fs(fs, 0);加载0扇区并核实0扇区是否为FAT引导扇区
初始化fs->win的标识符
move_window(fs, 0)将0扇区数据读到fs->win[]中
LD_WORD(&fs->win[BS_55AA]) != 0xAA55根据读的数据对应值判断是否是引导扇区
##不是引导扇区
## 强制分区
## return
##是引导扇区
判断引导分区是否正常
fs->n_fats = fs->win[BPB_NumFATs];更具引导分区数据对文件系统对象进行初始化
fs 文件系统初始化完成,返回
为DIR对象dj开辟空间
follow_path(dj,"0:")追踪路径
##如果路径小于' '
dir_sdi(dj, 0);设置目录索引
##静态表
##动态表
get_fat(dj->fs, 根目录的开始扇区)循环蔟链获得FAT项的值
##子类型==FAT12
move_window()
返回对应的蔟的值(从2开始,蔟链为下一个蔟号)
判断蔟链是否正确,修改当前蔟号,当前扇区,sfn指针等
##如果路径>=' '
create_name(dj,"0:")
##LFN
##SFN
初始化dj->fn
return
dir_find("0:")在目录中查找对象
dir_sdi(dp, 0);设置目录索引
##静态表
##动态表
get_fat(dj->fs, 根目录的开始扇区)循环蔟链获得FAT项的值
##子类型==FAT12
move_window()
返回对应的蔟的值(从2开始,蔟链为下一个蔟号)
##while
move_window(dj->fs, dj->sect)
c = dir[DIR_Name];//获取文件名
dir_next()将目录表移动到下一个
##静态表
##动态表
get_fat(dj->fs, 根目录的开始扇区)循环蔟链获得FAT项的值
设置当前索引,win中当前项
循环结束
##如果没有发现目录
设置返回值 返回
##如果发现目录
ld_clust(dj->fs, dj->dir)加载存储目录开始蔟号
##如果创建
##如果没有文件
##如果不是文件
dir_register(&dj);在目录中注册一个对象
dir_alloc()保留目录项
dir_sdi(dp, 0);
##while
move_window()
dir_next(dp, 1);
##如果有文件
move_window()
mem_cpy(dp->dir, dp->fn, 11);
win[]标识置1
## 如果要覆盖文件
##打开存在的文件
设置目录对象成员
##如果是文件
##如果是子目录
f_readdir(&dir, &fileinfo)按顺序读取目录项
validate(dp)检查对象的有效性
dir_sdi(dp, 0)设置目录索引
dir_read(dp, 0)读取一个目录项
get_fileinfo(dp, fno)获得文件信息
dir_next()下一个索引
f_open()
。。。。。。。。
f_lseek(&fil, mReadSize)
validate(fp)核实文件是否是有效的
##快速seek
##自然seek
##seek失败
f_unlink()删除文件或者目录
find_volume(&dj.fs, &path, 1)查找逻辑驱动器并核实是否装载
follow_path(&dj, path);追踪路径
ld_clust(dj.fs, dir)加载存储开始蔟编号
f_read(&fil, (char*)package, sizeof(storage_package), &i);
validate(fp);文件或者目录是否有效
重复读取所有的数据
##在扇区边界上
##在蔟的边界上
##在文件的顶部
##文件的结尾或者中间
mem_cpy(rbuff, &fp->buf[fp->fptr % SS(fp->fs)], rcnt);选取部分扇区数据
f_close()
f _sync(fp);刷新缓冲区数据
validate(fp); 核实文件是否有效
fp->fs = 0; 关闭文件