FatFs文件系统顶层函数接口详解

前面分析了 FAT32 文件系统结构,接下来结合代码分析其实现过程。

FatFs文件系统顶层函数接口详解

一、分析假设

  • 假设一个磁盘就一个分区。
  • 只分析 FAT32 文件系统相关的代码。
  • 函数的大部分分析,都写入代码注释中。

二、FatFs 文件系统顶层函数接口

2.1 f_mkfs()函数分析

为了方便分析,排除视觉障碍,已经删除了不在假设范围内代码。

/*-----------------------------------------------------------------------*/
/* Create FAT file system on the logical drive                           */
/*-----------------------------------------------------------------------*/
 
FRESULT f_mkfs (
	const TCHAR* path,	/* 磁盘号 */
	BYTE opt,			/* 格式化的类型:FAT32 */
	DWORD au,			/* 格式化时,要设置的簇大小,以字节为单位 */
	void* work,			/* 用户提供的 buffer */
	UINT len			/* 用户提供的 buffer 的大小,以字节为单位 */
)
{
	const UINT n_fats = 1;		/* Number of FATs for FAT12/16/32 volume (1 or 2) */
	const UINT n_rootdir = 512;	/* Number of root directory entries for FAT12/16 volume */
	static const WORD cst[] = {1, 4, 16, 64, 256, 512, 0};	/* Cluster size boundary for FAT12/16 volume (4Ks unit) */
	static const WORD cst32[] = {1, 2, 4, 8, 16, 32, 0};	/* Cluster size boundary for FAT32 volume (128Ks unit) */
	BYTE fmt, sys, *buf, *pte, pdrv, part;
	WORD ss;
	DWORD szb_buf, sz_buf, sz_blk, n_clst, pau, sect, nsect, n;
	DWORD b_vol, b_fat, b_data;				/* Base LBA for volume, fat, data */
	DWORD sz_vol, sz_rsv, sz_fat, sz_dir;	/* Size for volume, fat, dir, data */
	UINT i;
	int vol;
	DSTATUS stat;
 
    /*1. 获取磁盘号,进行一些必要参数的计算和分析,判断参数是否满足要求*/
	/* Check mounted drive and clear work area */
	vol = get_ldnumber(&path);					/* 找到物理磁盘 */
	if (vol < 0) return FR_INVALID_DRIVE;
	if (FatFs[vol]) FatFs[vol]->fs_type = 0;	/* fat 文件系统的描述,放到一个全局变量里面 */
	pdrv = LD2PD(vol);	/* 找到物理磁盘号 */
	part = LD2PT(vol);	/* 分区表,Partition (0:create as new, 1-4:get from partition table) */
 
	/* Check physical drive status */
	stat = disk_initialize(pdrv);
	if (stat & STA_NOINIT) return FR_NOT_READY;
	if (stat & STA_PROTECT) return FR_WRITE_PROTECTED;
 
    /* 获取块大小,块是擦除的基本单元 */
	if (disk_ioctl(pdrv, GET_BLOCK_SIZE, &sz_blk) != RES_OK || !sz_blk || sz_blk > 32768 || (sz_blk & (sz_blk - 1))) sz_blk = 1;	/* Erase block to align data area */
 
	ss = _MAX_SS;  /* 默认扇区大小为 512 字节 */
	if ((au != 0 && au < ss) || au > 0x1000000 || (au & (au - 1))) return FR_INVALID_PARAMETER;	/* Check if au is valid */
	au /= ss;	/* 一个簇占多少个扇区 */
 
	/* Get working buffer */
	buf = (BYTE*)work;		/* Working buffer */
	sz_buf = len / ss;		/* 下面写数据要用到一个 buffer, 这里计算出用户提供的 buffer 的大小(以扇区为单位)*/
	szb_buf = sz_buf * ss;	/* buffer 大小以字节为单位 */
	if (!szb_buf) return FR_MKFS_ABORTED;  /* buffer 如果不足 1 个扇区就报错 */
 
	/* Determine where the volume to be located (b_vol, sz_vol) */
	if (_MULTI_PARTITION && part != 0) {  /* 判断 fatfs 是否是支持多扇区的,我们不分区多扇区的玩法 */
		/* Get partition information from partition table in the MBR */
		if (disk_read(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR;	/* Load MBR */
		if (ld_word(buf + BS_55AA) != 0xAA55) return FR_MKFS_ABORTED;	/* Check if MBR is valid */
		pte = buf + (MBR_Table + (part - 1) * SZ_PTE);
		if (!pte[PTE_System]) return FR_MKFS_ABORTED;	/* No partition? */
		b_vol = ld_dword(pte + PTE_StLba);		/* Get volume start sector */
		sz_vol = ld_dword(pte + PTE_SizLba);	/* Get volume size */
	} else {
		/* 获取这个分区总的扇区个数 */
		if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_vol) != RES_OK) return FR_DISK_ERR;
		b_vol = (opt & FM_SFD) ? 0 : 63;		/* opt=FM_FAT32,所以此时,保留扇区个数 b_vol=63 */
		if (sz_vol < b_vol) return FR_MKFS_ABORTED;
		sz_vol -= b_vol;						/* 分区总扇区数减去保留扇区 */
	}
	if (sz_vol < 128) return FR_MKFS_ABORTED;	/* Check if volume size is >=128s */
 
	/* Pre-determine the FAT type */
	do {
		if (_FS_EXFAT && (opt & FM_EXFAT)) {	/* exFAT possible? */
			if ((opt & FM_ANY) == FM_EXFAT || sz_vol >= 0x4000000 || au > 128) {	/* exFAT only, vol >= 64Ms or au > 128s ? */
				fmt = FS_EXFAT; break;
			}
		}
		if (au > 128) return FR_INVALID_PARAMETER;	/* au 现在的单位是扇区 */
		if (opt & FM_FAT32) {	/* FAT32 possible? */
			if ((opt & FM_ANY) == FM_FAT32 || !(opt & FM_FAT)) {	/* FAT32 only or no-FAT? */
				fmt = FS_FAT32; break;  /* 跳出 do   while()循环 */
			}
		}
		if (!(opt & FM_FAT)) return FR_INVALID_PARAMETER;	/* no-FAT? */
		fmt = FS_FAT16;
	} while (0);
 
	{	/* Create an FAT12/16/32 volume */
		do {
			pau = au;
			/* Pre-determine number of clusters and FAT sub-type */
			if (fmt == FS_FAT32) {	/* FAT32 volume */
				if (!pau) {	/* 如果簇大小用户设置为 0,那么就默认设置为 128K */
					n = sz_vol / 0x20000;	/* Volume size in unit of 128KS */
					for (i = 0, pau = 1; cst32[i] && cst32[i] <= n; i++, pau <<= 1) ;	/* Get from table */
				}
				n_clst = sz_vol / pau;	/* 计算出本分区总共的簇数 */
				sz_fat = (n_clst * 4 + 8 + ss - 1) / ss;	/* sz_fat 代表这个分区需要一个 fat 表需要占的扇区数 */
				sz_rsv = 32;	/* 保留扇区数 */
				sz_dir = 0;		/* No static directory */
				if (n_clst <= MAX_FAT16 || n_clst > MAX_FAT32) return FR_MKFS_ABORTED;
			} else {				/* FAT12/16 volume */
				if (!pau) {	/* au auto-selection */
					n = sz_vol / 0x1000;	/* Volume size in unit of 4KS */
					for (i = 0, pau = 1; cst[i] && cst[i] <= n; i++, pau <<= 1) ;	/* Get from table */
				}
				n_clst = sz_vol / pau;
				if (n_clst > MAX_FAT12) {
					n = n_clst * 2 + 4;		/* FAT size [byte] */
				} else {
					fmt = FS_FAT12;
					n = (n_clst * 3 + 1) / 2 + 3;	/* FAT size [byte] */
				}
				sz_fat = (n + ss - 1) / ss;		/* FAT size [sector] */
				sz_rsv = 1;						/* Number of reserved sectors */
				sz_dir = (DWORD)n_rootdir * SZDIRE / ss;	/* Rootdir size [sector] */
			}
			b_fat = b_vol + sz_rsv;						/* FAT 表所在的扇区号(63+32) */
			b_data = b_fat + sz_fat * n_fats + sz_dir;	/* 数据区所在的扇区号(FAT 表的基地址号+FAT 表所占的扇区数+0) */
 
			/* Align data base to erase block boundary (for flash memory media) */
			n = ((b_data + sz_blk - 1) & ~(sz_blk - 1)) - b_data;	/* 由于擦除是按块来的,所以在此进行调整:需要让保留区、fat 表的要占完整的一个块 */
			if (fmt == FS_FAT32) {		/* FAT32: Move FAT base */
				sz_rsv += n; b_fat += n; /* 单位为扇区 */
			} else {					/* FAT12/16: Expand FAT size */
				sz_fat += n / n_fats;
			}
 
			/* Determine number of clusters and final check of validity of the FAT sub-type */
			if (sz_vol < b_data + pau * 16 - b_vol) return FR_MKFS_ABORTED;	/* Too small volume */
			n_clst = (sz_vol - sz_rsv - sz_fat * n_fats - sz_dir) / pau;
			if (fmt == FS_FAT32) {
				if (n_clst <= MAX_FAT16) {	/* Too few clusters for FAT32 */
					if (!au && (au = pau / 2) != 0) continue;	/* Adjust cluster size and retry */
					return FR_MKFS_ABORTED;
				}
			}
			if (fmt == FS_FAT16) {
				if (n_clst > MAX_FAT16) {	/* Too many clusters for FAT16 */
					if (!au && (pau * 2) <= 64) {
						au = pau * 2; continue;		/* Adjust cluster size and retry */
					}
					if ((opt & FM_FAT32)) {
						fmt = FS_FAT32; continue;	/* Switch type to FAT32 and retry */
					}
					if (!au && (au = pau * 2) <= 128) continue;	/* Adjust cluster size and retry */
					return FR_MKFS_ABORTED;
				}
				if  (n_clst <= MAX_FAT12) {	/* Too few clusters for FAT16 */
					if (!au && (au = pau * 2) <= 128) continue;	/* Adjust cluster size and retry */
					return FR_MKFS_ABORTED;
				}
			}
			if (fmt == FS_FAT12 && n_clst > MAX_FAT12) return FR_MKFS_ABORTED;	/* Too many clusters for FAT12 */
 
			/* Ok, it is the valid cluster configuration */
			break;
		} while (1);
 
		/* 2.创建分区的 DBR */
		mem_set(buf, 0, ss);
		mem_cpy(buf + BS_JmpBoot, "\xEB\xFE\x90" "MSDOS5.0", 11);/* Boot jump code (x86), OEM name */
		st_word(buf + BPB_BytsPerSec, ss);				/* Sector size [byte] */
		buf[BPB_SecPerClus] = (BYTE)pau;				/* Cluster size [sector] */
		st_word(buf + BPB_RsvdSecCnt, (WORD)sz_rsv);	/* Size of reserved area */
		buf[BPB_NumFATs] = (BYTE)n_fats;				/* Number of FATs */
		st_word(buf + BPB_RootEntCnt, (WORD)((fmt == FS_FAT32) ? 0 : n_rootdir));	/* Number of root directory entries */
		if (sz_vol < 0x10000) {
			st_word(buf + BPB_TotSec16, (WORD)sz_vol);	/* Volume size in 16-bit LBA */
		} else {
			st_dword(buf + BPB_TotSec32, sz_vol);		/* Volume size in 32-bit LBA */
		}
		buf[BPB_Media] = 0xF8;							/* Media descriptor byte */
		st_word(buf + BPB_SecPerTrk, 63);				/* Number of sectors per track (for int13) */
		st_word(buf + BPB_NumHeads, 255);				/* Number of heads (for int13) */
		st_dword(buf + BPB_HiddSec, b_vol);				/* Volume offset in the physical drive [sector] */
		if (fmt == FS_FAT32) {
			st_dword(buf + BS_VolID32, GET_FATTIME());	/* VSN */
			st_dword(buf + BPB_FATSz32, sz_fat);		/* FAT size [sector] */
			st_dword(buf + BPB_RootClus32, 2);			/* Root directory cluster # (2) */
			st_word(buf + BPB_FSInfo32, 1);				/* Offset of FSINFO sector (VBR + 1) */
			st_word(buf + BPB_BkBootSec32, 6);			/* Offset of backup VBR (VBR + 6) */
			buf[BS_DrvNum32] = 0x80;					/* Drive number (for int13) */
			buf[BS_BootSig32] = 0x29;					/* Extended boot signature */
			mem_cpy(buf + BS_VolLab32, "NO NAME    " "FAT32   ", 19);	/* Volume label, FAT signature */
		} else {
			st_dword(buf + BS_VolID, GET_FATTIME());	/* VSN */
			st_word(buf + BPB_FATSz16, (WORD)sz_fat);	/* FAT size [sector] */
			buf[BS_DrvNum] = 0x80;						/* Drive number (for int13) */
			buf[BS_BootSig] = 0x29;						/* Extended boot signature */
			mem_cpy(buf + BS_VolLab, "NO NAME    " "FAT     ", 19);	/* Volume label, FAT signature */
		}
		st_word(buf + BS_55AA, 0xAA55);					/* Signature (offset is fixed here regardless of sector size) */
		if (disk_write(pdrv, buf, b_vol, 1) != RES_OK) return FR_DISK_ERR;	/* Write it to the VBR sector */
 
		/* Create FSINFO record if needed */
		if (fmt == FS_FAT32) {
			disk_write(pdrv, buf, b_vol + 6, 1);		/* Write backup VBR (VBR + 6) */
			mem_set(buf, 0, ss);
			st_dword(buf + FSI_LeadSig, 0x41615252);
			st_dword(buf + FSI_StrucSig, 0x61417272);
			st_dword(buf + FSI_Free_Count, n_clst - 1);	/* Number of free clusters */
			st_dword(buf + FSI_Nxt_Free, 2);			/* Last allocated cluster# */
			st_word(buf + BS_55AA, 0xAA55);
			disk_write(pdrv, buf, b_vol + 7, 1);		/* Write backup FSINFO (VBR + 7) */
			disk_write(pdrv, buf, b_vol + 1, 1);		/* Write original FSINFO (VBR + 1) */
		}
 
		/* 3.初始化 fat 表 */
		mem_set(buf, 0, (UINT)szb_buf);
		sect = b_fat;		/* FAT start sector */
		for (i = 0; i < n_fats; i++) {			/* Initialize FATs each */
			if (fmt == FS_FAT32) {
				st_dword(buf + 0, 0xFFFFFFF8);	/* 目录项 0 */
				st_dword(buf + 4, 0xFFFFFFFF);	/* 目录项 1 */
				st_dword(buf + 8, 0x0FFFFFFF);	/* 根目录项 */
			} else {
				st_dword(buf + 0, (fmt == FS_FAT12) ? 0xFFFFF8 : 0xFFFFFFF8);	/* Entry 0 and 1 */
			}
			nsect = sz_fat;		/* Number of FAT sectors */
			do {	/* Fill FAT sectors */
				n = (nsect > sz_buf) ? sz_buf : nsect;
				if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) return FR_DISK_ERR;
				mem_set(buf, 0, ss);
				sect += n; nsect -= n;
			} while (nsect);
		}
 
		/* 4.把根目录所在的扇区全部清 0 */
		nsect = (fmt == FS_FAT32) ? pau : sz_dir;	/* Number of root directory sectors */
		do {
			n = (nsect > sz_buf) ? sz_buf : nsect;
			if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) return FR_DISK_ERR;
			sect += n; nsect -= n;
		} while (nsect);
	}
 
	/* 5.在分区的第 0 号扇区写入 MBR */
	if (_FS_EXFAT && fmt == FS_EXFAT) {
		sys = 0x07;			/* HPFS/NTFS/exFAT */
	} else {
		if (fmt == FS_FAT32) {
			sys = 0x0C;		/* FAT32X */
		} else {
			if (sz_vol >= 0x10000) {
				sys = 0x06;	/* FAT12/16 (>=64KS) */
			} else {
				sys = (fmt == FS_FAT16) ? 0x04 : 0x01;	/* FAT16 (<64KS) : FAT12 (<64KS) */
			}
		}
	}
	if (_MULTI_PARTITION && part != 0) {
		/* Update system ID in the partition table */
		if (disk_read(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR;	/* Read the MBR */
		buf[MBR_Table + (part - 1) * SZ_PTE + PTE_System] = sys;		/* Set system type */
		if (disk_write(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR;	/* Write it back to the MBR */
	} else {
		if (!(opt & FM_SFD)) {
			/* Create partition table in FDISK format */
			mem_set(buf, 0, ss);
			st_word(buf + BS_55AA, 0xAA55);		/* MBR signature */
			pte = buf + MBR_Table;				/* Create partition table for single partition in the drive */
            /*5.1 MBR 中分区表的信息*/
			pte[PTE_Boot] = 0;					/* Boot indicator */
			pte[PTE_StHead] = 1;				/* Start head */
			pte[PTE_StSec] = 1;					/* Start sector */
			pte[PTE_StCyl] = 0;					/* Start cylinder */
			pte[PTE_System] = sys;				/* System type */
			n = (b_vol + sz_vol) / (63 * 255);	/* (End CHS is incorrect) */
			pte[PTE_EdHead] = 254;				/* End head */
			pte[PTE_EdSec] = (BYTE)(n >> 2 | 63);	/* End sector */
			pte[PTE_EdCyl] = (BYTE)n;			/* End cylinder */
			st_dword(pte + PTE_StLba, b_vol);	/* Start offset in LBA */
			st_dword(pte + PTE_SizLba, sz_vol);	/* Size in sectors */
			if (disk_write(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR;	/* Write it to the MBR */
		}
	}
 
	if (disk_ioctl(pdrv, CTRL_SYNC, 0) != RES_OK) return FR_DISK_ERR;
 
	return FR_OK;
}

2.1.1 格式化函数的参数

分析思考我们在 Windows 上格式化一个磁盘,我们可以得出格式化磁盘需要内容:

  • 在“需要格式化的磁盘”右键;
  • 选择要格式化的文件系统类型,FAT16/FAT32/EXTFAT 等;
  • 选择簇大小。

因此,格式化函数的第 1 个参数是磁盘的路径,根据磁盘的路径函数就能找到对应的磁盘,第 2 个参数也是要传入文件系统的类型,第 3 个参数也就是簇的大小,第 4、5 个参数是这个函数运行所需要的内存。

2.1.2 格式化函数的作用

与我们之前分析 FAT 文件系统原理是一致的,此函数总共进行如下的操作:

  • 获取磁盘号,进行一些必要参数的计算和分析,判断参数是否满足要求。
  • 创建 DBR。同时创建 FSINFO 信息,FSINFO 信息的作用表示了当前分区还可用的簇多少已经下一个可用的簇号,从而可以方便操作系统处理。
  • 初始化 FAT 表。
  • 初始化根目录区。把根目录所在的簇全部清 0。
  • 创建 MBR,并且把当前的分区信息写入主分区字段内。

2.1.3 总结

从 f_mkfs 我们可以看出,此函数的作用与我们之前分析的 FAT 文件系统是完全吻合的。

2.2 f_mount 函数分析

为了方便分析,排除视觉障碍,已经删除了不在假设范围内代码。

FRESULT f_mount (
	FATFS* fs,			/* fat 文件系统描述的一个结构体,需要用户进行分配,然后会被记录在全局的 FATFS[]这个数组中 */
	const TCHAR* path,	/* 物理磁盘 */
	BYTE opt			/* 挂载选项,1-代表立马挂载,从代码分析中知,这个值必须传入 1 */
)
{
	FATFS *cfs;
	int vol;
	FRESULT res;
	const TCHAR *rp = path;
 
 
	/* 获取驱动号,为了找到 FatFs[]数组中的空闲项 */
	vol = get_ldnumber(&rp);
	if (vol < 0) return FR_INVALID_DRIVE;
	cfs = FatFs[vol];					/* Pointer to fs object */
 
	if (cfs) {
#if _FS_LOCK != 0
		clear_lock(cfs);
#endif
#if _FS_REENTRANT						/* Discard sync object of the current volume */
		if (!ff_del_syncobj(cfs->sobj)) return FR_INT_ERR;
#endif
		cfs->fs_type = 0;				/* Clear old fs object */
	}
 
	if (fs) {
		fs->fs_type = 0;				/* Clear new fs object */
#if _FS_REENTRANT						/* Create sync object for the new volume */
		if (!ff_cre_syncobj((BYTE)vol, &fs->sobj)) return FR_INT_ERR;
#endif
	}
 
    /* 把用户分配的 FATFS 结构图放入全局数组指针中 */
	FatFs[vol] = fs;					/* Register new fs object */
 
	if (!fs || opt != 1) return FR_OK;	/* Do not mount now, it will be mounted later */
    
    /* 这个函数才是挂载时的关键所在 */
	res = find_volume(&path, &fs, 0);	/* Force mounted the volume */
	LEAVE_FF(fs, res);
}

从 f_mount 函数分析中可知,find_volume()函数才是挂载的核心代码,

static
FRESULT find_volume (	/* FR_OK(0): successful, !=0: any error occurred */
	const TCHAR** path,	/* 硬件磁盘,与 f_mount 函数的参数是一致的 */
	FATFS** rfs,		/* 与 f_mount 函数的 fs 参数是一致的 */
	BYTE mode			/* 这个传入的是 0 */
)
{
	BYTE fmt, *pt;
	int vol;
	DSTATUS stat;
	DWORD bsect, fasize, tsect, sysect, nclst, szbfat, br[4];
	WORD nrsv;
	FATFS *fs;
	UINT i;
 
 
	/* Get logical drive number */
	*rfs = 0;
	vol = get_ldnumber(path);
	if (vol < 0) return FR_INVALID_DRIVE;
 
	/* Check if the file system object is valid or not */
    /* 1.找到用户分配的 FATFS 结构体 */
	fs = FatFs[vol];					/* Get pointer to the file system object */
	if (!fs) return FR_NOT_ENABLED;		/* Is the file system object available? */
 
	ENTER_FF(fs);						/* Lock the volume */
	*rfs = fs;							/* Return pointer to the file system object */
 
	mode &= (BYTE)~FA_READ;				/* Desired access mode, write access or not */
 
    /* 判定磁盘当前状态,如果磁盘被初始化过,那么就判定是挂载过了,直接返回 OK */
	if (fs->fs_type) {					/* If the volume has been mounted */
		stat = disk_status(fs->drv);
		if (!(stat & STA_NOINIT)) {		/* and the physical drive is kept initialized */
			if (!_FS_READONLY && mode && (stat & STA_PROTECT)) {	/* Check write protection if needed */
				return FR_WRITE_PROTECTED;
			}
			return FR_OK;				/* The file system object is valid */
		}
	}
 
	/* The file system object is not valid. */
	/* Following code attempts to mount the volume. (analyze BPB and initialize the fs object) */
 
    /* 2.进行 FATFS 结构体填充 */
	fs->fs_type = 0;					/* Clear the file system object */
	fs->drv = LD2PD(vol);				/* Bind the logical drive and a physical drive */
 
    /* 2.1 初始化磁盘 */
	stat = disk_initialize(fs->drv);	/* Initialize the physical drive */
	if (stat & STA_NOINIT) { 			/* Check if the initialization succeeded */
		return FR_NOT_READY;			/* Failed to initialize due to no medium or hard error */
	}
	if (!_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check disk write protection if needed */
		return FR_WRITE_PROTECTED;
	}
	/* Find an FAT partition on the drive. Supports only generic partitioning, FDISK and SFD. */
	bsect = 0;
 
    /* 2.2 check_fs()函数会把磁盘的第 1 个扇区(就是 MBR)读入到 fs->win[]数组中, 判断 MBR 是否是合法的 MBR*/
	fmt = check_fs(fs, bsect);			/* Load sector 0 and check if it is an FAT-VBR as SFD */
 
    /* 2.3 fmt=0,说明是 FAT32 文件系统 */
	if (fmt == 2 || (fmt < 2 && LD2PT(vol) != 0)) {	/* Not an FAT-VBR or forced partition number */
		for (i = 0; i < 4; i++) {			/* Get partition offset */
			pt = fs->win + (MBR_Table + i * SZ_PTE);
			br[i] = pt[PTE_System] ? ld_dword(pt + PTE_StLba) : 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) : 3;	/* Check the partition */
		} while (!LD2PT(vol) && fmt >= 2 && ++i < 4);
	}
	if (fmt == 4) return FR_DISK_ERR;		/* An error occured in the disk I/O layer */
	if (fmt >= 2) return FR_NO_FILESYSTEM;	/* No FAT volume is found */
 
	/* An FAT volume is found. Following code initializes the file system object */
 
	{
	    /* 读取 MBR 中的 BPB_BytsPerSec 域,判断扇区大小是否是 512(SS(fs)) */
		if (ld_word(fs->win + BPB_BytsPerSec) != SS(fs)) return FR_NO_FILESYSTEM;	/* (BPB_BytsPerSec must be equal to the physical sector size) */
 
        /* 2.4 获取 FAT 表的大小、FAT 表的个数、每个簇的扇区数、根目录项数(对于 FAT32 不存在这个)、磁盘簇的个数、MBR、FAT 表、数据区的位置、 */
		fasize = ld_word(fs->win + BPB_FATSz16);			/* Number of sectors per FAT */
		if (fasize == 0) fasize = ld_dword(fs->win + BPB_FATSz32);
		fs->fsize = fasize;
 
		fs->n_fats = fs->win[BPB_NumFATs];					/* Number of FATs */
		if (fs->n_fats != 1 && fs->n_fats != 2) return FR_NO_FILESYSTEM;	/* (Must be 1 or 2) */
		fasize *= fs->n_fats;								/* Number of sectors for FAT area */
 
		fs->csize = fs->win[BPB_SecPerClus];				/* Cluster size */
		if (fs->csize == 0 || (fs->csize & (fs->csize - 1))) return FR_NO_FILESYSTEM;	/* (Must be power of 2) */
 
		fs->n_rootdir = ld_word(fs->win + BPB_RootEntCnt);	/* Number of root directory entries */
		if (fs->n_rootdir % (SS(fs) / SZDIRE)) return FR_NO_FILESYSTEM;	/* (Must be sector aligned) */
 
		tsect = ld_word(fs->win + BPB_TotSec16);			/* Number of sectors on the volume */
		if (tsect == 0) tsect = ld_dword(fs->win + BPB_TotSec32);
 
		nrsv = ld_word(fs->win + BPB_RsvdSecCnt);			/* Number of reserved sectors */
		if (nrsv == 0) return FR_NO_FILESYSTEM;				/* (Must not be 0) */
 
		/* Determine the FAT sub type */
		sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZDIRE);	/* RSV + FAT + DIR */
		if (tsect < sysect) return FR_NO_FILESYSTEM;		/* (Invalid volume size) */
		nclst = (tsect - sysect) / fs->csize;				/* Number of clusters */
		if (nclst == 0) return FR_NO_FILESYSTEM;			/* (Invalid volume size) */
		fmt = FS_FAT32;
		if (nclst <= MAX_FAT16) fmt = FS_FAT16;
		if (nclst <= MAX_FAT12) fmt = FS_FAT12;
 
		/* Boundaries and Limits */
		fs->n_fatent = nclst + 2;							/* Number of FAT entries */
		fs->volbase = bsect;								/* Volume start sector */
		fs->fatbase = bsect + nrsv; 						/* FAT start sector */
		fs->database = bsect + sysect;						/* Data start sector */
		if (fmt == FS_FAT32) {
			if (ld_word(fs->win + BPB_FSVer32) != 0) return FR_NO_FILESYSTEM;	/* (Must be FAT32 revision 0.0) */
			if (fs->n_rootdir) return FR_NO_FILESYSTEM;		/* (BPB_RootEntCnt must be 0) */
			fs->dirbase = ld_dword(fs->win + BPB_RootClus32);	/* Root directory start cluster */
			szbfat = fs->n_fatent * 4;						/* (Needed FAT size) */
		} else {
			if (fs->n_rootdir == 0)	return FR_NO_FILESYSTEM;/* (BPB_RootEntCnt must not be 0) */
			fs->dirbase = fs->fatbase + fasize;				/* Root directory start sector */
			szbfat = (fmt == FS_FAT16) ?					/* (Needed FAT size) */
				fs->n_fatent * 2 : fs->n_fatent * 3 / 2 + (fs->n_fatent & 1);
		}
		if (fs->fsize < (szbfat + (SS(fs) - 1)) / SS(fs)) return FR_NO_FILESYSTEM;	/* (BPB_FATSz must not be less than the size needed) */
 
#if !_FS_READONLY
		/* Get FSINFO if available */
		fs->last_clst = fs->free_clst = 0xFFFFFFFF;		/* Initialize cluster allocation information */
		fs->fsi_flag = 0x80;
#if (_FS_NOFSINFO & 3) != 3
        /* 2.5 判断是否存在 FSINFO 扇区,如果存在则读出可用簇数、下一个可用簇号到 FATFS 结构体中 */
		if (fmt == FS_FAT32				/* Enable FSINFO only if FAT32 and BPB_FSInfo32 == 1 */
			&& ld_word(fs->win + BPB_FSInfo32) == 1
			&& move_window(fs, bsect + 1) == FR_OK)
		{
			fs->fsi_flag = 0;
			if (ld_word(fs->win + BS_55AA) == 0xAA55	/* Load FSINFO data if available */
				&& ld_dword(fs->win + FSI_LeadSig) == 0x41615252
				&& ld_dword(fs->win + FSI_StrucSig) == 0x61417272)
			{
#if (_FS_NOFSINFO & 1) == 0
				fs->free_clst = ld_dword(fs->win + FSI_Free_Count);
#endif
#if (_FS_NOFSINFO & 2) == 0
				fs->last_clst = ld_dword(fs->win + FSI_Nxt_Free);
#endif
			}
		}
#endif	/* (_FS_NOFSINFO & 3) != 3 */
#endif	/* !_FS_READONLY */
	}
 
	fs->fs_type = fmt;	/* FAT sub-type */
	fs->id = ++Fsid;	/* File system mount ID */
#if _USE_LFN == 1
	fs->lfnbuf = LfnBuf;	/* Static LFN working buffer */
#if _FS_EXFAT
	fs->dirbuf = DirBuf;	/* Static directory block working buuffer */
#endif
#endif
#if _FS_RPATH != 0
	fs->cdir = 0;		/* Initialize current directory */
#endif
#if _FS_LOCK != 0		/* Clear file lock semaphores */
	clear_lock(fs);
#endif
	return FR_OK;
}

总结

f_mount 函数就是读出 MBR 扇区的内容放入 FATFS 结构图中,供以后使用。

2.3 f_open 函数分析

2.3.1 关键结构体

(1)FAT 文件系统描述一个文件夹的结构体。虽然是一个文件夹结构体,但是再打开一个文件的开始,先创建这个结构体,然后先把文件的信息填充到这个结构体,最终使用这个结构体填充 FIL 结构图的一些重要信息。

typedef struct {
	_FDID	obj;			/* 存放下一个目标的信息 */
	DWORD	dptr;			/* 当前目录项的起始地址(单位:字节) */
	DWORD	clust;			/* 当前簇 */
	DWORD	sect;			/* 当前簇对应的扇区位置(通过目录项的位置确定) */
	BYTE*	dir;			/* 指向当前扇区 win[]中的偏移(单位:字节) */
	BYTE	fn[12];			/* 存放 8.3 文件名,最后 1 个字节存放标志:是否有长目录项 */
#if _USE_LFN != 0
	DWORD	blk_ofs;		/* 指向第 1 个长目录项的起始位置(字节:单位) (0xFFFFFFFF:Invalid) */
#endif
#if _USE_FIND
	const TCHAR* pat;		/* Pointer to the name matching pattern */
#endif
} DIR;

(2)FAT 文件系统描述一个文件的结构体。

typedef struct {
	_FDID	obj;			/* 存放下一个目标的信息 */
	BYTE	flag;			/* 文件状态标志 */
	BYTE	err;			/* 文件错误码 */
	FSIZE_t	fptr;			/* 文件的读写指针位置 */
	DWORD	clust;			/* fptr 所在的当前簇 */
	DWORD	sect;			/* Sector number appearing in buf[] (0:invalid) */
#if !_FS_READONLY
	DWORD	dir_sect;		/* Sector number containing the directory entry */
	BYTE*	dir_ptr;		/* Pointer to the directory entry in the win[] */
#endif
#if _USE_FASTSEEK
	DWORD*	cltbl;			/* Pointer to the cluster link map table (nulled on open, set by application) */
#endif
#if !_FS_TINY
	BYTE	buf[_MAX_SS];	/* File private data read/write window */
#endif
} FIL;

(3)_FDID 结构体。

在(1)和(2)的结构体中,都有 _FDID 结构体,那么分析一下这个结构体的内容:

注:在 FATFS 中有个全局的指针数组 FATFS[],在 f_mount 的时候可知,这个数组会被填充,填充的内容是 MBR 的内容,之后,我们把这个数组指针称作为**“超级快”**。

typedef struct {
	FATFS*	fs;			/* 指向全局的在挂载的时候磁盘对应的超级块 */
	WORD	id;			/* Owner file system mount ID */
	BYTE	attr;		/* 目标属性,根据目录项中的属性进行填充 */
	BYTE	stat;		/* Object chain status (b1-0: =0:not contiguous, =2:contiguous (no data on FAT), =3:got flagmented, b2:sub-directory stretched) */
	DWORD	sclust;		/* 目标的起始簇号 */
	FSIZE_t	objsize;	/* 文件或者文件夹的大小 */
#if _FS_LOCK != 0
	UINT	lockid;		/* File lock ID origin from 1 (index of file semaphore table Files[]) */
#endif
} _FDID;

2.3.2 f_open 函数分析

下面是 f_open 的源代码:

FRESULT f_open (
	FIL* fp,			/* 返回的文件描述符 */
	const TCHAR* path,	/* 打开文件的路径 */
	BYTE mode			/* 文件打开的模式 */
)
{
	FRESULT res;
	DIR dj; /* 分配一个 DIR 结构体 */
	FATFS *fs;
#if !_FS_READONLY
	DWORD dw, cl, bcs, clst, sc;
	FSIZE_t ofs;
#endif

	if (!fp) return FR_INVALID_OBJECT;

	/* Get logical drive */
	mode &= _FS_READONLY ? FA_READ : FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_CREATE_NEW | FA_OPEN_ALWAYS | FA_OPEN_APPEND | FA_SEEKEND;
	res = find_volume(&path, &fs, mode);
	if (res == FR_OK) {
		dj.obj.fs = fs;
        /* 根据不同的配置来分配 fs->lfn,用于存放 path 指向的路径 */
		INIT_NAMBUF(fs);
		res = follow_path(&dj, path);	/* Follow the file path */
#if !_FS_READONLY	/* R/W configuration */
		if (res == FR_OK) {
			if (dj.fn[NSFLAG] & NS_NONAME) {	/* Origin directory itself? */
				res = FR_INVALID_NAME;
			}
#if _FS_LOCK != 0
			else {
				res = chk_lock(&dj, (mode & ~FA_READ) ? 1 : 0);
			}
#endif
		}
		/* Create or Open a file */
		if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) {
			if (res != FR_OK) {
                /* 文件不存在 创建文件,省略不少代码 */
            }
			else {
                /* 文件存在 但是不允许写入 */
				if (dj.obj.attr & (AM_RDO | AM_DIR)) {	/* Cannot overwrite it (R/O or DIR) */
					res = FR_DENIED;
				} else {
					if (mode & FA_CREATE_NEW) res = FR_EXIST;	/* Cannot create as new file */
				}
			}
			if (res == FR_OK && (mode & FA_CREATE_ALWAYS)) {
                /* 覆盖文件,省略不少代码 */
            }
		}
		else {	/* 成功打开一个文件 */
			if (res == FR_OK) {					/* Following succeeded */
				if (dj.obj.attr & AM_DIR) {		/* It is a directory */
					res = FR_NO_FILE;
				} else {
					if ((mode & FA_WRITE) && (dj.obj.attr & AM_RDO)) { /* R/O violation */
						res = FR_DENIED;
					}
				}
			}
		}
		if (res == FR_OK) {
			if (mode & FA_CREATE_ALWAYS)		/* Set file change flag if created or overwritten */
				mode |= FA_MODIFIED;
			fp->dir_sect = fs->winsect;			/* Pointer to the directory entry */
			fp->dir_ptr = dj.dir;
		}
#else	

#endif
		if (res == FR_OK) 
        {
            /* 使用 dj 结构体对文件描述符 fp 填充 */
            /* 具体填充的代码,参考源码,最终会进行一些总结概括 */
        }      

		FREE_NAMBUF();
	}

	if (res != FR_OK) fp->obj.fs = 0;	/* Invalidate file object on error */

	LEAVE_FF(fs, res);
}

(1)在注释 1 的地方,先调用 INIT_NAMBUF(fs)宏,初始化 fs->lfn,这个 lfn 之后会被用来存放 path 中以’/’为分隔符的各个段的名字。

(2)接着在注释 2 的地方调用 follow_path(&dj, path),追踪 path 的各个段,下面是 follow_path()函数的源码:

/*-----------------------------------------------------------------------*/
/* 跟踪文件路径                                                  */
/*-----------------------------------------------------------------------*/
static
FRESULT follow_path (	/* FR_OK(0): successful, !=0: error code */
	DIR* dp,			/* Directory object to return last directory and found object */
	const TCHAR* path	/* Full-path string to find a file or directory */
)
{
	FRESULT res;
	BYTE ns;
	_FDID *obj = &dp->obj;
	FATFS *fs = obj->fs;


	{										/* With heading separator */
		while (*path == '/' || *path == '\\') path++;	/* Strip heading separator */
		obj->sclust = 0;					/* Start from the root directory */
	}

	if ((UINT)*path < ' ') {				/* Null path name is the origin directory itself */
		dp->fn[NSFLAG] = NS_NONAME;
		res = dir_sdi(dp, 0);

	} else {								/* Follow path */
		for (;;) {
			/***************** 1.获取 path 的每一段名字 *****************/
			res = create_name(dp, &path);	
			if (res != FR_OK) break;
			/***************** 2.判断磁盘上面是否存在提取出来的目录项 *****************/
			res = dir_find(dp);				/* Find an object with the segment name */
			/***************** 3.获取当前文件名获取的状态 *****************/
			ns = dp->fn[NSFLAG];
			if (res != FR_OK) {				/* Failed to find the object */
				if (res == FR_NO_FILE) {	/* 不存在此目录项 */
					if (_FS_RPATH && (ns & NS_DOT)) {	/* If dot entry is not exist, stay there */
						if (!(ns & NS_LAST)) continue;	/* Continue to follow if not last segment */
						dp->fn[NSFLAG] = NS_NONAME;
						res = FR_OK;
					} else {							/* Could not find the object */
						if (!(ns & NS_LAST)) res = FR_NO_PATH;	/* Adjust error code if not last segment */
					}
				}
				break;
			}
			if (ns & NS_LAST) break;			/* 是最后一段 */
			/* Get into the sub-directory */
			if (!(obj->attr & AM_DIR)) {		/* 是子目录,不需要再追踪 */
				res = FR_NO_PATH; break;
			}
			{
				/********* 4.设置下一个簇的位置*********/
				obj->sclust = ld_clust(fs, fs->win + dp->dptr % SS(fs));	/* Open next directory */
			}
		}
	}

	return res;
}

在这个函数中又调用 create_name()和 dir_find(dp)两个函数,这两个函数是追踪的核心代码,也是 f_open 的核心代码。

(2.1)下面是 create_name()的源码:

/*-----------------------------------------------------------------------*/
/* 使用传入的 Path,然后用'/'进行分段,提取每一段的名字       ,同时修改 path 指向的位置*/
/*-----------------------------------------------------------------------*/

static
FRESULT create_name (	/* FR_OK: successful, FR_INVALID_NAME: could not create */
	DIR* dp,			/* Pointer to the directory object */
	const TCHAR** path	/* Pointer to pointer to the segment in the path string */
)
{
//#if _USE_LFN != 0	/* LFN configuration */
	BYTE b, cf;
	WCHAR w, *lfn;
	UINT i, ni, si, di;
	const TCHAR *p;

	/* Create LFN in Unicode */
	p = *path; lfn = dp->obj.fs->lfnbuf; si = di = 0;
	for (;;) {
		w = p[si++];					/* 从 path 中获取一个字符 */
		if (w < ' ') break;				/* 如果字符的值<' ',那么就判定 path 没有字符了,因为文件的命名字符不能<' ' */
		if (w == '/' || w == '\\') {	/* 如果字符是/或者\字符,就跳出来,说明一段已经结束了 */
			while (p[si] == '/' || p[si] == '\\') si++;	
			break;
		}

        /* 一段的名字太长了,报错,
           这个判断写再下面才容易阅读,尴尬中。。。
        */
		if (di >= _MAX_LFN) return FR_INVALID_NAME;	

        /* 如果字符不是 UNICODE 编码的话,就进行转码 */
#if !_LFN_UNICODE
		w &= 0xFF;
		if (IsDBCS1(w)) {				/* Check if it is a DBC 1st byte (always false on SBCS cfg) */
			b = (BYTE)p[si++];			/* Get 2nd byte */
			w = (w << 8) + b;			/* Create a DBC */
			if (!IsDBCS2(b)) return FR_INVALID_NAME;	/* Reject invalid sequence */
		}
		w = ff_convert(w, 1);			/* Convert ANSI/OEM to Unicode */
		if (!w) return FR_INVALID_NAME;	/* Reject invalid code */
#endif
        /* 非法字符检测 */
		if (w < 0x80 && chk_chr("\"*:<>\?|\x7F", w)) return FR_INVALID_NAME;	/* Reject illegal characters for LFN */
        /******************* 1.把字符存入 lfn 的 buffer 中 *******************/		   
        lfn[di++] = w; 
	}
    /******************* 2.让 path 指向下一段*******************/
	*path = &p[si];						
	cf = (w < ' ') ? NS_LAST : 0;		/******************** 3.判断是否是最后一段 ********************/

    /* 在 lfn 中,从后向前找,找到第 1 个并不是空格也不是点的位置 */
	while (di) {						
		w = lfn[di - 1];
		if (w != ' ' && w != '.') break;
		di--;
	}
	lfn[di] = 0; /* 在不是空格也不是点的后面设置字符串结束标志 */
	if (di == 0) return FR_INVALID_NAME;	/* 判断名字是否为空 */

	/* Create SFN in directory form */
	mem_set(dp->fn, ' ', 11);
    /*从 lfn 的头开始找到不是空格也不是点的位置*/
	for (si = 0; lfn[si] == ' ' || lfn[si] == '.'; si++) ;	
	if (si) cf |= NS_LOSS | NS_LFN;  /* 如果 lfn 的头部存在空格和点,设置标志 */
	while (di && lfn[di - 1] != '.') di--;	/* 从后向前找到点的位置,作为扩展名的起始位置 */

    /* 此时 si 指向文件名的起始位置,di 指向文件名的结尾 */
	i = b = 0; ni = 8;
	for (;;) {
		w = lfn[si++];					/* Get an LFN character */
		if (!w) break;					/* Break on end of the LFN */
		if (w == ' ' || (w == '.' && si != di)) {	/* Remove spaces and dots */
			cf |= NS_LOSS | NS_LFN; continue;
		}

        /* 判断是否有长文件名 */
		if (i >= ni || si == di) {		/* 如果文件名有 8 个字符或者到了文件名的结束字符 */
			if (ni == 11) {				/* 如果 ni==11,说明需要设置长文件名 */
				cf |= NS_LOSS | NS_LFN; break;
			}
			if (si != di) cf |= NS_LOSS | NS_LFN;	/* 文件名的长度肯定是大于 8 个字符,需要设置长文件名 */
			if (si > di) break;			/* di 指向的是文件名的结尾,说明不存在扩展名 */
			si = di; i = 8; ni = 11;	/* 直接让 si=di,下一次循环就直接会跳出,不会再给 dp->fn 赋值了 */
			b <<= 2; continue;
		}

        /* 字符编码转换 */
		if (w >= 0x80) {				/* Non ASCII character */
#ifdef _EXCVT
			w = ff_convert(w, 0);		/* Unicode -> OEM code */
			if (w) w = ExCvt[w - 0x80];	/* Convert extended character to upper (SBCS) */
#else
			w = ff_convert(ff_wtoupper(w), 0);	/* Upper converted Unicode -> OEM code */
#endif
			cf |= NS_LFN;				/* Force create LFN entry */
		}

        /* 下面把小写转换成大写存入 dp->fn 中 */
		if (_DF1S && w >= 0x100) {		/* Is this DBC? (always false at SBCS cfg) */
			if (i >= ni - 1) {
				cf |= NS_LOSS | NS_LFN; i = ni; continue;
			}
			dp->fn[i++] = (BYTE)(w >> 8);
		} else {						/* SBC */
			if (!w || chk_chr("+,;=[]", w)) {	/* Replace illegal characters for SFN */
				w = '_'; cf |= NS_LOSS | NS_LFN;/* Lossy conversion */
			} else {
				if (IsUpper(w)) {		/* ASCII large capital */
					b |= 2;
				} else {
					if (IsLower(w)) {	/* ASCII small capital */
						b |= 1; w -= 0x20;
					}
				}
			}
		}
        /******************* 4.把短文件名放入 dp->fn[]中 *******************/
		dp->fn[i++] = (BYTE)w;
	}

    /* 如果 dp->fn[0]== 0XE5 那么就替换成 0x05,因为 E5 代表的是被删除的目录项*/
	if (dp->fn[0] == DDEM) dp->fn[0] = RDDEM;

	if (ni == 8) b <<= 2;
    
	if ((b & 0x0C) == 0x0C || (b & 0x03) == 0x03) cf |= NS_LFN;	/* 进一步保证需要有长文件名 */

    /* 文件名和扩展名不存在长文件名标志 */
	if (!(cf & NS_LFN)) {						/* When LFN is in 8.3 format without extended character, NT flags are created */
		if ((b & 0x03) == 0x01) cf |= NS_EXT;	/* NT flag (Extension has only small capital) */
		if ((b & 0x0C) == 0x04) cf |= NS_BODY;	/* NT flag (Filename has only small capital) */
	}

    /******************* 5.在 dp->fn[]最后一个字节写入文件名的标志:是否有长文件名、是否是最后一段 *******************/
	dp->fn[NSFLAG] = cf;	/* 把 CF 放入 fn 的最后一个字节 */
	return FR_OK;
}

create_name ()函数的主要作用是根据 path 中的“/”进行分割每一段的名字,并且把短文件名存入 dp->fn[]中,具体操作步骤:

  • 跟进 path 找出一段文件名,然后存入 lfn[]缓存中。
  • 修改 path 指向下一段文件名的起始位置。
  • 把 lfn[]制作成短文件名放入 dp->fn[]中。
  • 把当前的状态放入 dp->fn[]的最后一个字节,用来指示:是否有长文件名,是否是最后一段。

(2.2)dir_find()函数的源码:

/*-----------------------------------------------------------------------*/
/*根据 dp->sect、dp->fn 找到相应的目录项,                */
/*-----------------------------------------------------------------------*/

static
FRESULT dir_find (	/* FR_OK(0):succeeded, !=0:error */
	DIR* dp			/* Pointer to the directory object with the file name */
)
{
	FRESULT res;
	FATFS *fs = dp->obj.fs;
	BYTE c;
#if _USE_LFN != 0
	BYTE a, ord, sum;
#endif

    /*********** 1.根据 dp->obj.sclust,设置 dp 的当前信息 ************/
	res = dir_sdi(dp, 0);			/* Rewind directory object */
	if (res != FR_OK) return res;
	/* On the FAT12/16/32 volume */
#if _USE_LFN != 0
	ord = sum = 0xFF; dp->blk_ofs = 0xFFFFFFFF;	/* Reset LFN sequence */
#endif
	do {
        /********* 2.如果当前扇区的内容不在 win[]缓冲,就从磁盘读出来 *************/
		res = move_window(fs, dp->sect); 
		if (res != FR_OK) break;
		c = dp->dir[DIR_Name];
		if (c == 0) { res = FR_NO_FILE; break; }	/* 判断目录项的第 1 个字节是否合法 */
//#if _USE_LFN != 0	/* LFN configuration */
		dp->obj.attr = a = dp->dir[DIR_Attr] & AM_MASK;
        /* 如果第 1 个字节是 E5 或者是卷标,就判定也不是合适的目录项 */
		if (c == DDEM || ((a & AM_VOL) && a != AM_LFN)) {	
			ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF;	/* Reset LFN sequence */
		} else {
			if (a == AM_LFN) {			/* 是长目录项 */
				if (!(dp->fn[NSFLAG] & NS_NOLFN)) {
					if (c & LLEF) {		/* 如果 c==0x40 那么就是最后一个长目录项 */
						sum = dp->dir[LDIR_Chksum];
						c &= (BYTE)~LLEF; ord = c;	/* LFN start order */
						dp->blk_ofs = dp->dptr;	/* Start offset of LFN */
					}
					/* Check validity of the LFN entry and compare it with given name */
					ord = (c == ord && sum == dp->dir[LDIR_Chksum] && cmp_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF;
				}
			} else {					/* An SFN entry is found */
				if (!ord && sum == sum_sfn(dp->dir)) break;	/* LFN matched? */

                /********* 3.比较短目录名是否匹配 **************/
				if (!(dp->fn[NSFLAG] & NS_LOSS) && !mem_cmp(dp->dir, dp->fn, 11)) break;
				ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF;	/* Reset LFN sequence */
			}
		}
        /********** 4.寻找下一个目录项:会修改 dp->dptr,dp->dir 的值,也可能修改当前簇,当前扇区等信息 
           如果此时在当前目录下没有目录项了,就会返回错误***************
        */
		res = dir_next(dp, 0);	
	} while (res == FR_OK);

	return res;
}

dir_find ()函数的作用是在扇区中找到每一个目录项,取出目录项的文件名与 dp->fn[]中的文件名对比,用来确定是否找到对应的目录项,主要步骤:

  • dir_sdi(dp, 0)函数用来设置 dp 的当前扇区 dp->sect 等信息。
  • 读出当前扇区的信息到 win[]中。
  • 然后取出 win[]中的每一个目录项。
  • 判断目录项中的文件名是否与 dp->fn[]中的文件名一致。

(3)通过 follow_path()函数的返回值,就可以最终判定出 path 的目标文件是否存在。如果文件存在,在 f_open()的最后会设置文件描述符 fp 的信息:

		else {	/* Open an existing file */
			if (res == FR_OK) {					/* Following succeeded */
				if (dj.obj.attr & AM_DIR) {		/* It is a directory */
					res = FR_NO_FILE;
				} else {
					if ((mode & FA_WRITE) && (dj.obj.attr & AM_RDO)) { /* R/O violation */
						res = FR_DENIED;
					}
				}
			}
		}

主要设置文件描述符 fp 的当前扇区,文件的打开模式 mode 等信息。

2.3.3 核心思想总结

通过 f_open()函数的过程,核心思想:

  • 使用一个 _FDID 结构体记录要解析的簇信息。
  • 用 DIR 结构体把 _FDID 结构体对应的簇,解析出对应的扇区。
  • 把扇区上面的每个目录项进行分析和目标名字是否匹配。
  • 如果最终匹配到文件目录项,就使用 DIR 结构体里面对应的信息,将 FILE 结构体初始化,返回到用户空间。

2.4 f_read 函数分析

下面是 f_read()函数的源码:

FRESULT f_read (
	FIL* fp, 	/* Pointer to the file object */
	void* buff,	/* Pointer to data buffer */
	UINT btr,	/* Number of bytes to read */
	UINT* br	/* Pointer to number of bytes read */
)
{
	FRESULT res;
	FATFS *fs;
	DWORD clst, sect;
	FSIZE_t remain;
	UINT rcnt, cc, csect;
	BYTE *rbuff = (BYTE*)buff;

	*br = 0;	/* Clear read byte counter */
    /********* 1.如果文件可读数据不足,调整要读取的数据字节数 **********/
    remain = fp->obj.objsize - fp->fptr;
	if (btr > remain) btr = (UINT)remain;		/* Truncate btr by remaining bytes */

	for ( ;  btr;								/* Repeat until all data read */
		rbuff += rcnt, fp->fptr += rcnt, *br += rcnt, btr -= rcnt) {
		if (fp->fptr % SS(fs) == 0) {			/* On the sector boundary? */
			csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1));	/* Sector offset in the cluster */
			/* 够了一个正簇,需要找到文件的下一个簇的位置 */
			if (csect == 0) {					/* On the cluster boundary? */
				if (fp->fptr == 0) {			/* On the top of the file? */
					clst = fp->obj.sclust;		/* Follow cluster chain from the origin */
				} else {						/* Middle or end of the file */
					if (fp->cltbl) {
						clst = clmt_clust(fp, fp->fptr);	/* Get cluster# from the CLMT */
					} else
					{
						clst = get_fat(&fp->obj, fp->clust);	/* Follow cluster chain on the FAT */
					}
				}
				fp->clust = clst;				/* Update current cluster */
			}
			sect = clust2sect(fs, fp->clust);	/* Get current sector */
			sect += csect;
			cc = btr / SS(fs);					/* When remaining bytes >= sector size, */
            /************ 2.如果要读出的数据大于 1 个扇区,就先把整数扇区直接读入到用户的缓冲区 ****************/
			if (cc) {							/* Read maximum contiguous sectors directly */
				if (csect + cc > fs->csize) {	/* Clip at cluster boundary */
					cc = fs->csize - csect;
				}
				if (disk_read(fs->drv, rbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR);

				if ((fp->flag & FA_DIRTY) && fp->sect - sect < cc) {
					mem_cpy(rbuff + ((fp->sect - sect) * SS(fs)), fp->buf, SS(fs));
				}
				rcnt = SS(fs) * cc;				/* Number of bytes transferred */
				continue;
			}
            /* fp 缓冲区对应的 sect 值与要读的扇区不相同的话,先要把脏扇区回写到磁盘,再从磁盘把数据读入到 fp 对应的缓冲 */
			if (fp->sect != sect) {			/* Load data sector if not in cache */
				if (fp->flag & FA_DIRTY) {		/* Write-back dirty sector cache */
					if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
					fp->flag &= (BYTE)~FA_DIRTY;
				}
				if (disk_read(fs->drv, fp->buf, sect, 1) != RES_OK)	ABORT(fs, FR_DISK_ERR);	/* Fill sector cache */
			}
			fp->sect = sect;
		}
		rcnt = SS(fs) - (UINT)fp->fptr % SS(fs);	/* Number of bytes left in the sector */
		if (rcnt > btr) rcnt = btr;					/* Clip it by btr if needed */
        
        /******* 3.把不足一个扇区的内容,通过当前扇区的缓冲区,直接读入到用户缓冲区 ********/
		mem_cpy(rbuff, fp->buf + fp->fptr % SS(fs), rcnt);	/* Extract partial sector */
	}

	LEAVE_FF(fs, FR_OK);
}

读文件相对来说简单一些:

  • 找出文件所在的簇的起始位置,这个簇在 f_open()函数的时候已经被确认了。
  • 然后在把簇中的内容读取到用户的缓冲区。

核心思想总结

  • 在 f_open()函数的时候,fp->obj.objsize 中记录的文件的大小,fp->clust 记录了文件的起始簇号。
  • 每次读文件的时候都会更改 fp->fptr 的值,如果 fp->fptr 的值够了一个簇,但是要读取的大小还不足,此时就要找到文件对应的下一个簇的位置,然后更新 fp->clust。
  • 在文件描述符的 fp 对应的结构体中,sect 指向当前的读扇区,buf[]缓冲当前的扇区内容。

2.5 f_write 函数分析

下面 f_write()函数的源码:

FRESULT f_write (
	FIL* fp,			/* Pointer to the file object */
	const void* buff,	/* Pointer to the data to be written */
	UINT btw,			/* Number of bytes to write */
	UINT* bw			/* Pointer to number of bytes written */
)
{
	FRESULT res;
	FATFS *fs;
	DWORD clst, sect;
	UINT wcnt, cc, csect;
	const BYTE *wbuff = (const BYTE*)buff;


	for ( ;  btw;						
		wbuff += wcnt, fp->fptr += wcnt, fp->obj.objsize = (fp->fptr > fp->obj.objsize) ? fp->fptr : fp->obj.objsize, *bw += wcnt, btw -= wcnt) {
		if (fp->fptr % SS(fs) == 0) {		/* On the sector boundary? */
			csect = (UINT)(fp->fptr / SS(fs)) & (fs->csize - 1);	/* Sector offset in the cluster */
            /****** 1.如果当前簇已经写完了,那么就要再找出一个空闲的簇 ********/
            if (csect == 0) {				/* On the cluster boundary? */
				if (fp->fptr == 0) {		/* On the top of the file? */
					clst = fp->obj.sclust;	/* Follow from the origin */
					if (clst == 0) {		/* If no cluster is allocated, */
						clst = create_chain(&fp->obj, 0);	/* create a new cluster chain */
					}
				} else {					/* On the middle or end of the file */
					if (fp->cltbl) {
						clst = clmt_clust(fp, fp->fptr);	/* Get cluster# from the CLMT */
					} else

					{
						clst = create_chain(&fp->obj, fp->clust);	/* Follow or stretch cluster chain on the FAT */
					}
				}
				if (clst == 0) break;		/* Could not allocate a new cluster (disk full) */
				if (clst == 1) ABORT(fs, FR_INT_ERR);
				if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
				fp->clust = clst;			/* Update current cluster */
				if (fp->obj.sclust == 0) fp->obj.sclust = clst;	/* Set start cluster if the first write */
			}

			if (fp->flag & FA_DIRTY) {		/* Write-back sector cache */
				if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
				fp->flag &= (BYTE)~FA_DIRTY;
			}
            
			sect = clust2sect(fs, fp->clust);	/* Get current sector */
			if (!sect) ABORT(fs, FR_INT_ERR);
			sect += csect;
			cc = btw / SS(fs);				/* When remaining bytes >= sector size, */
            /******** 2.先把整数倍扇区的内容,直接通过用户 buffer 直接写入磁盘,然后更新 ********/
            if (cc) {						/* Write maximum contiguous sectors directly */
				if (csect + cc > fs->csize) {	/* Clip at cluster boundary */
					cc = fs->csize - csect;
				}
				if (disk_write(fs->drv, wbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR);
		        if (fp->sect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */
					mem_cpy(fp->buf, wbuff + ((fp->sect - sect) * SS(fs)), SS(fs));
					fp->flag &= (BYTE)~FA_DIRTY;
				}
				wcnt = SS(fs) * cc;		/* Number of bytes transferred */
				continue;
			}

			if (fp->sect != sect && 		/* Fill sector cache with file data */
				fp->fptr < fp->obj.objsize &&
				disk_read(fs->drv, fp->buf, sect, 1) != RES_OK) {
					ABORT(fs, FR_DISK_ERR);
			}

            /******* 3.更新 fp 的当前扇区的值 fp->sect *********/    
			fp->sect = sect;
		}
		wcnt = SS(fs) - (UINT)fp->fptr % SS(fs);	/* Number of bytes left in the sector */
		if (wcnt > btw) wcnt = btw;					/* Clip it by btw if needed */

        /****** 4.把不足 1 个扇区的内容写入到 fp->buf,然后标记 fp->flag“脏”,这样在有机会的时候就会把此扇区写入到磁盘 **********/
		mem_cpy(fp->buf + fp->fptr % SS(fs), wbuff, wcnt);	/* Fit data to the sector */
		fp->flag |= FA_DIRTY;
	}

	fp->flag |= FA_MODIFIED;				/* Set file change flag */

	LEAVE_FF(fs, FR_OK);
}

f_wirte()和否 _read()的过程大致相同:

  • 先把整扇区的内容,直接通过用户的 buffer 直接写入磁盘。
  • 再把不足整扇区的内容,写入 fp 对应的 buf。
  • 最后调整 fp->sect 的值。

核心思想总结

先把整扇区的内容直接写入到磁盘,把剩余部分写入 fp 的 buf,在之后合适的时机就会写入到对应的扇区,同时更新 fp->sect 的值。总之,写文件描述符 fb 的 sect 也是指向当前扇区,buf 里面存储的是当前 sect 的内容。

至此,完成 FatFs 文件系统顶层函数接口的分析。

扫码关注尚为网微信公众号

尚为网微信公众号
每天学习电路设计嵌入式系统的专业知识,关注一波,没准就用上了。

原创文章,作者:sunev,如若转载,请注明出处:https://www.sunev.cn/embedded/944.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2021年4月8日
下一篇 2021年4月12日

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注