目录

FATFS学习3.2ff.cf_open

目录

FATFS学习(3.2):ff.c(f_open)

一:f_open

FRESULT f_open (
	FIL* fp,			/* Pointer to the blank file object */
	const TCHAR* path,	/* Pointer to the file name */
	BYTE mode			/* Access mode and open mode flags */
)
{
	FRESULT res;
	DIR dj;
	FATFS *fs;
#if !FF_FS_READONLY
	DWORD cl, bcs, clst, tm;
	LBA_t sc;
	FSIZE_t ofs;
#endif
	DEF_NAMBUF


	if (!fp) return FR_INVALID_OBJECT;

	/* Get logical drive number */
	mode &= FF_FS_READONLY ? FA_READ : FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_CREATE_NEW | FA_OPEN_ALWAYS | FA_OPEN_APPEND;
	res = mount_volume(&path, &fs, mode);
	if (res == FR_OK) {
		dj.obj.fs = fs;
		INIT_NAMBUF(fs);
		res = follow_path(&dj, path);	/* Follow the file path */
#if !FF_FS_READONLY	/* Read/Write configuration */
		if (res == FR_OK) {
			if (dj.fn[NSFLAG] & NS_NONAME) {	/* Origin directory itself? */
				res = FR_INVALID_NAME;
			}
#if FF_FS_LOCK
			else {
				res = chk_share(&dj, (mode & ~FA_READ) ? 1 : 0);	/* Check if the file can be used */
			}
#endif
		}
		/* Create or Open a file */
		if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) {
			if (res != FR_OK) {					/* No file, create new */
				if (res == FR_NO_FILE) {		/* There is no file to open, create a new entry */
#if FF_FS_LOCK
					res = enq_share() ? dir_register(&dj) : FR_TOO_MANY_OPEN_FILES;
#else
					res = dir_register(&dj);
#endif
				}
				mode |= FA_CREATE_ALWAYS;		/* File is created */
			}
			else {								/* Any object with the same name is already existing */
				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)) {	/* Truncate the file if overwrite mode */
#if FF_FS_EXFAT
				if (fs->fs_type == FS_EXFAT) {
					/* Get current allocation info */
					fp->obj.fs = fs;
					init_alloc_info(fs, &fp->obj);
					/* Set directory entry block initial state */
					memset(fs->dirbuf + 2, 0, 30);	/* Clear 85 entry except for NumSec */
					memset(fs->dirbuf + 38, 0, 26);	/* Clear C0 entry except for NumName and NameHash */
					fs->dirbuf[XDIR_Attr] = AM_ARC;
					st_dword(fs->dirbuf + XDIR_CrtTime, GET_FATTIME());
					fs->dirbuf[XDIR_GenFlags] = 1;
					res = store_xdir(&dj);
					if (res == FR_OK && fp->obj.sclust != 0) {	/* Remove the cluster chain if exist */
						res = remove_chain(&fp->obj, fp->obj.sclust, 0);
						fs->last_clst = fp->obj.sclust - 1;		/* Reuse the cluster hole */
					}
				} else
#endif
				{
					/* Set directory entry initial state */
					tm = GET_FATTIME();					/* Set created time */
					st_dword(dj.dir + DIR_CrtTime, tm);
					st_dword(dj.dir + DIR_ModTime, tm);
					cl = ld_clust(fs, dj.dir);			/* Get current cluster chain */
					dj.dir[DIR_Attr] = AM_ARC;			/* Reset attribute */
					st_clust(fs, dj.dir, 0);			/* Reset file allocation info */
					st_dword(dj.dir + DIR_FileSize, 0);
					fs->wflag = 1;
					if (cl != 0) {						/* Remove the cluster chain if exist */
						sc = fs->winsect;
						res = remove_chain(&dj.obj, cl, 0);
						if (res == FR_OK) {
							res = move_window(fs, sc);
							fs->last_clst = cl - 1;		/* Reuse the cluster hole */
						}
					}
				}
			}
		}
		else {	/* Open an existing file */
			if (res == FR_OK) {					/* Is the object exsiting? */
				if (dj.obj.attr & AM_DIR) {		/* File open against a directory */
					res = FR_NO_FILE;
				} else {
					if ((mode & FA_WRITE) && (dj.obj.attr & AM_RDO)) { /* Write mode open against R/O file */
						res = FR_DENIED;
					}
				}
			}
		}
		if (res == FR_OK) {
			if (mode & FA_CREATE_ALWAYS) mode |= FA_MODIFIED;	/* Set file change flag if created or overwritten */
			fp->dir_sect = fs->winsect;			/* Pointer to the directory entry */
			fp->dir_ptr = dj.dir;
#if FF_FS_LOCK
			fp->obj.lockid = inc_share(&dj, (mode & ~FA_READ) ? 1 : 0);	/* Lock the file for this session */
			if (fp->obj.lockid == 0) res = FR_INT_ERR;
#endif
		}
#else		/* R/O configuration */
		if (res == FR_OK) {
			if (dj.fn[NSFLAG] & NS_NONAME) {	/* Is it origin directory itself? */
				res = FR_INVALID_NAME;
			} else {
				if (dj.obj.attr & AM_DIR) {		/* Is it a directory? */
					res = FR_NO_FILE;
				}
			}
		}
#endif

		if (res == FR_OK) {
#if FF_FS_EXFAT
			if (fs->fs_type == FS_EXFAT) {
				fp->obj.c_scl = dj.obj.sclust;							/* Get containing directory info */
				fp->obj.c_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat;
				fp->obj.c_ofs = dj.blk_ofs;
				init_alloc_info(fs, &fp->obj);
			} else
#endif
			{
				fp->obj.sclust = ld_clust(fs, dj.dir);					/* Get object allocation info */
				fp->obj.objsize = ld_dword(dj.dir + DIR_FileSize);
			}
#if FF_USE_FASTSEEK
			fp->cltbl = 0;		/* Disable fast seek mode */
#endif
			fp->obj.fs = fs;	/* Validate the file object */
			fp->obj.id = fs->id;
			fp->flag = mode;	/* Set file access mode */
			fp->err = 0;		/* Clear error flag */
			fp->sect = 0;		/* Invalidate current data sector */
			fp->fptr = 0;		/* Set file pointer top of the file */
#if !FF_FS_READONLY
#if !FF_FS_TINY
			memset(fp->buf, 0, sizeof fp->buf);	/* Clear sector buffer */
#endif
			if ((mode & FA_SEEKEND) && fp->obj.objsize > 0) {	/* Seek to end of file if FA_OPEN_APPEND is specified */
				fp->fptr = fp->obj.objsize;			/* Offset to seek */
				bcs = (DWORD)fs->csize * SS(fs);	/* Cluster size in byte */
				clst = fp->obj.sclust;				/* Follow the cluster chain */
				for (ofs = fp->obj.objsize; res == FR_OK && ofs > bcs; ofs -= bcs) {
					clst = get_fat(&fp->obj, clst);
					if (clst <= 1) res = FR_INT_ERR;
					if (clst == 0xFFFFFFFF) res = FR_DISK_ERR;
				}
				fp->clust = clst;
				if (res == FR_OK && ofs % SS(fs)) {	/* Fill sector buffer if not on the sector boundary */
					sc = clst2sect(fs, clst);
					if (sc == 0) {
						res = FR_INT_ERR;
					} else {
						fp->sect = sc + (DWORD)(ofs / SS(fs));
#if !FF_FS_TINY
						if (disk_read(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) res = FR_DISK_ERR;
#endif
					}
				}
#if FF_FS_LOCK
				if (res != FR_OK) dec_share(fp->obj.lockid); /* Decrement file open counter if seek failed */
#endif
			}
#endif
		}

		FREE_NAMBUF();
	}

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

	LEAVE_FF(fs, res);
}

