一:f_read
FRESULT f_read (
FIL* fp, /* Open file to be read */
void* buff, /* Data buffer to store the read data */
UINT btr, /* Number of bytes to read */
UINT* br /* Number of bytes read */
)
{
FRESULT res;
FATFS *fs;
DWORD clst;
LBA_t sect;
FSIZE_t remain;
UINT rcnt, cc, csect;
BYTE *rbuff = (BYTE*)buff;
*br = 0; /* Clear read 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_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
remain = fp->obj.objsize - fp->fptr;
if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */
for ( ; btr > 0; btr -= rcnt, *br += rcnt, rbuff += rcnt, fp->fptr += rcnt) { /* Repeat until btr bytes read */
if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */
csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */
if (csect == 0) { /* On the cluster boundary? */
if (fp->fptr == 0) { /* On the top of the file? */
clst = fp->obj.sclust; /* Follow cluster chain from the origin */
} else { /* Middle or end of the file */
#if FF_USE_FASTSEEK
if (fp->cltbl) {
clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */
} else
#endif
{
clst = get_fat(&fp->obj, fp->clust); /* Follow cluster chain on the FAT */
}
}
if (clst < 2) ABORT(fs, FR_INT_ERR);
if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
fp->clust = clst; /* Update current cluster */
}
sect = clst2sect(fs, fp->clust); /* Get current sector */
if (sect == 0) ABORT(fs, FR_INT_ERR);
sect += csect;
cc = btr / SS(fs); /* When remaining bytes >= sector size, */
if (cc > 0) { /* Read maximum contiguous sectors directly */
if (csect + cc > fs->csize) { /* Clip at cluster boundary */
cc = fs->csize - csect;
}
if (disk_read(fs->pdrv, rbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR);
#if !FF_FS_READONLY && FF_FS_MINIMIZE <= 2 /* Replace one of the read sectors with cached data if it contains a dirty sector */
#if FF_FS_TINY
if (fs->wflag && fs->winsect - sect < cc) {
memcpy(rbuff + ((fs->winsect - sect) * SS(fs)), fs->win, SS(fs));
}
#else
if ((fp->flag & FA_DIRTY) && fp->sect - sect < cc) {
memcpy(rbuff + ((fp->sect - sect) * SS(fs)), fp->buf, SS(fs));
}
#endif
#endif
rcnt = SS(fs) * cc; /* Number of bytes transferred */
continue;
}
#if !FF_FS_TINY
if (fp->sect != sect) { /* Load data sector if not in cache */
#if !FF_FS_READONLY
if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */
if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
fp->flag &= (BYTE)~FA_DIRTY;
}
#endif
if (disk_read(fs->pdrv, fp->buf, sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */
}
#endif
fp->sect = sect;
}
rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes remains in the sector */
if (rcnt > btr) rcnt = btr; /* Clip it by btr if needed */
#if FF_FS_TINY
if (move_window(fs, fp->sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */
memcpy(rbuff, fs->win + fp->fptr % SS(fs), rcnt); /* Extract partial sector */
#else
memcpy(rbuff, fp->buf + fp->fptr % SS(fs), rcnt); /* Extract partial sector */
#endif
}
LEAVE_FF(fs, FR_OK);
}
整体思考:
首先,初始化变量和检查参数有效性。函数开始时清除已读取字节数,验证文件对象的有效性,检查文件是否以读模式打开,并调整要读取的字节数以不超过剩余文件大小。
然后,进入主循环,处理按扇区读取数据。这里需要理解扇区对齐和簇的处理逻辑。当文件指针位于扇区边界时,计算当前簇和扇区位置。可能需要使用FAT表来查找下一个簇,特别是当使用快速查找功能时。
接着,处理连续扇区的读取。如果剩余字节足够多,直接读取多个连续扇区以提高效率。这里需要考虑缓存一致性,比如处理脏数据回写。如果没有连续扇区可读,则使用缓存扇区数据,处理可能的缓存更新和读取。
然后,处理部分扇区数据的读取。当需要读取的数据不足一个扇区时,从缓存中复制数据到用户缓冲区。这里分为使用Tiny模式下的窗口缓存和普通模式下的文件对象缓存两种情况。
最后,处理错误和退出。在每一步操作中,都可能因磁盘错误或其他问题导致函数提前终止,返回相应的错误代码。
在分析过程中,需要注意FATFS的条件编译选项,如FF_USE_FASTSEEK、FF_FS_READONLY、FF_FS_TINY等,这些宏定义会影响代码的执行路径。此外,缓存管理、簇链遍历、磁盘I/O操作是关键部分,需要详细理解其逻辑。
总结时,需要归纳函数的工作流程、关键设计点、优化策略(如连续扇区读取、缓存利用)以及错误处理机制。同时,对比不同配置下的代码路径,理解如何通过宏定义实现不同功能模块的切换,增强代码的可移植性和灵活性。
可能遇到的疑问点包括:簇和扇区的转换逻辑、快速查找(CLMT)的实现细节、缓存脏数据的处理流程,以及不同配置(如Tiny模式)对缓存管理的影响。需要结合FAT文件系统的基本原理和FATFS库的设计文档来澄清这些部分。
通过这样的逐步分析,可以深入理解`f_read`函数的实现机制,掌握FATFS库在嵌入式系统中高效读取文件数据的方法,以及如何处理各种边界条件和错误情况。
1. 函数功能
f_read
是 FATFS 文件系统库中的核心函数,用于 从已打开的文件中读取数据。其核心任务包括:
按需读取文件内容:支持从任意文件位置(
fp->fptr
)读取指定字节数(btr
)。处理文件簇链:通过 FAT 表或 CLMT(快速查找表)定位数据所在的簇和扇区。
缓存管理:利用文件对象或全局窗口缓存优化小数据块的读取。
错误处理:检测文件状态、磁盘错误和无效操作,返回错误码。
2. 参数说明
参数 | 类型 | 说明 |
---|---|---|
fp |
FIL* |
已打开的文件对象指针。 |
buff |
void* |
用户提供的缓冲区,用于存储读取的数据。 |
btr |
UINT |
请求读取的字节数。 |
br |
UINT* |
实际读取的字节数(输出参数)。 |
3. 函数逻辑流程
步骤 1:初始化与参数校验
*br = 0; // 初始化已读字节数为 0
res = validate(&fp->obj, &fs); // 验证文件对象有效性
if (res != FR_OK || ...) LEAVE_FF(...); // 检查错误或权限问题
if (!(fp->flag & FA_READ)) LEAVE_FF(...); // 检查文件是否以读模式打开
remain = fp->obj.objsize - fp->fptr; // 计算剩余可读字节数
if (btr > remain) btr = (UINT)remain; // 调整请求字节数,避免越界
关键点:确保文件状态合法,避免读取越界或未授权的访问。
步骤 2:主循环(按扇区读取数据)
for ( ; btr > 0; ...) {
// 处理扇区对齐逻辑
if (fp->fptr % SS(fs) == 0) { // 文件指针位于扇区边界?
csect = ...; // 计算当前簇内的扇区偏移
if (csect == 0) { // 位于簇边界?
// 获取当前簇号(可能通过 CLMT 或 FAT 表遍历)
clst = get_cluster(...); // 关键:簇链遍历或 CLMT 查找
fp->clust = clst; // 更新文件对象的当前簇号
}
sect = clst2sect(fs, fp->clust); // 簇号转物理扇区号
sect += csect; // 加上簇内扇区偏移
// 尝试读取连续多个扇区(优化)
if (cc = btr / SS(fs)) { // 剩余字节 >= 扇区大小?
disk_read(...); // 直接读取多个连续扇区
rcnt = SS(fs) * cc; // 更新已读字节数
continue;
}
// 处理单个扇区缓存
if (fp->sect != sect) { // 缓存扇区是否需要更新?
if (缓存脏) disk_write(...); // 写回脏数据(若需要)
disk_read(fp->buf, sect); // 读取新扇区到缓存
fp->sect = sect; // 更新缓存扇区号
}
}
// 从缓存中复制部分数据到用户缓冲区
rcnt = ...; // 计算本次读取的字节数
memcpy(rbuff, fp->buf + offset, rcnt); // 复制数据
}
关键点:
扇区对齐优化:当请求数据跨越多个扇区时,优先读取连续扇区(减少磁盘操作次数)。
缓存管理:维护文件对象的
sect
和buf
,避免重复读取同一扇区。簇链遍历:通过
get_fat
或 CLMT 查找下一个簇号,支持大文件随机访问。
4. 关键设计思想
(1) 簇链与扇区定位
簇链遍历:
文件数据存储在多个簇中,每个簇对应一组连续扇区。get_fat
函数通过 FAT 表查找下一簇号。快速查找(CLMT):
若启用FF_USE_FASTSEEK
,使用预生成的 CLMT(Cluster Link Map Table)直接定位簇,避免遍历 FAT 表,加速大文件随机访问。
(2) 缓存策略
文件对象缓存(非 Tiny 模式):
每个文件对象(FIL
)维护一个扇区缓存(fp->buf
),用于存储当前访问的扇区数据。全局窗口缓存(Tiny 模式):
若启用FF_FS_TINY
,所有文件共享一个全局缓存(fs->win
),节省内存但降低并发性能。脏数据写回:
在读取新扇区前,若缓存数据被修改(FA_DIRTY
标志),需先写回磁盘。
(3) 错误处理
ABORT 宏:
统一处理错误,设置fs->err
并跳转到退出点(LEAVE_FF
)。磁盘操作检查:
所有disk_read
和disk_write
调用均检查返回值,确保数据完整性。
5. 优化策略
连续扇区批量读取:
当剩余读取字节数超过扇区大小时,直接读取多个连续扇区,减少磁盘 I/O 次数。缓存复用:
避免重复读取同一扇区,利用缓存提高小数据块的读取效率。延迟写回:
仅在需要替换缓存扇区时写回脏数据,减少不必要的磁盘写入。
6. 条件编译与配置
宏定义 | 功能说明 |
---|---|
FF_USE_FASTSEEK |
启用 CLMT 快速查找,加速大文件随机访问。 |
FF_FS_TINY |
使用全局窗口缓存(节省内存),替代文件对象缓存。 |
FF_FS_READONLY |
禁用写操作相关代码(如脏数据写回)。 |
FF_FS_MINIMIZE |
控制功能裁剪级别(影响缓存策略)。 |
7. 典型场景示例
FIL fil;
UINT br;
uint8_t buffer[512];
// 打开文件并读取前 512 字节
f_open(&fil, "file.txt", FA_READ);
f_read(&fil, buffer, 512, &br);
f_close(&fil);
流程:
f_read
检查文件状态,确定可读范围。根据文件指针
fptr
计算簇和扇区位置。若数据跨多个扇区,批量读取连续扇区;否则从缓存复制数据。
更新文件指针和已读字节数。
8. 总结
特性 | 说明 |
---|---|
核心逻辑 | 按扇区读取数据,处理簇链遍历和缓存管理。 |
性能优化 | 批量读取连续扇区、缓存复用、快速查找(CLMT)。 |
资源管理 | 动态管理缓存,支持 Tiny 模式节省内存。 |
错误鲁棒性 | 严格校验参数和磁盘操作结果,确保数据一致性。 |
可配置性 | 通过条件编译支持多种功能裁剪(如只读模式、Tiny 缓存)。 |
通过 f_read
函数,FATFS 实现了高效、灵活的文件读取机制,适用于资源受限的嵌入式系统,同时兼顾性能与内存开销。