【FATFS】f_lseek函数详细解析

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);  // 退出并返回结果
}

上一篇:AJAX JSON 实例


下一篇:18个Java语法糖-Java中的语法糖