幻域竞界 - 全球网游活动速递站

首页 > 全球速报 > FATFS文件系统原理及其移植详解

FATFS文件系统原理及其移植详解

一、FATFS简介

FATFS 是一个完全免费开源的 FAT/exFAT 文件系统模块,专门为小型的嵌入式系统而设计。它完全用标准 C 语言(ANSI C C89)编写,所以具有良好的硬件平台独立性,只需做简单的修改就可以移植到 8051、PIC、AVR、ARM、Z80、RX 等系列单片机上。它支持 FATl2、FATl6 和 FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对 8 位单片机和 16 位单片机做了优化。

1. FAT文件系统布局

系统引导扇区:引导程序,以及文件系统信息(扇区字节数/每簇扇区数/保留扇区数等)。

文件分配表:记录文件存储中簇与簇之间连接的信息。

根目录:存在所有文件和子目录信息(文件名/文件夹名/创建时间/文件大小)。

数据区:文件等数据存放地方,占用大部分的磁盘空间。

FAT 文件系统用 "簇" 作为数据单元,一个 "簇" 由一组连续的扇区组成,而一个扇区的大小为 512 字节。所有的簇从 2 开始进行编号,每个簇都有自己的地址编号,用户文件和数据都存储在簇中。

2. 文件系统类型及选择

在了解了FatFS文件系统的基本概念和用途之后,接下来我们将深入探讨FatFS支持的不同文件系统类型及其选择依据。我们将分析FAT12、FAT16和FAT32文件系统的结构特点,并探讨如何根据不同的需求和资源情况选择合适的文件系统。此外,我们还将分析FatFS如何实现高内存效率以及如何保持其强移植性。

2.1 FatFS支持的文件系统类型

2.1.1 FAT12 简介

FAT12(File Allocation Table 12)是最早的文件系统类型,它在1981年随着IBM的个人计算机一起发布。FAT12主要被设计用于软盘驱动器,并支持小容量存储设备。由于其索引表项仅占12位 ,所以其最大支持的存储容量受到限制,通常不超过16MB。

FAT12的核心优势在于其简单性和广泛兼容性,使其适用于对容量要求不高的环境。然而,由于其存储效率低下和容量大小限制,它已被逐渐淘汰。

2.1.2 FAT16 简介

FAT16是对FAT12的改进,它在1984年被引入到MS-DOS 3.0中。FAT16的索引表项使用了16位 ,这大大增加了单个文件的最大大小以及文件系统的总容量,使其能够支持最大2GB的存储空间。

FAT16相比于FAT12的优势在于更大的文件和存储空间支持,使其能够用于更多类型的存储设备。它广泛应用于早期的硬盘、光盘、闪存驱动器等。FAT16的文件名采用8.3格式,即最多8个字符的文件名,加上3个字符的文件扩展名。

2.1.3 FAT32文件系统优势

FAT32是在FAT16的基础上进一步演进的文件系统,首次出现在Windows 95的OSR2版本中。FAT32将索引表项扩展到32位,不仅支持更大的单个文件大小和存储空间(最大32GB),而且还提供了更好的存储效率和更佳的性能。

FAT32的主要优势在于其兼容性、稳定性和广泛的应用。它支持长达255个字符的长文件名(VFAT),大大优于FAT16的8.3格式。由于其广泛的应用和良好的设备支持,FAT32适合各种嵌入式和消费类电子产品。

2.2 高内存效率的实现机制

2.2.1 内存优化策略

FatFS文件系统设计时,内存效率是一个重要的考量。系统采用多种内存优化策略,以减少对嵌入式设备有限内存资源的占用。

例如,为了优化内存使用,FatFS支持缓冲区的动态分配,允许文件系统根据需要分配和释放内存,而不是占用固定大小的内存。此外,FatFS的代码非常紧凑高效,以减少内存占用。对于那些运行在内存受限环境中的嵌入式设备来说,这是一项重要的优化。