整体思考:

f_open 函数的主要功能是打开或创建文件,根据不同的模式(如读、写、创建新文件等)进行操作。函数参数包括指向文件对象的指针、路径字符串和访问模式。返回值为操作结果(FRESULT类型)。

接下来,我需要理解代码中的各个变量和宏定义的作用。例如,DIR dj 用于目录操作,FATFS *fs 指向文件系统对象,还有一些条件编译的宏(如 FF_FS_READONLYFF_FS_EXFAT 等),这些宏会影响代码的编译路径,需要特别注意。

然后,我会逐行分析代码的执行流程:

  1. 参数检查:检查 fp 是否为NULL,如果是则返回 FR_INVALID_OBJECT。这是基本的有效性检查,确保传入的文件对象指针有效。

  2. 获取逻辑驱动器号并挂载卷:通过 mount_volume 函数挂载对应的文件系统,获取文件系统对象 fs。这里会根据访问模式 mode 进行相应的挂载操作,可能涉及权限检查。

  3. 路径解析和目录遍历:使用 follow_path 函数解析路径,找到目标文件或目录。如果路径无效或文件不存在,这里会返回错误。

  4. 处理不同打开模式

  • 创建文件(如 FA_CREATE_ALWAYSFA_CREATE_NEW):检查文件是否已存在,若存在且不允许覆盖则返回错误;否则创建新文件条目。

  • 打开现有文件:检查文件属性(如是否为目录、是否只读),确保访问模式合法。

  1. 初始化文件对象:设置文件对象的各个字段,如簇号、文件大小、访问模式等。这里可能涉及簇链的遍历(如快速定位到文件末尾的 FA_OPEN_APPEND 模式)。

  2. 错误处理和资源释放:在出现错误时,释放可能分配的资源,并标记文件对象为无效。

在分析过程中,我需要特别注意条件编译的部分,例如 FF_FS_READONLY 表示只读文件系统,此时某些写操作相关的代码会被排除。此外,FF_FS_EXFAT 支持exFAT文件系统,相关的处理逻辑会有所不同。

接下来,我需要关注几个关键点:

  • 路径解析和目录遍历follow_path 如何解析路径,处理目录层级,找到目标文件或目录。这部分可能涉及目录项(directory entry)的查找和验证。

  • 文件创建过程:如何注册新的目录项(dir_register),处理文件属性的设置,以及簇链的初始化。例如,在创建文件时,可能需要分配初始簇并更新FAT表。

  • 簇链管理:在打开现有文件时,如何获取文件的起始簇号,以及如何快速定位到文件末尾(如 FA_OPEN_APPEND 模式下的处理)。

  • 错误处理机制:如何检测和处理各种错误情况,如权限不足、磁盘错误、无效路径等,并确保资源正确释放。

此外,需要注意线程安全和共享访问的问题,例如 FF_FS_LOCK 宏控制下的文件锁定机制,防止多线程同时修改同一文件导致数据不一致。

在分析过程中,可能会遇到一些不熟悉的函数或宏,例如 DEF_NAMBUFINIT_NAMBUFFREE_NAMBUF,这些可能与长文件名支持相关,需要结合FATFS的文档或源码其他部分来理解其作用。

最后,总结时需要将代码的功能、关键流程、设计模式、优化策略以及配置选项(条件编译)等方面整理出来,帮助用户全面理解 f_open 的实现细节。

1. 函数功能

f_open 是 FATFS 文件系统库的核心函数,用于 打开或创建文件 ,支持多种访问模式(读、写、追加、新建等)。主要功能包括:

  • 路径解析 :定位目标文件或目录。
  • 文件创建 :在指定路径创建新文件(若允许)。
  • 文件打开 :加载现有文件的元数据(如簇链、大小)。
  • 权限检查 :验证访问模式与文件属性的兼容性(如只读文件不可写)。
  • 资源初始化 :设置文件对象( FIL )的字段,准备后续读写操作。

2. 参数说明

参数类型说明
fpFIL*指向空白文件对象的指针,用于存储打开文件后的状态。
pathconst TCHAR*文件路径(如 "0:/dir/file.txt" )。
modeBYTE访问模式(如 FA_READFA_WRITE )和创建标志(如 FA_CREATE_NEW )。

3. 核心逻辑流程

步骤 1:参数校验与卷挂载
if (!fp) return FR_INVALID_OBJECT;                     // 检查文件对象指针有效性
res = mount_volume(&path, &fs, mode);                  // 挂载逻辑驱动器,获取文件系统对象
if (res != FR_OK) LEAVE_FF(fs, res);                   // 挂载失败则退出
  • 关键点 :确保文件系统已挂载,并获取对应的 FATFS 对象。

步骤 2:路径解析与目录遍历
dj.obj.fs = fs;                                        // 初始化目录对象
INIT_NAMBUF(fs);                                       // 初始化长文件名缓冲区(若支持)
res = follow_path(&dj, path);                          // 解析路径,定位目标文件/目录
  • follow_path :逐级解析路径,遍历目录项(directory entry),找到目标文件。
  • 错误处理 :路径无效( FR_NO_FILE )、权限不足( FR_DENIED )等。

