fatfs文件系统详解之f_mount函数分析

本文深入剖析了磁盘挂载过程,重点介绍了f_mount函数如何读取MBR并初始化FATFS结构,以及find_volume函数在挂载过程中的关键作用。

前序

上一篇分析了格式化一个磁盘的时候发生了什么,在格式化一个磁盘之后,就要将磁盘进行挂载,“挂载”这个词听起来很抽象,但是在软件代码上,到底发生了什么?

 

分析假设

(1)假设一个磁盘就一个分区。

(2)只分析FAT32文件系统相关的代码。

(3)函数的大部分分析,都写入代码注释中

 

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结构图中,供以后使用。

 

### FatFs 文件系统函数列表及用法 #### 挂载文件系统 为了挂载文件系统,可以使用 `f_mount` 函数。此函数用于关联工作区 (FATFS 对象) 到逻辑驱动器并初始化它。 ```c #include "ff.h" // 定义一个 FATFS 类型的工作区变量 static FATFS fs; // 将工作区挂载到指定的物理盘符 '0' FRESULT res = f_mount(&fs, "", 1); if(res != FR_OK){ // 错误处理代码... } ``` 该过程允许应用程序通过调用其他 API 来访问 SD 卡上的文件和目录[^3]。 #### 显示SD卡信息 要获取已安装卷的信息,可利用 `f_getfree` 获取未分配簇的数量以及每簇字节数: ```c TCHAR path[] = "/"; /* 物理驱动器号 */ DWORD fre_clust; FSIZE_t total_bytes, free_bytes; res = f_getfree(path, &fre_clust, &total_bytes); /* 计算总空间与可用空间 */ free_bytes = ((uint64_t)(fre_clust)) * (_MAX_SS / 512); printf("Total space: %llu bytes\n", total_bytes); printf("Free space : %llu bytes\n", free_bytes); ``` 这段程序展示了如何计算整个存储设备的容量及其剩余可用量[^1]。 #### 读取/写入 TXT 文件 对于简单的文本文件操作,有专门设计来简化这些任务的方法——`f_open`, `f_read`, 和 `f_write`. 打开现有文件或创建新文件: ```c FIL file; /* 文件对象 */ res = f_open(&file, "example.txt", FA_CREATE_ALWAYS | FA_WRITE); if(res == FR_OK){ const char* text_to_write = "Hello world!"; UINT bw; res = f_write(&file, text_to_write, strlen(text_to_write), &bw); if(bw != strlen(text_to_write)){ // 写入失败后的错误处理... } } f_close(&file); ``` 上述例子说明了怎样向名为 example.txt 的文件中追加字符串 “Hello world!” 。如果目标文件已经存在则会被覆盖;否则将会被新建出来。 同样地,可以从现有的 .txt 文件里提取内容: ```c char buffer[128]; /* 缓冲区 */ UINT br; res = f_open(&file, "example.txt", FA_READ); if(res == FR_OK){ while(1){ res = f_read(&file, buffer, sizeof(buffer)-1, &br); if(res || !br) break; buffer[br] = '\0'; // 添加终止符以便打印输出 printf("%s", buffer); } f_close(&file); } ``` 这里实现了逐块读取直到遇到 EOF 或发生异常为止的功能。 #### 获取文件信息 当需要查询特定路径下的某个实体是否确实是一个常规文件而非目录时,应该考虑采用 `f_stat` 方法来进行验证。成功情况下返回的状态码将是 `FR_OK` 并填充给定指针指向的数据结构 (`FILINFO`) 中包含的时间戳和其他元数据字段。 ```c FILINFO fno; res = f_stat("path/to/file_or_directory", &fno); switch(res){ case FR_OK: if(fno.fattrib & AM_DIR){ /* 是目录 */ puts("It's a directory."); }else{ /* 否则是普通文件 */ printf("File size is %u bytes.\n", fno.fsize); } break; default: perror("Error occurred"); } ``` 以上片段解释了怎么判断某项资源究竟是什么类型的节点,并且如果是文件的话还会报告其尺寸大小[^2]。 #### 扫描文件列表 遍历当前目录下所有的条目可以通过循环调用 `f_readdir` 实现。每次迭代都会更新传递过来的对象实例中的成员值以反映下一个匹配项的相关详情直至到达末尾位置(`""`)。 ```c DIR dir; struct _finddata_t ent; res = f_opendir(&dir, "."); if(res == FR_OK){ do{ res = f_readdir(&dir, &ent); if(!strcmp(ent.name,"")) continue; // 跳过根目录本身 if(ent.attrib & AM_DIR){ printf("[D]%s/\n", ent.name); }else{ printf("-%s (%lu)\n", ent.name, ent.size); } }while(res == FR_OK && strcmp(ent.name,"")); f_closedir(&dir); } ``` 本段脚本示范了一种方法去列举出所有直接位于当前工作目录里的项目名称连同它们各自的属性标记一起展示出来。 #### 删除文件 最后一点就是移除不再需要使用的单个文件可通过简单的一行命令完成 —— 使用 `f_unlink` : ```c res = f_unlink("old_file.txt"); if(res == FR_OK){ puts("Successfully deleted old_file.txt!"); }else{ perror("Failed to delete the specified file."); } ``` 这将尝试永久性地从磁盘上去掉所指示的目标文件。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值