2.2.2 内存使用分析

要深入了解FatFS的内存使用情况,可以参考内存映射机制。这涉及到了操作系统级别的内存管理,但是FatFS的实现允许我们对其使用的内存有一个概览。

例如,在一个典型的嵌入式系统中,FatFS会分配一块内存作为文件操作的缓冲区,这个区域的大小可以根据实际需要进行配置。这种机制允许FatFS在进行大文件读写操作时,将数据分块读入内存,减少一次性内存占用,避免内存溢出的风险。

在实际应用中,FatFS会根据文件系统的配置文件和实际应用场景,动态地分配和管理内存。

2.3 强移植性设计

为了实现高移植性,FatFS采取了多种策略:

首先,文件系统的大部分实现都是用C语言编写的,这确保了其能够在多种平台上编译和运行。

其次,FatFS提供了一系列抽象层,将硬件和平台相关的操作与核心文件系统逻辑分离开来。这使得移植到新平台变得简单,只需提供针对该平台的具体实现即可。

下面是基本移植过程流程图:

成功 失败 开始移植 配置ffconf.h 编写diskio.c 编译测试 移植完成 调试问题

如图所示,开发者首先需要配置 ffconf.h ,然后编写 diskio.c ,接着编译并测试文件系统。如果测试失败,则需要回到步骤C进行调试。

二、FATFS模块的层次结构

底层接口 (Low level device controls)

FatFs 通过硬件抽象层与具体的存储设备交互,即磁盘接口。支持多种存储设备媒介(SD 卡、USB、NAND、NOR Flash)。

提供各种存储媒介的读/写接口(disk_read()、disk_write())

提供给文件创建、修改时间的实时时钟,需要我们根据平台和存储介质编写移植代码。

缓存管理层

FatFs 会在 RAM 中保留一个扇区缓冲区,用于加速文件读写操作,将块设备的读写操作优化为扇区大小。

FATFS 文件系统层 (FatFs Module)

实现了 FAT 文件读/写协议,主要由ff.c 和 ff.h组成。使用者一般不用修改,使用时将头文件直接包含进去。

主要的数据结构

文件控制块(FIL):表示一个已打开的文件,包含文件的状态、文件的指针、当前位置等信息。

目录控制块(DIR):表示一个已打开的目录,包含目录的结构、当前读取位置等。

文件系统对象(FATFS):用于表示整个文件系统的状态,包括当前挂载的信息、文件系统类型、扇区大小等。

文件分配表(FAT):用于管理文件的簇链,标识文件占用了哪些簇,以及这些簇是否已被分配、空闲或损坏。

应用层 (Application)

使用者无需理会 FATFS 的内容结构和复杂的 FAT 协议,只需要调用 FATFS 模块提供给用户的一系列应用接口函数,如f_open,f_read,f_write和f_close等,这些函数用于用户操作文件和目录,如打开、读取、写入和关闭文件等,就像在 PC 机上读/写文件。

1. 应用层核心接口

1.1 文件和目录操作功能

FatFS 提供了一套函数库(接口),使开发者能够方便地在 FAT 文件系统上操作文件、目录,甚至是卷。

c

复制代码

/******************************************** 1.文件操作 *******************************************/

FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); // 打开/创建一个文件

FRESULT f_close (FIL* fp); // 关闭一个打开的文件

FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); // 从文件中读取数据

FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); // 往文件中写入数据

TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); // 读一个字符串

int f_putc (TCHAR c, FIL* fp) // 写一个字符

int f_puts (const TCHAR* str, FIL* cp); // 写一个字符串

int f_printf (FIL* fp, const TCHAR* str, ...); // 写一个格式化字符串

FRESULT f_lseek (FIL* fp, FSIZE_t ofs); // 文件指针定位

FRESULT f_truncate (FIL* fp); // 截断文件

FRESULT f_sync (FIL* fp); // 刷新文件

