一:f_write
FRESULT f_write (FIL* fp, /* Open file to be written */const void* buff, /* Data to be written */UINT btw, /* Number of bytes to write */UINT* bw /* Number of bytes written */
)
{FRESULT res;FATFS *fs;DWORD clst;LBA_t sect;UINT wcnt, cc, csect;const BYTE *wbuff = (const BYTE*)buff;*bw = 0; /* Clear write byte counter */res = validate(&fp->obj, &fs); /* Check validity of the file object */if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); /* Check validity */if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode *//* Check fptr wrap-around (file size cannot reach 4 GiB at FAT volume) */if ((!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) && (DWORD)(fp->fptr + btw) < (DWORD)fp->fptr) {btw = (UINT)(0xFFFFFFFF - (DWORD)fp->fptr);}for ( ; btw > 0; btw -= wcnt, *bw += wcnt, wbuff += wcnt, fp->fptr += wcnt, fp->obj.objsize = (fp->fptr > fp->obj.objsize) ? fp->fptr : fp->obj.objsize) { /* Repeat until all data written */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 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 FF_USE_FASTSEEKif (fp->cltbl) {clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */} else
#endif{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 FF_FS_TINYif (fs->winsect == fp->sect && sync_window(fs) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Write-back sector cache */
#elseif (fp->flag & FA_DIRTY) { /* Write-back sector cache */if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);fp->flag &= (BYTE)~FA_DIRTY;}
#endifsect = clst2sect(fs, fp->clust); /* Get current sector */if (sect == 0) ABORT(fs, FR_INT_ERR);sect += csect;cc = btw / SS(fs); /* When remaining bytes >= sector size, */if (cc > 0) { /* Write maximum contiguous sectors directly */if (csect + cc > fs->csize) { /* Clip at cluster boundary */cc = fs->csize - csect;}if (disk_write(fs->pdrv, wbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR);
#if FF_FS_MINIMIZE <= 2
#if FF_FS_TINYif (fs->winsect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */memcpy(fs->win, wbuff + ((fs->winsect - sect) * SS(fs)), SS(fs));fs->wflag = 0;}
#elseif (fp->sect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */memcpy(fp->buf, wbuff + ((fp->sect - sect) * SS(fs)), SS(fs));fp->flag &= (BYTE)~FA_DIRTY;}
#endif
#endifwcnt = SS(fs) * cc; /* Number of bytes transferred */continue;}
#if FF_FS_TINYif (fp->fptr >= fp->obj.objsize) { /* Avoid silly cache filling on the growing edge */if (sync_window(fs) != FR_OK) ABORT(fs, FR_DISK_ERR);fs->winsect = sect;}
#elseif (fp->sect != sect && /* Fill sector cache with file data */fp->fptr < fp->obj.objsize &&disk_read(fs->pdrv, fp->buf, sect, 1) != RES_OK) {ABORT(fs, FR_DISK_ERR);}
#endiffp->sect = sect;}wcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes remains in the sector */if (wcnt > btw) wcnt = btw; /* Clip it by btw if needed */
#if FF_FS_TINYif (move_window(fs, fp->sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */memcpy(fs->win + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */fs->wflag = 1;
#elsememcpy(fp->buf + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */fp->flag |= FA_DIRTY;
#endif}fp->flag |= FA_MODIFIED; /* Set file change flag */LEAVE_FF(fs, FR_OK);
}
1. 函数功能
f_write
是 FATFS 文件系统库的核心函数,用于 向已打开的文件写入数据。主要功能包括:
-
数据写入:将用户缓冲区数据写入文件的指定位置。
-
簇链管理:动态扩展文件簇链(分配新簇),处理大文件和跨簇写入。
-
缓存优化:利用文件对象缓存或全局窗口缓存优化小块数据写入。
-
错误处理:检测磁盘错误、簇分配失败、权限问题等。
2. 输入输出
参数 | 类型 | 说明 |
---|---|---|
fp | FIL* | 已打开的文件对象指针,记录文件状态(如当前簇、文件指针等)。 |
buff | const void* | 用户提供的待写入数据缓冲区。 |
btw | UINT | 请求写入的字节数。 |
bw | UINT* | 实际写入的字节数(输出参数)。 |
返回值 | FRESULT | 操作结果(如 FR_OK 、FR_DISK_ERR 、FR_DENIED )。 |
3. 核心逻辑流程
步骤 1:参数校验与初始化
*bw = 0; // 初始化已写入字节数
res = validate(&fp->obj, &fs); // 验证文件对象有效性
if (res != FR_OK || ...) LEAVE_FF(...); // 错误检查
if (!(fp->flag & FA_WRITE)) ... // 检查写权限
-
关键点:确保文件对象合法且以写模式打开。
步骤 2:处理文件大小限制(非 exFAT)
/* Check fptr wrap-around (file size cannot reach 4 GiB at FAT volume) */
if ((!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) && (DWORD)(fp->fptr + btw) < (DWORD)fp->fptr {btw = (UINT)(0xFFFFFFFF - (DWORD)fp->fptr);
}
if (非 exFAT 且文件指针 + btw 溢出) btw = 调整至最大合法值; // 避免 4GB 溢出(FAT 限制)
-
FAT 限制:FAT12/16/32 文件大小不超过 4GB(
DWORD
上限)。
步骤 3:主循环(按簇/扇区写入)
for (; btw > 0; btw -= wcnt, ...) {// 检查文件指针是否位于扇区边界if (fp->fptr % SS(fs) == 0) {csect = 计算簇内扇区偏移;if (csect == 0) { // 位于簇边界,可能需要分配新簇if (fp->fptr == 0) { // 文件起始,获取初始簇clst = fp->obj.sclust;if (clst == 0) clst = create_chain(...); // 分配新簇链} else { // 文件中间或末尾,扩展簇链clst = create_chain(...); // 从当前簇扩展}fp->clust = clst; // 更新当前簇}// 处理缓存写回(避免脏数据丢失)
#if FF_FS_TINYsync_window(fs); // Tiny模式:同步全局窗口缓存
#elseif (fp->flag & FA_DIRTY) // 标准模式:写回私有缓存disk_write(fp->buf, fp->sect);
#endifsect = clst2sect(fs, fp->clust) + csect; // 计算物理扇区号// 批量写入连续扇区(优化)if (剩余字节 >= 扇区大小) {disk_write(wbuff, sect, cc); // 直接写入多个扇区wcnt = SS(fs) * cc; // 更新已写入字节数continue;}// 加载当前扇区到缓存(若非末尾或需要部分写入)
#if FF_FS_TINYfs->winsect = sect; // 更新全局窗口缓存位置
#elsedisk_read(fp->buf, sect); // 读取扇区到私有缓存
#endiffp->sect = sect; // 记录当前扇区}// 写入部分数据到缓存wcnt = 计算当前扇区剩余空间;memcpy(缓存地址, wbuff, wcnt); // 数据复制到缓存
#if FF_FS_TINYfs->wflag = 1; // 标记全局缓存为脏
#elsefp->flag |= FA_DIRTY; // 标记私有缓存为脏
#endif
}
-
关键操作:
-
簇链扩展:通过
create_chain
动态分配簇,维护 FAT 表。 -
批量写入优化:当剩余数据超过扇区大小时,直接写入连续扇区。
-
缓存管理:处理脏数据写回,确保数据一致性。
-
4. 关键设计思想
(1) 簇链动态分配
-
create_chain
函数:
根据当前簇号查找或分配新簇,更新 FAT 表。若当前簇为0(文件未分配簇),则创建新簇链;否则扩展现有簇链。 -
错误处理:
-
clst == 0
:磁盘空间不足(FR_DISK_FULL
)。 -
clst == 1
:内部错误(FR_INT_ERR
)。 -
clst == 0xFFFFFFFF
:磁盘 I/O 错误(FR_DISK_ERR
)。
-
(2) 写入优化策略
-
连续扇区批量写入:
当剩余数据量大于等于扇区大小且对齐时,直接调用disk_write
写入多个扇区,减少 I/O 次数。 -
缓存复用:
对小数据或非对齐写入,先写入文件对象或全局缓存,延迟写回磁盘,减少碎片化操作。
(3) 缓存一致性
-
脏数据标记:
-
标准模式:文件对象私有缓存标记为
FA_DIRTY
,后续同步时写回。 -
Tiny 模式:全局窗口缓存标记为
fs->wflag = 1
,通过sync_window
写回。
-
-
缓存预读:
在部分写入场景下,若目标扇区不在缓存中,需先读取原扇区内容,避免覆盖未修改数据。
5. 条件编译与配置
宏定义 | 功能说明 |
---|---|
FF_FS_TINY | 启用 Tiny 模式,使用全局窗口缓存(fs->win ),节省内存但降低并发性能。 |
FF_USE_FASTSEEK | 启用快速定位(CLMT),优化大文件随机写入的簇查找。 |
FF_FS_EXFAT | 支持 exFAT 文件系统,解除 4GB 文件大小限制,优化簇管理。 |
FF_FS_READONLY | 禁用写操作相关代码(如簇分配、缓存写回)。 |
6. 错误处理机制
错误码 | 触发条件 |
---|---|
FR_DISK_ERR | 磁盘 I/O 错误(如 disk_write 或 disk_read 失败)。 |
FR_INT_ERR | 内部逻辑错误(如无效簇号 clst == 1 )。 |
FR_DENIED | 文件未以写模式打开或写保护。 |
FR_DISK_FULL | 磁盘空间不足,无法分配新簇。 |
7. 示例流程
场景 1:追加写入文件末尾
-
簇链检查:当前簇已满,调用
create_chain
分配新簇。 -
扇区写入:直接写入新簇的连续扇区,更新文件大小
fp->obj.objsize
。 -
缓存标记:若为部分扇区写入,标记缓存为脏,延迟写回。
场景 2:跨簇写入
-
当前簇剩余空间不足:写入部分数据至当前簇末尾。
-
分配新簇:调用
create_chain
扩展簇链。 -
继续写入:剩余数据写入新簇的扇区,更新
fp->clust
和fp->sect
。
8. 总结
特性 | 说明 |
---|---|
核心功能 | 动态管理簇链,高效处理不同大小的写入请求,维护文件系统完整性。 |
性能优化 | 批量连续扇区写入、缓存延迟写回、簇预分配策略。 |
资源管理 | 按需分配簇,避免空间浪费;缓存机制减少磁盘访问。 |
错误鲁棒性 | 严格检查磁盘状态、簇有效性,确保写入操作原子性。 |
可配置性 | 支持多种文件系统(FAT/exFAT)和模式(Tiny/标准),适应不同嵌入式场景。 |
通过 f_write
函数,FATFS 实现了高效可靠的文件写入机制,兼顾性能与资源效率,满足嵌入式系统对存储操作的严苛要求。