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_READONLY
、FF_FS_EXFAT
等),这些宏会影响代码的编译路径,需要特别注意。
然后,我会逐行分析代码的执行流程:
参数检查:检查
fp
是否为NULL,如果是则返回FR_INVALID_OBJECT
。这是基本的有效性检查,确保传入的文件对象指针有效。获取逻辑驱动器号并挂载卷:通过
mount_volume
函数挂载对应的文件系统,获取文件系统对象fs
。这里会根据访问模式mode
进行相应的挂载操作,可能涉及权限检查。路径解析和目录遍历:使用
follow_path
函数解析路径,找到目标文件或目录。如果路径无效或文件不存在,这里会返回错误。处理不同打开模式:
创建文件(如
FA_CREATE_ALWAYS
、FA_CREATE_NEW
):检查文件是否已存在,若存在且不允许覆盖则返回错误;否则创建新文件条目。打开现有文件:检查文件属性(如是否为目录、是否只读),确保访问模式合法。
初始化文件对象:设置文件对象的各个字段,如簇号、文件大小、访问模式等。这里可能涉及簇链的遍历(如快速定位到文件末尾的
FA_OPEN_APPEND
模式)。错误处理和资源释放:在出现错误时,释放可能分配的资源,并标记文件对象为无效。
在分析过程中,我需要特别注意条件编译的部分,例如 FF_FS_READONLY
表示只读文件系统,此时某些写操作相关的代码会被排除。此外,FF_FS_EXFAT
支持exFAT文件系统,相关的处理逻辑会有所不同。
接下来,我需要关注几个关键点:
路径解析和目录遍历:
follow_path
如何解析路径,处理目录层级,找到目标文件或目录。这部分可能涉及目录项(directory entry)的查找和验证。文件创建过程:如何注册新的目录项(
dir_register
),处理文件属性的设置,以及簇链的初始化。例如,在创建文件时,可能需要分配初始簇并更新FAT表。簇链管理:在打开现有文件时,如何获取文件的起始簇号,以及如何快速定位到文件末尾(如
FA_OPEN_APPEND
模式下的处理)。错误处理机制:如何检测和处理各种错误情况,如权限不足、磁盘错误、无效路径等,并确保资源正确释放。
此外,需要注意线程安全和共享访问的问题,例如 FF_FS_LOCK
宏控制下的文件锁定机制,防止多线程同时修改同一文件导致数据不一致。
在分析过程中,可能会遇到一些不熟悉的函数或宏,例如 DEF_NAMBUF
、INIT_NAMBUF
、FREE_NAMBUF
,这些可能与长文件名支持相关,需要结合FATFS的文档或源码其他部分来理解其作用。
最后,总结时需要将代码的功能、关键流程、设计模式、优化策略以及配置选项(条件编译)等方面整理出来,帮助用户全面理解 f_open
的实现细节。
1. 函数功能
f_open
是 FATFS 文件系统库的核心函数,用于
打开或创建文件
,支持多种访问模式(读、写、追加、新建等)。主要功能包括:
- 路径解析 :定位目标文件或目录。
- 文件创建 :在指定路径创建新文件(若允许)。
- 文件打开 :加载现有文件的元数据(如簇链、大小)。
- 权限检查 :验证访问模式与文件属性的兼容性(如只读文件不可写)。
- 资源初始化
:设置文件对象(
FIL
)的字段,准备后续读写操作。
2. 参数说明
参数 | 类型 | 说明 |
---|---|---|
fp | FIL* | 指向空白文件对象的指针,用于存储打开文件后的状态。 |
path | const TCHAR* | 文件路径(如 "0:/dir/file.txt" )。 |
mode | BYTE | 访问模式(如 FA_READ 、 FA_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_OBJECT
、FR_INVALID_NAME
)。 - 权限错误(
FR_DENIED
)。 - 磁盘错误(
FR_DISK_ERR
)。 - 文件状态错误(
FR_NO_FILE
、FR_EXIST
)。
- 参数错误(
资源回滚 :
在错误路径中释放已分配的簇、目录项、锁等资源。
7. 示例场景
场景 1:创建新文件
FIL fil;
f_open(&fil, "0:/newfile.txt", FA_WRITE | FA_CREATE_NEW);
流程 :
- 解析路径,确认文件不存在。
- 注册新目录项,分配初始簇。
- 初始化文件对象,设置写模式。
场景 2:追加写入现有文件
f_open(&fil, "0:/data.log", FA_WRITE | FA_OPEN_APPEND);
流程 :
- 解析路径,加载文件元数据。
- 遍历簇链至末尾,定位最后一个扇区。
- 预读末尾扇区到缓存,准备追加写入。
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. 输入输出
参数 | 类型 | 说明 |
---|---|---|
path | const TCHAR** | 路径指针(用于提取逻辑驱动器号,如 "0:/file.txt" → 驱动器0)。 |
rfs | FATFS** | 输出参数,返回挂载的文件系统对象指针。 |
mode | BYTE | 访问模式(用于检查写保护,如 FA_WRITE )。 |
返回值 | FRESULT | 操作结果(如 FR_OK 、 FR_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);
加载FSInfo(FAT32优化);
}
- 关键计算
:
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_read
、disk_initialize
)的错误均向上传递。
7. 示例流程
挂载FAT32卷
- 解析路径
"0:/data.txt"
,获取驱动器号0。 - 检查驱动器0未挂载,初始化SD卡。
- 读取引导扇区,验证BPB字段。
- 计算数据区起始、簇数量,确定FAT32类型。
- 加载FSInfo,记录空闲簇。
- 初始化
FATFS
对象,返回成功。
挂载exFAT卷
- 解析路径
"1:/video.mp4"
,获取驱动器号1。 - 初始化USB驱动器,读取exFAT引导扇区。
- 验证exFAT版本、扇区大小、位图连续性。
- 初始化
FATFS
对象,设置位图起始位置。 - 返回成功,准备文件操作。
8. 总结
特性 | 说明 |
---|---|
功能完整性 | 支持FAT12/16/32/exFAT,兼容多种存储设备。 |
健壮性 | 严格的参数校验和错误处理,确保卷合法性。 |
性能优化 | 缓存关键信息(FSInfo、位图),减少磁盘访问。 |
可配置性 | 通过宏灵活裁剪功能(如只读、exFAT、长文件名)。 |
跨平台适配 | 依赖底层 disk_initialize 和 disk_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. 输入输出
参数 | 类型 | 说明 |
---|---|---|
dp | DIR* | 目录对象指针,存储当前目录状态和查找结果。 |
path | const TCHAR* | 完整路径(如 "/data/logs/app.log" )。 |
返回值 | FRESULT | 操作结果(如 FR_OK 、 FR_NO_FILE 、 FR_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_name 和 dir_find 的长名处理逻辑)。 |
6. 错误处理机制
错误码 | 触发条件 |
---|---|
FR_NO_FILE | 当前路径段不存在于目录中。 |
FR_NO_PATH | 中间路径段不存在或目标非目录。 |
FR_INVALID_NAME | 路径段包含非法字符(由 create_name 检测)。 |
FR_DISK_ERR | 磁盘 I/O 错误(在 dir_find 或 dir_sdi 中触发)。 |
7. 示例流程
解析路径 /data/logs/app.log
起始目录 :根目录(
sclust=0
)。第一段
data
:create_name
提取DATA
(短名)或data
(长名)。dir_find
查找根目录中的DATA
目录项。- 进入
data
目录(更新sclust
为data
的簇号)。
第二段
logs
:- 在
data
目录中查找LOGS
目录项。 - 进入
logs
目录。
- 在
最后一段
app.log
:- 在
logs
目录中查找文件APP.LOG
。 - 标记
NS_LAST
,返回成功。
- 在
处理错误路径 /nonexistent/file.txt
查找
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. 输入输出
参数 | 类型 | 说明 |
---|---|---|
dp | DIR* | 目录对象指针,包含目标目录状态及待注册对象的名称信息。 |
返回值 | FRESULT | 操作结果(如 FR_OK 、 FR_DENIED 、 FR_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_DENIED | SFN 冲突超过 99 次或目录空间不足。 |
FR_DISK_ERR | 磁盘 I/O 错误(如 dir_alloc 或 move_window 失败)。 |
FR_NO_FILE | 中间路径段不存在(由 dir_find 返回)。 |
7. 示例流程
注册长文件名 "Report 2023.docx"
- 生成 SFN
:若短名
REPORT~1.DOC
冲突,尝试REPORT~2.DOC
直至唯一。 - 分配目录项 :1个 SFN 条目 + 2个 LFN 条目(假设名称长度需 2个 LFN)。
- 写入 LFN 条目
:按逆序填充
"Report 2023.docx"
的 Unicode 编码。 - 写入 SFN 条目
:填充
REPORT~1.DOC
和 NT 标志。
exFAT 目录扩展
- 分配新簇
:原目录簇已满,
dir_alloc
分配新簇并扩展 FAT 链。 - 更新元数据
:目录大小增加,通过
store_xdir
写回 exFAT 主目录项。
8. 总结
特性 | 说明 |
---|---|
多文件系统支持 | 兼容 FAT12/16/32 和 exFAT,处理不同目录结构和命名规则。 |
健壮性 | 通过冲突检测、磁盘状态检查确保数据一致性。 |
性能优化 | 连续目录项分配减少碎片,exFAT 预分配提升大目录操作效率。 |
可配置性 | 宏控制支持长文件名、exFAT 等特性,适应不同嵌入式场景。 |
资源管理 | 动态分配目录项,按需扩展目录簇链,优化存储利用率。 |
通过
dir_register
函数,FATFS 实现了灵活且高效的文件/目录注册机制,支持复杂的文件名管理和存储扩展需求,尤其适合嵌入式系统对资源利用率和可靠性的严格要求。