FRESULT f_stat (const TCHAR* path, FILINFO* fno); // 获取文件信息

FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); // 获取第一个文件

FRESULT f_findnext (DIR* dp, FILINFO* fno); // 获取下一个文件

FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt); // 扩展文件

FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); // 将数据转发到流

#define f_tell(fp) ((fp)->fptr) // 获取文件指针位置

#define f_size(fp) ((fp)->obj.objsize) // 获取文件大小

#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize)) // 判断文件是否到达文件末尾

#define f_rewind(fp) f_lseek((fp), 0) // 移动文件指针到文件开头

/******************************************** 2.目录操作 *******************************************/

FRESULT f_opendir (DIR* dp, const TCHAR* path); // 打开一个目录

FRESULT f_closedir (DIR* dp) // 关闭一个打开的目录

FRESULT f_readdir (DIR* dp, FILINFO* fno); // 读取目录条目

FRESULT f_mkdir (const TCHAR* path); // 创建一个目录

FRESULT f_unlink (const TCHAR* path); // 删除一个文件或目录

FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); // 重命名/移动一个文件或目录

FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); // 修改文件/目录属性

FRESULT f_utime (const TCHAR* path, const FILINFO* fno); // 更新文件/目录修改时间

FRESULT f_chdir (const TCHAR* path); // 改变当前目录

FRESULT f_getcwd (TCHAR* buff, UINT len); // 获取当前目录

#define f_rmdir(path) f_unlink(path) // 删除一个目录

#define f_rewinddir(dp) f_readdir((dp), 0) // 移动目录指针到目录开头

/****************************************** 3.卷管理操作 *****************************************/

FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); // 注册/注销一个工作区

FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len); // 格式化,创建一个文件系统

FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); // 获取磁盘信息以及空闲簇数量

FRESULT f_chdrive (const TCHAR* path) // 改变当前驱动器

FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); // 获取卷标

FRESULT f_setlabel (const TCHAR* label); // 设置卷标

FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); // 磁盘分区

FRESULT f_setcp (WORD cp); // 设置代码页

#define f_error(fp) ((fp)->err) // 获取错误代码

#define f_unmount(path) f_mount(0, path, 0) // 注销一个工作区

2. FAT模块层(ff.c)

在 FatFS 的设计中,硬件 I/O 操作与文件系统逻辑被完全分离开来,这是 FatFS 优秀的架构设计之一。这种设计使得 FatFS 可以在多种不同的硬件平台和存储介质上运行,而无需针对每一种硬件进行大量的修改。

硬件I/O抽象层为文件系统提供了一个统一的接口,这些接口包括初始化( disk_initialize )、读取扇区( disk_read )、写入扇区( disk_write )等。通过抽象层,我们可以更方便地将 FatFS 应用于不同的硬件平台,只需要按照 FatFS 所需的接口实现相应的硬件I/O函数即可。

例如,FAT为硬件读定义了如下接口,用户通过实现 disk_read 函数,就可以将任何存储介质与 FatFS 连接起来。同样的方式,也可以实现写入操作的 disk_write 、同步操作的 disk_sync 等函数,将硬件的细节从文件系统的实现中分离出来。

c

复制代码

DRESULT disk_read (

BYTE pdrv, // 驱动器号

BYTE* buff, // 数据缓冲区

LBA_t sector, // 起始扇区号

UINT count // 要读取的扇区数量

);

3. 硬件接口层(diskio.c)

diskio.c 是与硬件存储设备通信的接口层。它定义了一系列与底层磁盘I/O操作相关的函数,比如读取、写入扇区等。编写 diskio.c 时,需要根据实际硬件平台的存储设备特性,实现这些函数。

FatFS 文件系统中的 "disk I/O" 接口实现步骤大体如下:

初始化存储介质和相应的硬件。

实现 disk_initialize 函数,用于初始化存储介质。

实现 disk_read 和 disk_write 函数,分别用于读写操作。

