FATFS f_lseek函数详细解析
f_lseek
是 FatFs 文件系统库中的一个函数,用于移动文件指针到指定的位置。这个函数的主要作用是调整文件对象的读写位置,可以用于在文件中随机访问数据。通过 f_lseek
,可以实现类似于文件跳转、追加写操作等功能。
FRESULT f_lseek (
FIL* fp, /* 文件对象指针 */
FSIZE_t ofs /* 从文件开头开始的偏移量 */
)
{
FRESULT res; // 定义函数返回结果类型变量
FATFS *fs; // 文件系统对象指针
DWORD clst, bcs; // 集群(cluster)和集群大小(byte per cluster)
LBA_t nsect; // 逻辑扇区号
FSIZE_t ifptr; // 文件指针当前的位置
#if FF_USE_FASTSEEK
DWORD cl, pcl, ncl, tcl, tlen, ulen; // 用于快速查找的变量
DWORD *tbl; // 集群链表指针
LBA_t dsc; // 物理扇区号
#endif
res = validate(&fp->obj, &fs); // 验证文件对象的有效性
if (res == FR_OK) res = (FRESULT)fp->err; // 检查是否有文件操作错误
#if FF_FS_EXFAT && !FF_FS_READONLY
if (res == FR_OK && fs->fs_type == FS_EXFAT) {
res = fill_last_frag(&fp->obj, fp->clust, 0xFFFFFFFF); // 如果需要,填充FAT表中的最后一个片段
}
#endif
if (res != FR_OK) LEAVE_FF(fs, res); // 如果出错,退出并返回结果
#if FF_USE_FASTSEEK
if (fp->cltbl) { // 如果启用了快速查找
if (ofs == CREATE_LINKMAP) { // 如果是创建集群链表
tbl = fp->cltbl; // 获取集群链表表头
tlen = *tbl++; ulen = 2; // 获取链表的长度和使用项
cl = fp->obj.sclust; // 获取集群链表的起始集群
if (cl != 0) {
do {
tcl = cl; ncl = 0; ulen += 2; // 初始化集群链的相关变量
do {
pcl = cl; ncl++; // 获取连续集群链长度
cl = get_fat(&fp->obj, cl); // 获取下一个集群
if (cl <= 1) ABORT(fs, FR_INT_ERR); // 处理集群链中的错误
if (cl == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); // 处理磁盘错误
} while (cl == pcl + 1); // 持续获取集群链,直到找到不连续的集群
if (ulen <= tlen) { // 如果链表有足够空间
*tbl++ = ncl; *tbl++ = tcl; // 存储链表中的集群数和顶集群
}
} while (cl < fs->n_fatent); // 重复操作直到集群链结束
}
*fp->cltbl = ulen; // 更新链表的使用项
if (ulen <= tlen) {
*tbl = 0; // 链表结束
} else {
res = FR_NOT_ENOUGH_CORE; // 如果链表空间不足,返回错误
}
} else { // 否则进行快速查找
if (ofs > fp->obj.objsize) ofs = fp->obj.objsize; // 如果偏移量超过文件大小,则限制为文件大小
fp->fptr = ofs; // 更新文件指针位置
if (ofs > 0) {
fp->clust = clmt_clust(fp, ofs - 1); // 根据偏移量查找集群
dsc = clst2sect(fs, fp->clust); // 获取集群的扇区号
if (dsc == 0) ABORT(fs, FR_INT_ERR); // 错误处理
dsc += (DWORD)((ofs - 1) / SS(fs)) & (fs->csize - 1); // 计算扇区内的偏移
if (fp->fptr % SS(fs) && dsc != fp->sect) { // 如果需要,重新加载扇区缓存
#if !FF_FS_TINY
#if !FF_FS_READONLY
if (fp->flag & FA_DIRTY) { // 如果扇区缓存需要写回
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, dsc, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); // 读取当前扇区
#endif
fp->sect = dsc; // 更新文件对象的扇区号
}
}
}
} else
#endif
/* 正常查找 */
{
#if FF_FS_EXFAT
if (fs->fs_type != FS_EXFAT && ofs >= 0x100000000) ofs = 0xFFFFFFFF; // 如果文件系统不是exFAT,最大偏移量限制为4GB
#endif
if (ofs > fp->obj.objsize && (FF_FS_READONLY || !(fp->flag & FA_WRITE))) { // 如果是只读模式,偏移量不能超过文件大小
ofs = fp->obj.objsize; // 限制偏移量为文件大小
}
ifptr = fp->fptr; // 保存当前文件指针
fp->fptr = nsect = 0; // 重置文件指针和扇区号
if (ofs > 0) {
bcs = (DWORD)fs->csize * SS(fs); // 获取每个集群的字节数
if (ifptr > 0 && (ofs - 1) / bcs >= (ifptr - 1) / bcs) { // 如果是前向查找
fp->fptr = (ifptr - 1) & ~(FSIZE_t)(bcs - 1); // 从当前集群开始
ofs -= fp->fptr;
clst = fp->clust;
} else { // 如果是后向查找
clst = fp->obj.sclust; // 从第一个集群开始
#if !FF_FS_READONLY
if (clst == 0) { // 如果没有集群链,创建新链
clst = create_chain(&fp->obj, 0);
if (clst == 1) ABORT(fs, FR_INT_ERR);
if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
fp->obj.sclust = clst;
}
#endif
fp->clust = clst; // 更新文件的当前集群
}
if (clst != 0) {
while (ofs > bcs) { // 集群跟踪循环
ofs -= bcs; fp->fptr += bcs;
#if !FF_FS_READONLY
if (fp->flag & FA_WRITE) { // 如果是写入模式
if (FF_FS_EXFAT && fp->fptr > fp->obj.objsize) { // exFAT下需要更新文件大小
fp->obj.objsize = fp->fptr;
fp->flag |= FA_MODIFIED;
}
clst = create_chain(&fp->obj, clst); // 在集群链末尾扩展链
if (clst == 0) { // 如果磁盘满了,剪裁文件大小
ofs = 0; break;
}
} else
#endif
{
clst = get_fat(&fp->obj, clst); // 如果不是写入模式,则继续跟踪集群链
}
if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); // 错误处理
if (clst <= 1 || clst >= fs->n_fatent) ABORT(fs, FR_INT_ERR); // 错误处理
fp->clust = clst; // 更新当前集群
}
fp->fptr += ofs;
if (ofs % SS(fs)) {
nsect = clst2sect(fs, clst); // 获取当前扇区号
if (nsect == 0) ABORT(fs, FR_INT_ERR); // 错误处理
nsect += (DWORD)(ofs / SS(fs)); // 计算在扇区内的偏移
}
}
}
if (!FF_FS_READONLY && fp->fptr > fp->obj.objsize) { // 如果文件大小扩展,更新文件大小
fp->obj.objsize = fp->fptr;
fp->flag |= FA_MODIFIED;
}
if (fp->fptr % SS(fs) && nsect != fp->sect) { // 如果需要,更新扇区缓存
#if !FF_FS_TINY
#if !FF_FS_READONLY
if (fp->flag & FA_DIRTY) { // 写回脏扇区缓存
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, nsect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); // 读取扇区
#endif
fp->sect = nsect; // 更新当前扇区号
}
}
LEAVE_FF(fs, res); // 退出并返回结果
}