步骤 3:处理不同打开模式
(a) 创建文件( FA_CREATE_ALWAYS / FA_CREATE_NEW
if (res == FR_NO_FILE) {                               // 目标文件不存在
    res = dir_register(&dj);                           // 创建新目录项
    mode |= FA_CREATE_ALWAYS;                          // 标记为已创建
} else if (res == FR_OK) {                             // 文件已存在
    if (mode & FA_CREATE_NEW) res = FR_EXIST;          // 不允许覆盖,返回存在错误
    else if (目标为只读/目录) res = FR_DENIED;           // 不可覆盖只读文件或目录
    else 截断文件内容;                                   // 清空文件(FA_CREATE_ALWAYS)
}
  • dir_register :在目录中注册新文件条目,分配初始簇(若需要)。
  • 截断文件 :调用 remove_chain 清空原有簇链,重置文件大小为 0。
(b) 打开现有文件
if (dj.obj.attr & AM_DIR) res = FR_NO_FILE;            // 路径指向目录,非文件
if ((mode & FA_WRITE) && (dj.obj.attr & AM_RDO))       // 写模式打开只读文件
    res = FR_DENIED;

步骤 4:初始化文件对象
fp->obj.sclust = ld_clust(fs, dj.dir);                 // 获取起始簇号
fp->obj.objsize = ld_dword(dj.dir + DIR_FileSize);     // 获取文件大小
fp->flag = mode;                                       // 设置访问模式
fp->fptr = 0;                                          // 文件指针初始化为 0
  • exFAT 处理 :若启用 FF_FS_EXFAT ,额外初始化扩展属性。
  • 快速定位 :若启用 FF_USE_FASTSEEK ,初始化 CLMT(簇链映射表)。

步骤 5:处理追加模式( FA_OPEN_APPEND
if (mode & FA_SEEKEND) {                               // 追加模式:定位到文件末尾
    fp->fptr = fp->obj.objsize;                        // 文件指针设为文件大小
    遍历簇链到末尾,更新 fp->clust;                      // 记录最后一个簇号
    预读末尾扇区到缓存(若非对齐);                       // 优化后续写入性能
}
  • 簇链遍历 :通过 get_fat 逐簇查找,直至文件末尾。

步骤 6:错误处理与资源释放
if (res != FR_OK) {
    fp->obj.fs = 0;                                    // 标记文件对象无效
#if FF_FS_LOCK
    dec_share(fp->obj.lockid);                         // 释放文件锁
#endif
}
FREE_NAMBUF();                                         // 释放长文件名缓冲区
  • 关键点 :确保在错误时释放所有临时资源,避免内存泄漏。

4. 关键设计思想

(1) 路径解析与目录遍历
  • follow_path 函数

    递归解析路径,逐级查找目录项。支持长文件名( FF_USE_LFN )和短文件名。

  • 目录项缓存

    使用 dj.dir 缓存当前目录项,减少磁盘访问次数。

(2) 文件创建与截断
  • 目录项注册

    dir_register 在目录中创建新条目,分配初始簇(若需要),更新 FAT 表。

  • 簇链管理

    remove_chain 清空文件的簇链,释放磁盘空间(截断操作)。

(3) 文件打开优化
  • 追加模式预读

    FA_OPEN_APPEND 模式下预读文件末尾扇区,减少后续写入的寻址开销。

  • 缓存初始化

    文件对象( FIL )的 buf 字段预读初始扇区,加速后续读写。


5. 条件编译与配置

宏定义功能说明
FF_FS_READONLY禁用写操作相关代码(如文件创建、截断)。
FF_FS_EXFAT支持 exFAT 文件系统,扩展目录项处理逻辑。
FF_USE_LFN启用长文件名支持,需 DEF_NAMBUF 管理缓冲区。
FF_FS_LOCK文件锁定机制,防止多任务并发访问冲突。
FF_USE_FASTSEEK启用 CLMT 快速定位,优化大文件随机访问。

6. 错误处理机制

  • 错误码分类

    • 参数错误( FR_INVALID_OBJECTFR_INVALID_NAME )。
    • 权限错误( FR_DENIED )。
    • 磁盘错误( FR_DISK_ERR )。
    • 文件状态错误( FR_NO_FILEFR_EXIST )。
  • 资源回滚

    在错误路径中释放已分配的簇、目录项、锁等资源。


7. 示例场景

场景 1:创建新文件
FIL fil;
f_open(&fil, "0:/newfile.txt", FA_WRITE | FA_CREATE_NEW);
  • 流程

    1. 解析路径,确认文件不存在。
    2. 注册新目录项,分配初始簇。
    3. 初始化文件对象,设置写模式。
场景 2:追加写入现有文件
f_open(&fil, "0:/data.log", FA_WRITE | FA_OPEN_APPEND);
  • 流程

    1. 解析路径,加载文件元数据。
    2. 遍历簇链至末尾,定位最后一个扇区。
    3. 预读末尾扇区到缓存,准备追加写入。

8. 总结

特性说明
功能完整性支持多种打开模式(读、写、创建、追加),兼容 exFAT。
性能优化预读末尾扇区、簇链缓存、条件编译减少开销。
错误鲁棒性严格校验参数与状态,确保资源正确释放。
可配置性通过宏控制功能模块(如长文件名、文件锁、只读模式)。
跨平台适配依赖底层 disk_read / disk_write 接口,便于移植到不同存储设备。

通过 f_open 函数,FATFS 实现了高效灵活的文件访问控制,适用于嵌入式系统对文件操作的高可靠性和低资源消耗需求。

二:引用重要函数

1.mount_volume

/*-----------------------------------------------------------------------*/
/* Determine logical drive number and mount the volume if needed         */
/*-----------------------------------------------------------------------*/

static FRESULT mount_volume (	/* FR_OK(0): successful, !=0: an error occurred */
	const TCHAR** path,			/* Pointer to pointer to the path name (drive number) */
	FATFS** rfs,				/* Pointer to pointer to the found filesystem object */
	BYTE mode					/* Desiered access mode to check write protection */
)
{
	int vol;
	FATFS *fs;
	DSTATUS stat;
	LBA_t bsect;
	DWORD tsect, sysect, fasize, nclst, szbfat;
	WORD nrsv;
	UINT fmt;


	/* Get logical drive number */
	*rfs = 0;
	vol = get_ldnumber(path);
	if (vol < 0) return FR_INVALID_DRIVE;

	/* Check if the filesystem object is valid or not */
	fs = FatFs[vol];					/* Get pointer to the filesystem object */
	if (!fs) return FR_NOT_ENABLED;		/* Is the filesystem object available? */
#if FF_FS_REENTRANT
	if (!lock_volume(fs, 1)) return FR_TIMEOUT;	/* Lock the volume, and system if needed */
#endif
	*rfs = fs;							/* Return pointer to the filesystem object */

	mode &= (BYTE)~FA_READ;				/* Desired access mode, write access or not */
	if (fs->fs_type != 0) {				/* If the volume has been mounted */
		stat = disk_status(fs->pdrv);
		if (!(stat & STA_NOINIT)) {		/* and the physical drive is kept initialized */
			if (!FF_FS_READONLY && mode && (stat & STA_PROTECT)) {	/* Check write protection if needed */
				return FR_WRITE_PROTECTED;
			}
			return FR_OK;				/* The filesystem object is already valid */
		}
	}

	/* The filesystem object is not valid. */
	/* Following code attempts to mount the volume. (find an FAT volume, analyze the BPB and initialize the filesystem object) */

	fs->fs_type = 0;					/* Invalidate the filesystem object */
	stat = disk_initialize(fs->pdrv);	/* Initialize the volume hosting 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 (!FF_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check disk write protection if needed */
		return FR_WRITE_PROTECTED;
	}
#if FF_MAX_SS != FF_MIN_SS				/* Get sector size (multiple sector size cfg only) */
	if (disk_ioctl(fs->pdrv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK) return FR_DISK_ERR;
	if (SS(fs) > FF_MAX_SS || SS(fs) < FF_MIN_SS || (SS(fs) & (SS(fs) - 1))) return FR_DISK_ERR;
#endif

	/* Find an FAT volume on the hosting drive */
	fmt = find_volume(fs, LD2PT(vol));
	if (fmt == 4) return FR_DISK_ERR;		/* An error occurred in the disk I/O layer */
	if (fmt >= 2) return FR_NO_FILESYSTEM;	/* No FAT volume is found */
	bsect = fs->winsect;					/* Volume offset in the hosting physical drive */

	/* An FAT volume is found (bsect). Following code initializes the filesystem object */

#if FF_FS_EXFAT
	if (fmt == 1) {
		QWORD maxlba;
		DWORD so, cv, bcl, i;

		for (i = BPB_ZeroedEx; i < BPB_ZeroedEx + 53 && fs->win[i] == 0; i++) ;	/* Check zero filler */
		if (i < BPB_ZeroedEx + 53) return FR_NO_FILESYSTEM;

		if (ld_word(fs->win + BPB_FSVerEx) != 0x100) return FR_NO_FILESYSTEM;	/* Check exFAT version (must be version 1.0) */

		if (1 << fs->win[BPB_BytsPerSecEx] != SS(fs)) {	/* (BPB_BytsPerSecEx must be equal to the physical sector size) */
			return FR_NO_FILESYSTEM;
		}

		maxlba = ld_qword(fs->win + BPB_TotSecEx) + bsect;	/* Last LBA of the volume + 1 */
		if (!FF_LBA64 && maxlba >= 0x100000000) return FR_NO_FILESYSTEM;	/* (It cannot be accessed in 32-bit LBA) */

		fs->fsize = ld_dword(fs->win + BPB_FatSzEx);	/* Number of sectors per FAT */

		fs->n_fats = fs->win[BPB_NumFATsEx];			/* Number of FATs */
		if (fs->n_fats != 1) return FR_NO_FILESYSTEM;	/* (Supports only 1 FAT) */

		fs->csize = 1 << fs->win[BPB_SecPerClusEx];		/* Cluster size */
		if (fs->csize == 0)	return FR_NO_FILESYSTEM;	/* (Must be 1..32768 sectors) */

		nclst = ld_dword(fs->win + BPB_NumClusEx);		/* Number of clusters */
		if (nclst > MAX_EXFAT) return FR_NO_FILESYSTEM;	/* (Too many clusters) */
		fs->n_fatent = nclst + 2;

		/* Boundaries and Limits */
		fs->volbase = bsect;
		fs->database = bsect + ld_dword(fs->win + BPB_DataOfsEx);
		fs->fatbase = bsect + ld_dword(fs->win + BPB_FatOfsEx);
		if (maxlba < (QWORD)fs->database + nclst * fs->csize) return FR_NO_FILESYSTEM;	/* (Volume size must not be smaller than the size required) */
		fs->dirbase = ld_dword(fs->win + BPB_RootClusEx);

		/* Get bitmap location and check if it is contiguous (implementation assumption) */
		so = i = 0;
		for (;;) {	/* Find the bitmap entry in the root directory (in only first cluster) */
			if (i == 0) {
				if (so >= fs->csize) return FR_NO_FILESYSTEM;	/* Not found? */
				if (move_window(fs, clst2sect(fs, (DWORD)fs->dirbase) + so) != FR_OK) return FR_DISK_ERR;
				so++;
			}
			if (fs->win[i] == ET_BITMAP) break;			/* Is it a bitmap entry? */
			i = (i + SZDIRE) % SS(fs);	/* Next entry */
		}
		bcl = ld_dword(fs->win + i + 20);				/* Bitmap cluster */
		if (bcl < 2 || bcl >= fs->n_fatent) return FR_NO_FILESYSTEM;	/* (Wrong cluster#) */
		fs->bitbase = fs->database + fs->csize * (bcl - 2);	/* Bitmap sector */
		for (;;) {	/* Check if bitmap is contiguous */
			if (move_window(fs, fs->fatbase + bcl / (SS(fs) / 4)) != FR_OK) return FR_DISK_ERR;
			cv = ld_dword(fs->win + bcl % (SS(fs) / 4) * 4);
			if (cv == 0xFFFFFFFF) break;				/* Last link? */
			if (cv != ++bcl) return FR_NO_FILESYSTEM;	/* Fragmented bitmap? */
		}

#if !FF_FS_READONLY
		fs->last_clst = fs->free_clst = 0xFFFFFFFF;		/* Initialize cluster allocation information */
#endif
		fmt = FS_EXFAT;			/* FAT sub-type */
	} else
#endif	/* FF_FS_EXFAT */
	{
		if (ld_word(fs->win + BPB_BytsPerSec) != SS(fs)) return FR_NO_FILESYSTEM;	/* (BPB_BytsPerSec must be equal to the physical sector size) */

		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 = 0;
		if (nclst <= MAX_FAT32) fmt = FS_FAT32;
		if (nclst <= MAX_FAT16) fmt = FS_FAT16;
		if (nclst <= MAX_FAT12) fmt = FS_FAT12;
		if (fmt == 0) return FR_NO_FILESYSTEM;

		/* 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 != 0) 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 !FF_FS_READONLY
		/* Get FSInfo if available */
		fs->last_clst = fs->free_clst = 0xFFFFFFFF;		/* Initialize cluster allocation information */
		fs->fsi_flag = 0x80;
#if (FF_FS_NOFSINFO & 3) != 3
		if (fmt == FS_FAT32				/* Allow to update FSInfo only if 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 (FF_FS_NOFSINFO & 1) == 0
				fs->free_clst = ld_dword(fs->win + FSI_Free_Count);
#endif
#if (FF_FS_NOFSINFO & 2) == 0
				fs->last_clst = ld_dword(fs->win + FSI_Nxt_Free);
#endif
			}
		}
#endif	/* (FF_FS_NOFSINFO & 3) != 3 */
#endif	/* !FF_FS_READONLY */
	}

	fs->fs_type = (BYTE)fmt;/* FAT sub-type (the filesystem object gets valid) */
	fs->id = ++Fsid;		/* Volume mount ID */
#if FF_USE_LFN == 1
	fs->lfnbuf = LfnBuf;	/* Static LFN working buffer */
#if FF_FS_EXFAT
	fs->dirbuf = DirBuf;	/* Static directory block scratchpad buuffer */
#endif
#endif
#if FF_FS_RPATH != 0
	fs->cdir = 0;			/* Initialize current directory */
#endif
#if FF_FS_LOCK				/* Clear file lock semaphores */
	clear_share(fs);
#endif
	return FR_OK;
}

1. 函数功能

mount_volume 是 FATFS 文件系统库的核心函数,负责 挂载逻辑驱动器并初始化文件系统对象 。主要功能包括:

  • 逻辑驱动器解析 :通过路径确定逻辑驱动器号。
  • 物理驱动器初始化 :检测存储介质并准备访问。
  • 文件系统识别 :分析引导扇区(BPB)以确定 FAT/exFAT 类型。
  • 文件系统对象初始化 :填充 FATFS 结构体,记录卷参数(如簇大小、FAT表位置)。
  • 错误处理 :检测无效卷、磁盘错误、写保护等。

2. 输入输出

参数类型说明
pathconst TCHAR**路径指针(用于提取逻辑驱动器号,如 "0:/file.txt" → 驱动器0)。
rfsFATFS**输出参数,返回挂载的文件系统对象指针。
modeBYTE访问模式(用于检查写保护,如 FA_WRITE )。
返回值FRESULT操作结果(如 FR_OKFR_NO_FILESYSTEM )。

3. 核心逻辑流程

步骤 1:获取逻辑驱动器号
vol = get_ldnumber(path);          // 解析路径获取驱动器号
if (vol < 0) return FR_INVALID_DRIVE;
fs = FatFs[vol];                   // 获取对应的文件系统对象
if (!fs) return FR_NOT_ENABLED;    // 驱动器未启用
  • 关键点 :驱动器号有效性检查与文件系统对象绑定。

步骤 2:检查卷是否已挂载
if (fs->fs_type != 0) {            // 卷已挂载
    stat = disk_status(fs->pdrv);  // 检查物理驱动器状态
    if (!(stat & STA_NOINIT)) {    // 驱动器已初始化
        if (需写访问 && 写保护) return FR_WRITE_PROTECTED;
        return FR_OK;              // 直接返回成功
    }
}
  • 优化 :避免重复挂载已初始化的卷。

步骤 3:初始化物理驱动器
stat = disk_initialize(fs->pdrv);  // 初始化物理驱动器(如SD卡)
if (stat & STA_NOINIT)             // 初始化失败(无介质或错误)
    return FR_NOT_READY;
if (需写访问 && 写保护)             // 检查写保护
    return FR_WRITE_PROTECTED;
  • 关键操作 :底层存储介质的准备。

步骤 4:读取并验证文件系统
fmt = find_volume(fs, LD2PT(vol)); // 查找并验证FAT/exFAT卷
if (fmt == 4) return FR_DISK_ERR;  // 磁盘错误
if (fmt >= 2) return FR_NO_FILESYSTEM; // 无效文件系统
  • find_volume :读取引导扇区,检查签名(如 55 AA ),确定文件系统类型。

步骤 5:解析 exFAT 文件系统
if (fmt == 1) {                    // exFAT处理
    验证exFAT版本、扇区大小、簇大小;
    计算卷参数(volbase, fatbase, database, dirbase;
    检查位图连续性;
    fmt = FS_EXFAT;
}
  • exFAT 特性 :位图管理空闲簇,目录项结构更复杂。

步骤 6:解析 FAT12/16/32 文件系统
else {                             // FAT12/16/32处理
    解析BPB字段(扇区大小、FAT表数量、保留扇区等);
    计算数据区起始、簇数量;
    确定FAT类型FAT12/16/32;
    加载FSInfoFAT32优化;
}
  • 关键计算sysect = nrsv + fasize + rootdir_sectors

步骤 7:初始化文件系统对象
fs->fs_type = fmt;                // 设置文件系统类型
fs->id = ++Fsid;                  // 挂载ID(用于缓存失效)
初始化LFN缓冲区、当前目录、文件锁;
  • 状态标记fs_type 标记卷类型, id 防止多挂载冲突。

4. 关键设计思想

(1) 多文件系统支持
  • FAT12/16/32

    通过传统BPB字段解析,支持小容量存储。

  • exFAT

    扩展引导扇区结构,优化大文件和大容量存储。

(2) 性能优化
  • FSInfo 缓存

    在FAT32中缓存空闲簇和最后分配的簇,加速簇分配。

  • 位图连续性检查

    exFAT要求位图连续存储,简化空闲簇管理。

(3) 错误鲁棒性
  • 多重校验

    检查扇区对齐( SS(fs) )、簇大小(2的幂)、FAT表数量(1或2)。

  • 写保护处理

    根据访问模式提前返回错误,避免无效操作。


5. 条件编译与配置

宏定义功能说明
FF_FS_EXFAT启用exFAT支持,增加引导扇区解析和位图管理逻辑。
FF_FS_READONLY禁用写相关操作(如FSInfo更新)。
FF_USE_LFN长文件名支持,需静态缓冲区 LfnBuf
FF_FS_REENTRANT可重入支持,通过锁机制防止多线程冲突。

6. 错误处理机制

  • 常见错误码

    • FR_INVALID_DRIVE :无效逻辑驱动器号。
    • FR_NO_FILESYSTEM :无有效FAT/exFAT卷。
    • FR_DISK_ERR :磁盘I/O错误。
    • FR_WRITE_PROTECTED :写保护冲突。
  • 错误传播

    所有底层操作( disk_readdisk_initialize )的错误均向上传递。


7. 示例流程

挂载FAT32卷
  1. 解析路径 "0:/data.txt" ,获取驱动器号0。
  2. 检查驱动器0未挂载,初始化SD卡。
  3. 读取引导扇区,验证BPB字段。
  4. 计算数据区起始、簇数量,确定FAT32类型。
  5. 加载FSInfo,记录空闲簇。
  6. 初始化 FATFS 对象,返回成功。
挂载exFAT卷
  1. 解析路径 "1:/video.mp4" ,获取驱动器号1。
  2. 初始化USB驱动器,读取exFAT引导扇区。
  3. 验证exFAT版本、扇区大小、位图连续性。
  4. 初始化 FATFS 对象,设置位图起始位置。
  5. 返回成功,准备文件操作。

8. 总结

特性说明
功能完整性支持FAT12/16/32/exFAT,兼容多种存储设备。
健壮性严格的参数校验和错误处理,确保卷合法性。
性能优化缓存关键信息(FSInfo、位图),减少磁盘访问。
可配置性通过宏灵活裁剪功能(如只读、exFAT、长文件名)。
跨平台适配依赖底层 disk_initializedisk_read ,易于移植到不同硬件。

通过 mount_volume 函数,FATFS 实现了高效、可靠的文件系统挂载机制,为后续文件操作(如打开、读写)奠定基础,尤其适合资源受限但需求多样的嵌入式系统。

2.follow_path

/*-----------------------------------------------------------------------*/
/* Follow a file 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;
	FATFS *fs = dp->obj.fs;


#if FF_FS_RPATH != 0
	if (!IsSeparator(*path) && (FF_STR_VOLUME_ID != 2 || !IsTerminator(*path))) {	/* Without heading separator */
		dp->obj.sclust = fs->cdir;			/* Start at the current directory */
	} else
#endif
	{										/* With heading separator */
		while (IsSeparator(*path)) path++;	/* Strip separators */
		dp->obj.sclust = 0;					/* Start from the root directory */
	}
#if FF_FS_EXFAT
	dp->obj.n_frag = 0;	/* Invalidate last fragment counter of the object */
#if FF_FS_RPATH != 0
	if (fs->fs_type == FS_EXFAT && dp->obj.sclust) {	/* exFAT: Retrieve the sub-directory's status */
		DIR dj;

		dp->obj.c_scl = fs->cdc_scl;
		dp->obj.c_size = fs->cdc_size;
		dp->obj.c_ofs = fs->cdc_ofs;
		res = load_obj_xdir(&dj, &dp->obj);
		if (res != FR_OK) return res;
		dp->obj.objsize = ld_dword(fs->dirbuf + XDIR_FileSize);
		dp->obj.stat = fs->dirbuf[XDIR_GenFlags] & 2;
	}
#endif
#endif

	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 (;;) {
			res = create_name(dp, &path);	/* Get a segment name of the path */
			if (res != FR_OK) break;
			res = dir_find(dp);				/* Find an object with the segment name */
			ns = dp->fn[NSFLAG];
			if (res != FR_OK) {				/* Failed to find the object */
				if (res == FR_NO_FILE) {	/* Object is not found */
					if (FF_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;		/* Last segment matched. Function completed. */
			/* Get into the sub-directory */
			if (!(dp->obj.attr & AM_DIR)) {	/* It is not a sub-directory and cannot follow */
				res = FR_NO_PATH; break;
			}
#if FF_FS_EXFAT
			if (fs->fs_type == FS_EXFAT) {	/* Save containing directory information for next dir */
				dp->obj.c_scl = dp->obj.sclust;
				dp->obj.c_size = ((DWORD)dp->obj.objsize & 0xFFFFFF00) | dp->obj.stat;
				dp->obj.c_ofs = dp->blk_ofs;
				init_alloc_info(fs, &dp->obj);	/* Open next directory */
			} else
#endif
			{
				dp->obj.sclust = ld_clust(fs, fs->win + dp->dptr % SS(fs));	/* Open next directory */
			}
		}
	}

	return res;
}

1. 函数功能

follow_path 是 FATFS 文件系统库的核心函数,用于 解析文件路径并定位到目标文件或目录 。主要功能包括:

  • 路径分割 :将完整路径分解为多个路径段(如 /dir1/file.txt"dir1""file.txt" )。
  • 目录遍历 :逐级进入子目录,直到找到目标对象。
  • 错误处理 :检测无效路径、不存在的目录或文件、权限问题等。
  • 支持多文件系统 :兼容 FAT12/16/32 和 exFAT 的目录结构差异。

2. 输入输出

参数类型说明
dpDIR*目录对象指针,存储当前目录状态和查找结果。
pathconst TCHAR*完整路径(如 "/data/logs/app.log" )。
返回值FRESULT操作结果(如 FR_OKFR_NO_FILEFR_NO_PATH )。

3. 核心逻辑流程

步骤 1:确定起始目录
// 处理路径前缀分隔符(如 "/" 或 "0:/")
if (路径以分隔符开头) {
    dp->obj.sclust = 0;    // 从根目录开始
} else {
    dp->obj.sclust = fs->cdir; // 从当前目录开始(若支持相对路径)
}
  • 绝对路径 :以分隔符开头,从根目录( sclust=0 )开始解析。
  • 相对路径 :从当前目录( fs->cdir )开始(需启用 FF_FS_RPATH )。

步骤 2:处理空路径(当前目录)
if (*path 是空字符或终止符) {
    dp->fn[NSFLAG] = NS_NONAME; // 标记为无目标对象
    dir_sdi(dp, 0);             // 定位到目录起始位置
    return FR_OK;
}
  • 空路径 :直接返回当前目录本身(如 f_opendir("") )。

步骤 3:逐级解析路径段
for (;;) {
    // 1. 提取当前路径段(如 "dir1")
    res = create_name(dp, &path); // 解析路径段到 dp->fn
    if (res != FR_OK) break;

    // 2. 在目录中查找该路径段对应的目录项
    res = dir_find(dp);
    ns = dp->fn[NSFLAG]; // 获取名称状态标志

    if (res != FR_OK) {
        // 处理查找失败(如路径段不存在)
        if (res == FR_NO_FILE) {
            if ( "."  ".." 目录且允许自动创建) continue;
            else 返回 FR_NO_PATH;
        }
        break;
    }

    // 3. 如果是路径的最后一段,结束查找
    if (ns & NS_LAST) break;

    // 4. 进入子目录
    if (当前对象不是目录) {
        res = FR_NO_PATH; // 无法继续遍历
        break;
    }

    // 更新目录对象以进入子目录
#if FF_FS_EXFAT
    if (exFAT) {
        // 保存父目录信息,初始化子目录分配信息
        init_alloc_info(fs, &dp->obj);
    } else
#endif
    {
        dp->obj.sclust = 获取子目录的起始簇号; // FAT12/16/32
    }
}

4. 关键设计思想

(1) 路径段解析
  • create_name 函数

    将路径段转换为标准 8.3 格式或长文件名格式,处理特殊字符(如空格、大小写转换)。

  • 名称状态标志( NSFLAG

    • NS_LAST :标记是否为路径的最后一段。
    • NS_DOT :标记是否为 ... 目录。
    • NS_NONAME :标记空路径(当前目录本身)。
(2) 目录项查找
  • dir_find 函数

    遍历当前目录的所有目录项,匹配名称、属性(如文件/目录)、时间戳等。

    支持长文件名( FF_USE_LFN )和 exFAT 的扩展目录项。

(3) 错误处理
  • 路径不存在

    dir_find 返回 FR_NO_FILE ,若为中间路径段则返回 FR_NO_PATH

    若路径段为 ... 且允许自动处理( FF_FS_RPATH ),则继续查找。

  • 权限问题

    若目标非目录但路径未结束(如 /file.txt/sub ),返回 FR_NO_PATH

(4) exFAT 特殊处理
  • 目录信息保存

    exFAT 需要保存父目录的碎片信息( c_scl , c_size , c_ofs ),以支持快速定位。

  • 初始化分配信息

    init_alloc_info 加载子目录的簇分配状态,优化后续文件操作。


5. 条件编译与配置

宏定义功能说明
FF_FS_RPATH启用相对路径支持(从当前目录解析路径)。
FF_FS_EXFAT支持 exFAT 文件系统,处理扩展目录项和碎片信息。
FF_USE_LFN启用长文件名解析(需配合 create_namedir_find 的长名处理逻辑)。

6. 错误处理机制

错误码触发条件
FR_NO_FILE当前路径段不存在于目录中。
FR_NO_PATH中间路径段不存在或目标非目录。
FR_INVALID_NAME路径段包含非法字符(由 create_name 检测)。
FR_DISK_ERR磁盘 I/O 错误(在 dir_finddir_sdi 中触发)。

7. 示例流程

解析路径 /data/logs/app.log
  1. 起始目录 :根目录( sclust=0 )。

  2. 第一段 data

    • create_name 提取 DATA (短名)或 data (长名)。
    • dir_find 查找根目录中的 DATA 目录项。
    • 进入 data 目录(更新 sclustdata 的簇号)。
  3. 第二段 logs

    • data 目录中查找 LOGS 目录项。
    • 进入 logs 目录。
  4. 最后一段 app.log

    • logs 目录中查找文件 APP.LOG
    • 标记 NS_LAST ,返回成功。
处理错误路径 /nonexistent/file.txt
  1. 查找 nonexistent

    • dir_find 返回 FR_NO_FILE
    • 由于是中间路径段,最终返回 FR_NO_PATH

8. 总结

特性说明
模块化设计分离路径解析( create_name )和目录查找( dir_find ),提高复用性。
多文件系统支持通过条件编译兼容 FAT/exFAT,处理目录结构差异。
高效遍历逐级进入目录,减少不必要的簇读取。
灵活错误处理区分路径段错误和最终目标错误,提供精确的错误码。
资源优化动态管理目录对象状态,避免冗余数据加载。

通过 follow_path 函数,FATFS 实现了高效且鲁棒的文件路径解析机制,为文件打开( f_open )、目录遍历( f_opendir )等操作提供了基础支持,尤其适合嵌入式系统对路径处理的低开销需求。

3.dir_register

static FRESULT dir_register (	/* FR_OK:succeeded, FR_DENIED:no free entry or too many SFN collision, FR_DISK_ERR:disk error */
	DIR* dp						/* Target directory with object name to be created */
)
{
	FRESULT res;
	FATFS *fs = dp->obj.fs;
#if FF_USE_LFN		/* LFN configuration */
	UINT n, len, n_ent;
	BYTE sn[12], sum;


	if (dp->fn[NSFLAG] & (NS_DOT | NS_NONAME)) return FR_INVALID_NAME;	/* Check name validity */
	for (len = 0; fs->lfnbuf[len]; len++) ;	/* Get lfn length */

#if FF_FS_EXFAT
	if (fs->fs_type == FS_EXFAT) {	/* On the exFAT volume */
		n_ent = (len + 14) / 15 + 2;	/* Number of entries to allocate (85+C0+C1s) */
		res = dir_alloc(dp, n_ent);		/* Allocate directory entries */
		if (res != FR_OK) return res;
		dp->blk_ofs = dp->dptr - SZDIRE * (n_ent - 1);	/* Set the allocated entry block offset */

		if (dp->obj.stat & 4) {			/* Has the directory been stretched by new allocation? */
			dp->obj.stat &= ~4;
			res = fill_first_frag(&dp->obj);	/* Fill the first fragment on the FAT if needed */
			if (res != FR_OK) return res;
			res = fill_last_frag(&dp->obj, dp->clust, 0xFFFFFFFF);	/* Fill the last fragment on the FAT if needed */
			if (res != FR_OK) return res;
			if (dp->obj.sclust != 0) {		/* Is it a sub-directory? */
				DIR dj;

				res = load_obj_xdir(&dj, &dp->obj);	/* Load the object status */
				if (res != FR_OK) return res;
				dp->obj.objsize += (DWORD)fs->csize * SS(fs);		/* Increase the directory size by cluster size */
				st_qword(fs->dirbuf + XDIR_FileSize, dp->obj.objsize);
				st_qword(fs->dirbuf + XDIR_ValidFileSize, dp->obj.objsize);
				fs->dirbuf[XDIR_GenFlags] = dp->obj.stat | 1;		/* Update the allocation status */
				res = store_xdir(&dj);				/* Store the object status */
				if (res != FR_OK) return res;
			}
		}

		create_xdir(fs->dirbuf, fs->lfnbuf);	/* Create on-memory directory block to be written later */
		return FR_OK;
	}
#endif
	/* On the FAT/FAT32 volume */
	memcpy(sn, dp->fn, 12);
	if (sn[NSFLAG] & NS_LOSS) {			/* When LFN is out of 8.3 format, generate a numbered name */
		dp->fn[NSFLAG] = NS_NOLFN;		/* Find only SFN */
		for (n = 1; n < 100; n++) {
			gen_numname(dp->fn, sn, fs->lfnbuf, n);	/* Generate a numbered name */
			res = dir_find(dp);				/* Check if the name collides with existing SFN */
			if (res != FR_OK) break;
		}
		if (n == 100) return FR_DENIED;		/* Abort if too many collisions */
		if (res != FR_NO_FILE) return res;	/* Abort if the result is other than 'not collided' */
		dp->fn[NSFLAG] = sn[NSFLAG];
	}

	/* Create an SFN with/without LFNs. */
	n_ent = (sn[NSFLAG] & NS_LFN) ? (len + 12) / 13 + 1 : 1;	/* Number of entries to allocate */
	res = dir_alloc(dp, n_ent);		/* Allocate entries */
	if (res == FR_OK && --n_ent) {	/* Set LFN entry if needed */
		res = dir_sdi(dp, dp->dptr - n_ent * SZDIRE);
		if (res == FR_OK) {
			sum = sum_sfn(dp->fn);	/* Checksum value of the SFN tied to the LFN */
			do {					/* Store LFN entries in bottom first */
				res = move_window(fs, dp->sect);
				if (res != FR_OK) break;
				put_lfn(fs->lfnbuf, dp->dir, (BYTE)n_ent, sum);
				fs->wflag = 1;
				res = dir_next(dp, 0);	/* Next entry */
			} while (res == FR_OK && --n_ent);
		}
	}

#else	/* Non LFN configuration */
	res = dir_alloc(dp, 1);		/* Allocate an entry for SFN */

#endif

	/* Set SFN entry */
	if (res == FR_OK) {
		res = move_window(fs, dp->sect);
		if (res == FR_OK) {
			memset(dp->dir, 0, SZDIRE);	/* Clean the entry */
			memcpy(dp->dir + DIR_Name, dp->fn, 11);	/* Put SFN */
#if FF_USE_LFN
			dp->dir[DIR_NTres] = dp->fn[NSFLAG] & (NS_BODY | NS_EXT);	/* Put NT flag */
#endif
			fs->wflag = 1;
		}
	}

	return res;
}

1. 函数功能

dir_register 是 FATFS 文件系统库的核心函数,用于 在目录中注册新的文件或子目录项 。主要功能包括:

  • 目录项分配 :在目录中分配一个或多个连续的目录条目(支持长文件名和 exFAT)。
  • 文件名处理 :生成短文件名(SFN)避免冲突,或存储长文件名(LFN)信息。
  • exFAT 扩展支持 :处理 exFAT 的目录扩展、碎片管理和元数据更新。
  • 错误处理 :检测磁盘错误、文件名冲突、目录空间不足等。

2. 输入输出

参数类型说明
dpDIR*目录对象指针,包含目标目录状态及待注册对象的名称信息。
返回值FRESULT操作结果(如 FR_OKFR_DENIEDFR_DISK_ERR )。

3. 核心逻辑流程

步骤 1:名称有效性检查
if (dp->fn[NSFLAG] & (NS_DOT | NS_NONAME)) return FR_INVALID_NAME;
  • 禁止注册. (当前目录)、 .. (上级目录)或空名称( NS_NONAME )。

步骤 2:处理 exFAT 文件系统
if (fs->fs_type == FS_EXFAT) {
    n_ent = (len + 14) / 15 + 2;       // 计算所需目录项数量(1个85项 + 1个C0项 + 多个C1项)
    res = dir_alloc(dp, n_ent);        // 分配连续目录项
    if (res != FR_OK) return res;

    // 若目录因分配扩展,更新其元数据(大小、FAT链)
    if (dp->obj.stat & 4) {
        fill_first_frag(&dp->obj);     // 填充首簇的FAT链
        fill_last_frag(&dp->obj, ...); // 填充末簇的FAT链
        // 更新目录大小及状态
        dp->obj.objsize += ...;
        store_xdir(&dj);               // 写回exFAT目录项
    }

    create_xdir(fs->dirbuf, fs->lfnbuf); // 创建exFAT目录项内容(85/C0/C1)
    return FR_OK;
}
  • 目录项结构 :exFAT 使用扩展目录项(85 主项、C0 流扩展项、C1 名称扩展项)。
  • 目录扩展 :若目录因新条目分配而扩展,需更新其大小及 FAT 簇链。

步骤 3:处理 FAT12/16/32 长文件名(LFN)
// 生成唯一短文件名(避免冲突)
if (sn[NSFLAG] & NS_LOSS) {            // 长文件名无法转换为合法8.3格式
    for (n = 1; n < 100; n++) {
        gen_numname(dp->fn, sn, ...);  // 生成带编号的SFN(如FILE0001.TXT)
        res = dir_find(dp);            // 检查是否与现有SFN冲突
        if (res != FR_OK) break;       // 无冲突时退出循环
    }
    if (n == 100) return FR_DENIED;    // 超过重试次数,返回拒绝
}

// 分配目录项(1个SFN + n_ent个LFN条目)
n_ent = (需要LFN时计算条目数);
res = dir_alloc(dp, n_ent);            // 分配连续目录项
if (res == FR_OK && --n_ent) {
    sum = sum_sfn(dp->fn);             // 计算SFN校验和
    do {
        put_lfn(fs->lfnbuf, dp->dir, ...); // 写入LFN条目(反向存储)
        fs->wflag = 1;                 // 标记目录扇区为脏
        dir_next(dp, 0);               // 移动到下一个目录项
    } while (--n_ent);
}
  • 短文件名冲突解决 :若长文件名无法转换为唯一 SFN,生成带编号的 SFN(最多尝试 99 次)。
  • LFN 条目写入 :每个 LFN 条目存储 13 个字符(UTF-16),按逆序写入目录。

步骤 4:设置短文件名(SFN)目录项
memset(dp->dir, 0, SZDIRE);            // 清空目录项
memcpy(dp->dir + DIR_Name, dp->fn, 11);// 写入8.3格式文件名
dp->dir[DIR_NTres] = ...;              // 设置NT标志(大小写、扩展名标记)
fs->wflag = 1;                         // 标记目录扇区为脏
  • SFN 格式 :8字节名称 + 3字节扩展名,不足部分填充空格。
  • NT 标志 :记录名称的大小写信息(如 DIR_NTres 字段)。

4. 关键设计思想

(1) 文件名处理策略
  • 短文件名生成

    当长文件名不符合 8.3 格式时,生成唯一的 SFN(如 LONGNAME~1.TXT ),避免与现有文件冲突。

  • 校验和机制

    LFN 条目包含 SFN 的校验和( sum_sfn ),确保 SFN 与 LFN 的关联性。

(2) 目录项分配
  • 连续分配

    dir_alloc 确保 LFN 和 SFN 条目连续存储,减少目录碎片。

  • exFAT 优化

    exFAT 目录项预分配和碎片管理( fill_first_frag / fill_last_frag )提升大目录性能。

(3) 错误处理
  • 冲突限制

    SFN 生成最多尝试 99 次,防止无限循环。

  • 磁盘状态检查

    每次目录写入后检查 fs->wflag ,确保数据持久化。


5. 条件编译与配置

宏定义功能说明
FF_USE_LFN启用长文件名支持,增加 LFN 条目处理逻辑。
FF_FS_EXFAT支持 exFAT 文件系统,处理扩展目录项和碎片管理。
FF_FS_READONLY禁用写操作相关代码(如目录项分配、元数据更新)。

6. 错误处理机制

错误码触发条件
FR_INVALID_NAME名称非法(如 ... 或空名)。
FR_DENIEDSFN 冲突超过 99 次或目录空间不足。
FR_DISK_ERR磁盘 I/O 错误(如 dir_allocmove_window 失败)。
FR_NO_FILE中间路径段不存在(由 dir_find 返回)。

7. 示例流程

注册长文件名 "Report 2023.docx"
  1. 生成 SFN :若短名 REPORT~1.DOC 冲突,尝试 REPORT~2.DOC 直至唯一。
  2. 分配目录项 :1个 SFN 条目 + 2个 LFN 条目(假设名称长度需 2个 LFN)。
  3. 写入 LFN 条目 :按逆序填充 "Report 2023.docx" 的 Unicode 编码。
  4. 写入 SFN 条目 :填充 REPORT~1.DOC 和 NT 标志。
exFAT 目录扩展
  1. 分配新簇 :原目录簇已满, dir_alloc 分配新簇并扩展 FAT 链。
  2. 更新元数据 :目录大小增加,通过 store_xdir 写回 exFAT 主目录项。

8. 总结

特性说明
多文件系统支持兼容 FAT12/16/32 和 exFAT,处理不同目录结构和命名规则。
健壮性通过冲突检测、磁盘状态检查确保数据一致性。
性能优化连续目录项分配减少碎片,exFAT 预分配提升大目录操作效率。
可配置性宏控制支持长文件名、exFAT 等特性,适应不同嵌入式场景。
资源管理动态分配目录项,按需扩展目录簇链,优化存储利用率。

通过 dir_register 函数,FATFS 实现了灵活且高效的文件/目录注册机制,支持复杂的文件名管理和存储扩展需求,尤其适合嵌入式系统对资源利用率和可靠性的严格要求。