实现 disk_ioctl 函数,用于执行控制命令。

在实际的嵌入式系统开发过程中,开发者需要根据硬件的特性和存储介质的规格来定制这些函数,确保文件系统能够高效且正确地工作。

三、FATFS的移植步骤

FATFS 模块在移植的时候,我们一般只需要修改 2 个文件,即 ffconf.h 和 diskio.c。

1. 准备工作

1.1 硬件需求分析

硬件平台的特性直接影响到移植的难易程度和最终效果。硬件需求分析主要包括以下几点:

CPU架构 :了解目标平台使用的CPU架构(如ARM, MIPS等),因为FatFS对不同架构的支持程度不同。

存储介质 :确认存储介质的类型(如NOR Flash, NAND Flash, SD卡等),因为不同的存储介质可能需要不同的驱动程序。

内存大小 :评估目标硬件的RAM大小,以确保系统有足够的内存运行FatFS及其堆栈。

I/O接口 :分析硬件平台提供的I/O接口(如SPI, I2C, USB等),这关系到"disk I/O"接口的实现。

1.2 源码下载

从官网(http://elm-chan.org/fsw/ff/00index_e.html)下载最新版本的 FATFS 软件包,

解压后可以得到两个文件夹:

一个是documents文件夹,这里是FATFS的一些使用文档和说明,以后在文件编程的时候可以查看该文档;

另一个是src文件夹,里面就是我们所要的源文件。

ffsystem.c 通常是一个可选文件,提供了操作系统依赖的函数,这些函数用于动态内存分配和多任务环境中的互斥锁管理,只有当启用 FF_FS_REENTRANT 配置时,才需要移植,不启用时不用添加进工程目录。

1.3 移植工具和环境搭建

交叉编译器 :获取对应目标平台的交叉编译器,如arm-none-eabi-gcc。

开发板和调试器 :准备目标硬件平台的开发板以及必要的调试工具,如JTAG/SWD调试器。

集成开发环境 :选择一个合适的IDE进行代码编辑和编译,如KEIL或VS Code。

版本控制系统 :使用版本控制系统管理代码,如Git,保证代码的安全性和易于团队协作。

2. 配置文件修改(ffconf.h)

配置文件 ffconf.h 中的各项设置决定了FatFS的行为,根据平台特性和具体disk进行必要的修改是关键步骤。以下是部分关键配置项:

FF_FS_NORTC 的配置

FF_FS_NORTC配置项决定是否启用时间戳功能(记录文件创建或修改时间,若fatfs仅配置为只读,则该选项不必启用)

不启用:当你并不在意文件的创建或修改时间的时候,可以使能此配置项,此时每一个文件的时间戳将使用一个固定的时间,该时间用接下来的三个宏定义设置(FF_NORTC_MON、 FF_NORTC_MDAY、 FF_NORTC_YEAR)。下面例子不启用时间戳功能,通用固定的时间戳2025年1月1号

c

复制代码

#define FF_FS_NORTC 1

#define FF_NORTC_MON 1

#define FF_NORTC_MDAY 1

#define FF_NORTC_YEAR 2025

启用 :启用后,你需要自行编写get_fattime()函数,来为fatfs提供实时时间,此时后面的3个宏的取值可以忽略。

FF_CODE_PAGE 的配置

该选项用于选择目标系统的文件系统语言

下面示例设置语言类型为简体中文

c

复制代码

#define FF_CODE_PAGE 936

FF_USE_STRFUNC 的配置

下面示例设置支持字符串类操作,支持f_printf()函数打印长整型、浮点型,采用UTF-8字符集。

c

复制代码

#define FF_USE_STRFUNC 1

#define FF_PRINT_LLI 1

#define FF_PRINT_FLOAT 1

#define FF_STRF_ENCODE 3

FF_VOLUMES 的配置

设置 FATFS 支持的逻辑设备数目,一般取值1-10之间。 如果使用的是单一分区,可能需要设置 FF_VOLUMES 为1。

3. 修改sidkio.c文件中的5个函数

在开始之前,我们首先要根据自己项目的实际情况,添加硬件初始化及磁盘操作的相关头文件,然后将需要安装Fatfs文件系统的存储设备分配物理驱动盘号(Physical drive nmuber)。

c

复制代码

#define DEV_SD 0 //为SD卡添加驱动号0

底层磁盘 I/O 模块并不是 FATFS 的一部分,并且必须由用户提供。因为 FATFS 模块完全与磁盘 I/O 层分开,因此需要用户自己根据具体的存储设备来提供这些函数,来实现底层物理磁盘的操作。这些函数一般有 5 个,全部在 diskio.c 里面。

disk_status(): 获取磁盘状态。

disk_initialize(): 初始化磁盘(通常是 SPI 或 SD 卡的初始化)。

disk_read(): 从磁盘读取数据。

disk_write(): 向磁盘写入数据。

disk_ioctl(): 控制磁盘的操作,比如获取磁盘扇区、块大小、擦除等。

1. 初始化磁盘驱动器函数

c

复制代码

//假设我们安装的是SD卡设备

DSTATUS disk_initialize (BYTE pdrv)

{

int result;

switch (pdrv)

{

case DEV_SD :

result = SD_Init();

if (result)

{

return STA_NOINIT;

}

return RES_OK;

default :

break;

}

return STA_NOINIT;

}

2. 获取磁盘状态函数

在disk_status()中实现磁盘设备ID读取检测(闪存设备),实现状态信息返回

c

复制代码

//假设我们安装的是SPI Flash设备

DSTATUS disk_status (BYTE pdrv)

{

DSTATUS stat;

int result;

//根据实际disk状况,返回对应状态

switch(pdrv){

case DEV_USB:

case DEV_MMC:

case DEV_USB:

case DEV_RAM:

return STA_NOINIT;

case DEV_SPI:

result = SFC_ReadJEDEC();

if((result != 0) && (result != ~0))

stat = 0;

else

stat = STA_NOINIT;

return stat;

}

return STA_NOINIT;

}

3. 从磁盘驱动器读扇区函数

c

复制代码

/*-------------------------------------------------------------*/

/* Read Sector(s)

/*-------------------------------------------------------------*/

DRESULT disk_read (

BYTE pdrv, /* Physical drive nmuber to identify the drive */

BYTE *buff, /* Data buffer to store read data */

LBA_t sector, /* Start sector in LBA */

UINT count) /* Number of sectors to read */

{

int result;

switch (pdrv)

{

case DEV_SD :

result = SD_ReadData(&g_sd_handler, sector, count, buff);

if (result)

{

return RES_PARERR;

}

}

return RES_OK;

}

4. 向磁盘驱动器写扇区函数

c

复制代码

/*------------------------------------------------------------*/

/* Write Sector(s) */

/*------------------------------------------------------------*/

DRESULT disk_write (

BYTE pdrv, /* Physical drive nmuber to identify the drive */

const BYTE *buff, /* Data to be written */

LBA_t sector, /* Start sector in LBA */

UINT count ) /* Number of sectors to write */

{

int result;

switch (pdrv)

{

case DEV_SD :

result = SD_WriteData(&g_sd_handler, sector, count, (uint8_t *)buff);

if (result)

{

return RES_PARERR;

}

}

return RES_OK;

}

5. 控制设备实现指定功能函数

c

复制代码

/*------------------------------------------------------------*/

/* Miscellaneous Functions

/*--------------------------------------------------------------*/

DRESULT disk_ioctl (

BYTE pdrv, /* Physical drive nmuber (0..) */

BYTE cmd, /* Control code */

void *buff) /* Buffer to send/receive control data */

{

DRESULT result = RES_ERROR;

switch (pdrv)

{

case DEV_SD :

switch (cmd)

{

case CTRL_SYNC:

result = RES_OK;

break;

case GET_SECTOR_SIZE:

*(DWORD*)buff = 512;

result = RES_OK;

break;;

case GET_BLOCK_SIZE:

*(DWORD*)buff = g_sd_handler.SdCard.BlockSize;

result = RES_OK;

break;

case GET_SECTOR_COUNT:

*(DWORD*)buff = g_sd_handler.SdCard.BlockNbr * g_sd_handler.SdCard.BlockSize / 512;

result = RES_OK;

break;

default:

result = RES_PARERR;

}

}

return result;

}

6. 时间接口函数

在diskio.c文件的最后我们还得提供获取时间的函数,因为ff.c中调用了它,用于记录文件的创建,修改时间,需要用户实现,否则会发生编译出错。对于这部分函数,我们直接返回0即可。

c

复制代码

DWORD get_fattime(void)

{

return 0;

}

4. 编写测试代码

移植完FatFs后,需要通过f_mount()函数实现对文件系统的挂载,而后ff.c所提供的标准文件操作接口来实现文件读写。

c

复制代码

#include "ff.h"

int main(void)

{

FATFS fs;

FIL fil;

FRESULT result;

char writeData[] = "Hello, world!\r\n";

char readData[100] = {0};

uint32_t write_data = 0, read_count = 0;

uint32_t fSize = 0;

HAL_Init();

System_Clock_Init(8, 336, 2, 7);

Delay_Init();

HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

UART_Init(&g_usart1_handle, USART1, 115200);

// 挂载SD卡设备

// 第一个参数是挂载点,第二个参数是挂载设备编号,第三个参数是是立即挂载

result = f_mount(&fs, "0:", 1);

if (result)

{

printf("mount fail %d\r\n", result);

}

else

{

printf("mount success\r\n");

}

// 打开文件

// 第一个参数是文件指针,第二个参数是文件路径,第三个参数是文件打开模式

result = f_open(&fil, "0:test.txt", FA_READ | FA_WRITE | FA_OPEN_ALWAYS);

if (result)

{

printf("open fail %d\r\n", result);

}

else

{

printf("open success\r\n");

}

// 写入数据

f_write(&fil, writeData, sizeof(writeData), (UINT *)&write_data);

// 写完文件的读写指针已经到末尾,我们需要重新定位到文件开头

f_lseek(&fil, 0);

// 获取文件大小

fSize = f_size(&fil);

// 读取数据

// 第一个参数是文件指针,第二个参数是读取的数据,第三个参数是要读取的数据长度,第四个参数保存实际读取的数据长度

f_read(&fil, readData, fSize, (UINT *)&read_count);

if (read_count)

{

printf("read count: %ld\r\n", read_count);

printf("data:\r\n");

printf("%s\r\n", readData);

}

// 关闭文件

f_close(&fil);

while (1)

{

}

return 0; //Never run here

}

四、Fatfs源码解析

重要数据结构

FATFS结构体:记录FATFS文件系统本身相关的一些参数

c

复制代码

typedef struct {

BYTE fs_type; /* FAT sub-type (0:Not mounted) */

BYTE drv; /* Physical drive number */

BYTE csize; /* 簇的大小(即包含几个扇区,取值为1、2、4...128,一般取8;给文件分配簇的时候一次分配3个簇 */

BYTE n_fats; /* Number of FAT copies (1 or 2) */

BYTE wflag; /* win[] flag (b0:dirty) */

BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */

WORD id; /* File system mount ID */

WORD n_rootdir; /* Number of root directory entries (FAT12/16) */

#if _MAX_SS != _MIN_SS

WORD ssize; /* 扇区大小,即占多少字节,可取512、1024、2048、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; /* Number of FAT entries, = number of clusters + 2 */

DWORD fsize; /* 一个FAT分区共有多少个扇区*/

DWORD volbase; /* Volume start sector 这里标志的是逻辑0扇区,即逻辑0扇区相对于物理0扇区偏移的扇区量,对于我的SD卡,这里应该是0xE3*/

DWORD fatbase; /* FAT start sector FAT开始扇区*/

DWORD dirbase; /* Root directory start sector (FAT32:Cluster#) 根目录所在的簇*/

DWORD database; /* Data start sector 根目录所在的扇区数 */

DWORD winsect; /* Current sector appearing in the win[] */

BYTE win[_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */

} FATFS;

FIL结构体:记录和特定文件有关的参数;

DIR结构体:记录和目录有关的参数;

挂载函数f_mount()解析

对于f_mount的代码,作用是挂载SD卡,即把硬件与软件的连接起来。主要调用find_volume()函数来完成的。对于该函数,首先需要注意的一个片段是:

c

复制代码

bsect = 0;

fmt = check_fs(fs, bsect); /* Load sector 0 and check if it is an FAT boot sector as SFD */

if (fmt == 1 || (!fmt && (LD2PT(vol)))) { /* Not an FAT boot sector or forced partition number */

for (i = 0; i < 4; i++) { /* Get partition offset */

pt = fs->win + MBR_Table + i * SZ_PTE;

br[i] = pt[4] ? LD_DWORD(&pt[8]) : 0;

}

i = LD2PT(vol); /* Partition number: 0:auto, 1-4:forced */

if (i) i--;

do { /* Find an FAT volume */

bsect = br[i];

fmt = bsect ? check_fs(fs, bsect) : 2; /* Check the partition */

} while (!LD2PT(vol) && fmt && ++i < 4);

}

if (fmt == 3) return FR_DISK_ERR; /* An error occured in the disk I/O layer */

if (fmt) return FR_NO_FILESYSTEM; /* No FAT volume is found */

这一段代码先检测了物理地址的零扇区处。对于部分SD卡,物理地址等于逻辑地址,即找不到MBR区域。

对于另外一部分物理地址和逻辑地址不重合的SD卡,这段代码先通过找物理地址零扇区处记录了逻辑扇区偏移量的那16个字节。

换句话说,这段代码的第一个功能是,找到逻辑扇区相对于物理扇区的偏移量,再把该偏移量赋值给bsect。

当找到逻辑地址的零扇区处后,它需要做的事情就是,需要检测这究竟是什么类型的文件系统。因此,在check_fs()里面,我们可以看到这样的代码:

c

复制代码

BYTE check_fs ( /* 0:Valid FAT-BS, 1:Valid BS but not FAT, 2:Not a BS, 3:Disk error */

FATFS* fs, /* File system object */

DWORD sect /* Sector# (lba) to check if it is an FAT boot record or not */

)

{

fs->wflag = 0; fs->winsect = 0xFFFFFFFF; /* Invaidate window */

if (move_window(fs, sect) != FR_OK) /* Load boot record */

return 3;

if (LD_WORD(&fs->win[BS_55AA]) != 0xAA55) /* Check boot record signature (always placed at offset 510 even if the sector size is >512) */

return 2;

if ((LD_DWORD(&fs->win[BS_FilSysType]) & 0xFFFFFF) == 0x544146) /* Check "FAT" string */

return 0;

if ((LD_DWORD(&fs->win[BS_FilSysType32]) & 0xFFFFFF) == 0x544146) /* Check "FAT" string */

return 0;

return 1;

}

这段代码首先是把DBR区域的数据取出来,再进行判断这段代码是否合法,判断分为两个部分:

一个是检测末尾的标识符是否为 0x55、0xAA;

另一个是检测文件系统是否为FAT。其中,BS_FilSysType = 54,BS_FilSysType32 = 82。

最后,根据check_fs的结果,来进行判断。




张飞的故乡是在哪里
苹果手机照片显示时间的详细步骤解析:从相册查看至第三方应用辅助