FATFS FAT32实现

FAT32概述

FAT32文件系统由DBR及其保留扇区,FAT1,FAT2 和 DATA 四个部分组成,其机构如下图:

这些结构是在分区被格式化时创建出来的,含义解释如下:

DBR及其保留扇区:DBR的含义是DOS引导记录,也称为操作系统引导记录,在DBR之后往往会有一些保留扇区。
FAT1:FAT的含义是文件分配表,FAT32一般有两份FAT,FAT1是第一份,也是主FAT。
FAT2:FAT2是FAT32的第二份文件分配表,也是FAT1的备份。
DATA:DATA也就是数据区,是FAT32文件系统的主要区域,其中包含目录区域。
引用https://www.cnblogs.com/naedzq/p/5877570.html

SD卡上内容

sdcard --------- aa.txt
                 bb ------------ bb.txt     

FATFS数据结构

  • FATFS描述了整个FAT32文件系统,主要是由DBR以及FSINFO两个扇区内的数据解析而来。
  • DIR描述了一个文件/目录项,它描述了文件的名称,大小,路径,簇号,扇区号,所在目录等等。
  • FIL描述一个文件对象,操作文件其实就是操作这个文件对象,它包含了文件的读指针,文件缓冲区buf,文件打开模式,文件的错误信息等等。
typedef struct {
    BYTE    fs_type;        /* FAT sub-type (0:Not mounted) */
    BYTE    drv;            /* Physical drive number */
    BYTE    csize;          /* Sectors per cluster (1,2,4...128) */
    BYTE    n_fats;         /* Number of FAT copies (1 or 2) */
    BYTE    wflag;          /* win[] flag (b0:dirty) */
    BYTE    fsi_flag;       /* FSINFO flags (b7:disabled, b0:dirty) */
    WORD    id;             /* File system mount ID */
    WORD    n_rootdir;      /* Number of root directory entries (FAT12/16) */
#if _MAX_SS != _MIN_SS
    WORD    ssize;          /* Bytes per sector (512, 1024, 2048 or 4096) */
#endif
#if _FS_REENTRANT
    _SYNC_t sobj;           /* Identifier of sync object */
#endif
#if !_FS_READONLY
    DWORD   last_clust;     /* Last allocated cluster */
    DWORD   free_clust;     /* Number of free clusters */
#endif
#if _FS_RPATH
    DWORD   cdir;           /* Current directory start cluster (0:root) */
#endif
    DWORD   n_fatent;       /* Number of FAT entries (= number of clusters + 2) */
    DWORD   fsize;          /* Sectors per FAT */
    DWORD   volbase;        /* Volume start sector */
    DWORD   fatbase;        /* FAT start sector */
    DWORD   dirbase;        /* Root directory start sector (FAT32:Cluster#) */
    DWORD   database;       /* Data start sector */
    DWORD   winsect;        /* Current sector appearing in the win[] */
    BYTE    win[_MAX_SS];   /* Disk access window for Directory, FAT (and file data at tiny cfg) */
} FATFS;



/* File object structure (FIL) */

typedef struct {
    FATFS*  fs;             /* Pointer to the related file system object (**do not change order**) */
    WORD    id;             /* Owner file system mount ID (**do not change order**) */
    BYTE    flag;           /* File status flags */
    BYTE    err;            /* Abort flag (error code) */
    DWORD   fptr;           /* File read/write pointer (Zeroed on file open) */
    DWORD   fsize;          /* File size */
    DWORD   sclust;         /* File data start cluster (0:no data cluster, always 0 when fsize is 0) */
    DWORD   clust;          /* Current cluster of fpter */
    DWORD   dsect;          /* Current data sector of fpter */
#if !_FS_READONLY
    DWORD   dir_sect;       /* Sector containing the directory entry */
    BYTE*   dir_ptr;        /* Pointer to the directory entry in the window */
#endif
#if _USE_FASTSEEK
    DWORD*  cltbl;          /* Pointer to the cluster link map table (Nulled on file open) */
#endif
#if _FS_LOCK
    UINT    lockid;         /* File lock ID (index of file semaphore table Files[]) */
#endif
#if !_FS_TINY
    BYTE    buf[_MAX_SS];   /* File data read/write buffer */
#endif
} FIL;



/* Directory object structure (DIR) */

typedef struct {
    FATFS*  fs;             /* Pointer to the owner file system object (**do not change order**) */
    WORD    id;             /* Owner file system mount ID (**do not change order**) */
    WORD    index;          /* Current read/write index number */
    DWORD   sclust;         /* Table start cluster (0:Root dir) */
    DWORD   clust;          /* Current cluster */
    DWORD   sect;           /* Current sector */
    BYTE*   dir;            /* Pointer to the current SFN entry in the win[] */
    BYTE*   fn;             /* Pointer to the SFN (in/out) {file[8],ext[3],status[1]} */
#if _FS_LOCK
    UINT    lockid;         /* File lock ID (index of file semaphore table Files[]) */
#endif
#if _USE_LFN
    WCHAR*  lfn;            /* Pointer to the LFN working buffer */
    WORD    lfn_idx;        /* Last matched LFN index number (0xFFFF:No LFN) */
#endif
} DIR;

DBR和f_mount

FAT32第0个扇区存的是DBR,里面主要是包含了文件系统的描述信息。FATFS中的f_mount函数就是对DBR的解析,并且将相应的信息存到数据结构FATFS中。

DBR结构

下图就是这张SD卡的DBR。

表1解析了上述DBR中的数据。除了表1中给出的数据定义,DBR中其他的数据是跟引导相关的,在这里我们并不关心。

表1

字节偏移 字节长度(字节) 字段内容及含义
0x0B 2 每扇区字节数 0x0200: 512
0x0D 1 每簇扇区数 0x08
0x0E 2 保留扇区 0x09CE:2510
0x10 1 FAT表个数 0x02
0x11 2 保留 0x0000
0x13 2 保留 0x0000
0x15 1 介质描述符 0xF8:硬盘
0x16 2 保留 0x0000
0x18 2 每磁道扇区数 0x003F
0x1A 2 磁头数 0x00FF
0x1C 4 隐藏扇区 0x0
0x20 4 该分区扇区总数 0x00ECE000:15523840
0x24 4 每FAT扇区数 0x3B19:15129
0x28 2 标记 0x0000
0x2A 2 版本 0x0000
0x2C 4 根目录首簇号 0x02
0x30 2 文件系统信息扇区号 0x0001
0x32 2 DBR备份扇区号 0x06
0x34 12 保留 0x0
0x40 1 BIOS驱动器号 0x80:无效
0x41 1 保留 0x0
0x42 1 扩展引导标记 0x29
0x43 4 卷序列号 0x66A4EA5B
0x47 11 卷标 NO NAME
0x52 8 文件系统类型 FAT32:0x3233544146

FATFS定义

typedef struct {
    BYTE    fs_type;        /* FAT sub-type (0:Not mounted) */
    BYTE    drv;            /* Physical drive number */
    BYTE    csize;          /* Sectors per cluster (1,2,4...128) */
    BYTE    n_fats;         /* Number of FAT copies (1 or 2) */
    BYTE    wflag;          /* win[] flag (b0:dirty) */
    BYTE    fsi_flag;       /* FSINFO flags (b7:disabled, b0:dirty) */
    WORD    id;             /* File system mount ID */
    WORD    n_rootdir;      /* Number of root directory entries (FAT12/16) */
#if _MAX_SS != _MIN_SS
    WORD    ssize;          /* Bytes per sector (512, 1024, 2048 or 4096) */
#endif
#if _FS_REENTRANT
    _SYNC_t sobj;           /* Identifier of sync object */
#endif
#if !_FS_READONLY
    DWORD   last_clust;     /* Last allocated cluster */
    DWORD   free_clust;     /* Number of free clusters */
#endif
#if _FS_RPATH
    DWORD   cdir;           /* Current directory start cluster (0:root) */
#endif
    DWORD   n_fatent;       /* Number of FAT entries (= number of clusters + 2) */
    DWORD   fsize;          /* Sectors per FAT */
    DWORD   volbase;        /* Volume start sector */
    DWORD   fatbase;        /* FAT start sector */
    DWORD   dirbase;        /* Root directory start sector (FAT32:Cluster#) */
    DWORD   database;       /* Data start sector */
    DWORD   winsect;        /* Current sector appearing in the win[] */
    BYTE    win[_MAX_SS];   /* Disk access window for Directory, FAT (and file data at tiny cfg) */
} FATFS;

f_mount实现

f_mount实现其实很简单,就是从存储介质上读出DBR解析然后将相应的数据填到FATFS结构体中。 以下代码我删掉了一些跟同步的代码以及一些错误判断,保留最主要的流程。并加入了中文注释,

FRESULT f_mount (
    FATFS* fs,          /* Pointer to the file system object (NULL:unmount)*/
    const TCHAR* path,  /* Logical drive number to be mounted/unmounted */
    BYTE opt            /* 0:Do not mount (delayed mount), 1:Mount immediately */
)
{
    FATFS *cfs;
    int vol;
    FRESULT res;
    const TCHAR *rp = path;


    vol = get_ldnumber(&rp);            //根据路径获取物理卷号,这里假设只有一张sd卡设备,这里为0
    if (vol < 0) return FR_INVALID_DRIVE;
    cfs = FatFs[vol];                   //根据物理卷号获取FATFS对象指针,static FATFS *FatFs[_VOLUMES];

    if (cfs) {                          //第一次mount的时候,该指针肯定为空,所以不会走到这里。
        cfs->fs_type = 0;               //当重复挂在同一个设备时候,需要将该FatFs对象中的字段清0.
    }

    if (fs) {
        fs->fs_type = 0;                //同上
    }
    FatFs[vol] = fs;                    //把新的FATFS对象fs注册到FatFs数组中,以便后面的函数使用。fs是上层传过来的。

    if (!fs || opt != 1) return FR_OK;  /* Do not mount now, it will be mounted later */

    res = find_volume(&fs, &path, 0);   //挂载函数的真正实现
    LEAVE_FF(fs, res);
}

从上面代码可以看到,mount函数真正的实现是在find_volume中实现,下面来看一下该函数的实现。 同样的,我只保留了一些关键流程代码,将一些错误判断就删掉了。 虽然代码看上去比较长,但实际上实现的功能很简单。

static
FRESULT find_volume (   /* FR_OK(0): successful, !=0: any error occurred */
    FATFS** rfs,        /* Pointer to pointer to the found file system object */
    const TCHAR** path, /* Pointer to pointer to the path name (drive number) */
    BYTE wmode          /* !=0: Check write protection for write access */
)
{
    BYTE fmt;
    int vol;
    DSTATUS stat;
    DWORD bsect, fasize, tsect, sysect, nclst, szbfat;
    WORD nrsv;
    FATFS *fs;


    /* 根据路径获取物理卷号 */
    *rfs = 0;
    vol = get_ldnumber(path);

    /* 根据vol从FatFs中取出FATFS对象指针,这个指针在f_mount中注册了 */
    fs = FatFs[vol];                    /* Get pointer to the file system object */
    *rfs = fs;                          /* Return pointer to the file system object */

    if (fs->fs_type) {                  /* 这里判断文件系统是否已经挂载,因为find_volume不只是mount函数调用
        ...                             还会被其他函数调用,mount这一级不会走到这里,因为此时文件系统还没有挂载*/
    }

    fs->fs_type = 0;                    /* 将fs_type清0 */
    fs->drv = LD2PD(vol);               /* 将逻辑卷号填入到drv字段 */
    stat = disk_initialize(fs->drv);    /* 初始化设备,这个函数需要不同的设备驱动来实现,这里就是初始化SD卡 */

    /* Find an FAT partition on the drive. Supports only generic partitioning, FDISK and SFD. */
    bsect = 0;
    fmt = check_fs(fs, bsect);                  /*check_fs会从SD卡中读取0个扇区,并检查第0扇区是否是DBR*/

/************************************check_fs实现*************************************************
static
BYTE check_fs ( /* 0:FAT boor sector, 1:Valid boor sector but not FAT, 2:Not a boot sector, 3:Disk error */
    FATFS* fs,  /* File system object */
    DWORD sect  /* Sector# (lba) to check if it is an FAT boot record or not */
)
{
    fs->wflag = 0; fs->winsect = 0xFFFFFFFF;    /* Invaidate window */
    if (move_window(fs, sect) != FR_OK)         /* Load boot record */
        return 3;

    if (LD_WORD(&fs->win[BS_55AA]) != 0xAA55)   /* Check boot record signature (always placed at offset 510 even if the sector size is >512) */
        return 2;

    if ((LD_DWORD(&fs->win[BS_FilSysType]) & 0xFFFFFF) == 0x544146)     /* Check "FAT" string */
        return 0;
    if ((LD_DWORD(&fs->win[BS_FilSysType32]) & 0xFFFFFF) == 0x544146)   /* Check "FAT" string */
        return 0;

    return 1;
}
************************************************************************************************/

    /* An FAT volume is found. Following code initializes the file system object */

    if (LD_WORD(fs->win+BPB_BytsPerSec) != SS(fs))      /* 从DBR中读取扇区大小 */
        return FR_NO_FILESYSTEM;

    /* 从DBR中读取每一个FAT大小并存入fsize字段 */
    fasize = LD_DWORD(fs->win+BPB_FATSz32);
    fs->fsize = fasize;

    fs->n_fats = fs->win[BPB_NumFATs];                  /* 从DBR中读取FAT个数,这里为2*/
    fasize *= fs->n_fats;                               /* 计算FAT算占用扇区 */

    fs->csize = fs->win[BPB_SecPerClus];                /* 计算每一个cluster所占用的扇区数,这里为8 */
    fs->n_rootdir = LD_WORD(fs->win+BPB_RootEntCnt);    /* 这个字段在FAT32中没有,忽略 */

    tsect = LD_DWORD(fs->win+BPB_TotSec32);             /* 从DBR中读取改分区所有的扇区数总和 */
    nrsv = LD_WORD(fs->win+BPB_RsvdSecCnt);             /* 从DBR中读取保留扇区个数 */

    /* 计算DATA区的起始扇区=保留扇区个数+FAT扇区个数 */
    sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZ_DIR); /* RSV+FAT+DIR */
    nclst = (tsect - sysect) / fs->csize;               /* 计算一共有多少个cluster */
    fmt = FS_FAT12;
    if (nclst >= MIN_FAT16) fmt = FS_FAT16;
    if (nclst >= MIN_FAT32) fmt = FS_FAT32;             /*这里fmt是FAT32*/

    /* Boundaries and Limits */
    fs->n_fatent = nclst + 2;                           /* 计算FAT区中entry的个数 */
    fs->volbase = bsect;                                /* SD卡起始扇区地址 */
    fs->fatbase = bsect + nrsv;                         /* FAT扇区起始地址*/
    fs->database = bsect + sysect;                      /* DATA扇区起始地址*/
    if (fmt == FS_FAT32) {
        if (fs->n_rootdir) return FR_NO_FILESYSTEM;     /* (BPB_RootEntCnt must be 0) */
        fs->dirbase = LD_DWORD(fs->win+BPB_RootClus);   /* 从DBR中读取根目录起始扇区地址 */
        szbfat = fs->n_fatent * 4;                      /* (Needed FAT size) */
    } 


    /* Initialize cluster allocation information */
    fs->last_clust = fs->free_clust = 0xFFFFFFFF;       


    fs->fsi_flag = 0x80;

    if (fmt == FS_FAT32                                 /*从FSInfo区读出空闲扇区个数和最后一个分配的扇区地址*/
        && LD_WORD(fs->win+BPB_FSInfo) == 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)
        {
            fs->free_clust = LD_DWORD(fs->win+FSI_Free_Count);
        }
    }

    fs->fs_type = fmt;  /* FAT3 */
    fs->id = ++Fsid;    /* File system mount ID */
    fs->cdir = 0;       /* Set current directory to root */
    return FR_OK;
}

f_mount函数基本没有分支,循环,都是顺序执行,其逻辑非常简单,就是从DBR中顺序读出相应数据即可。这里就不再画出流程图。

测试

下面加一些测试代码来看一看调用f_mount之后FATFS结构是怎么样的

void dump_fatfs(FATFS *fatfs_p)
{
    printf("fs_type: %x        \n", fatfs_p->fs_type);
    printf("drv: %x            \n", fatfs_p->drv);
    printf("csize: %x          \n", fatfs_p->csize);
    printf("n_fats: %x         \n", fatfs_p->n_fats);
    printf("wflag: %x          \n", fatfs_p->wflag);
    printf("fsi_flag: %x       \n", fatfs_p->fsi_flag);
    printf("id: %x             \n", fatfs_p->id);
    printf("n_rootdir: %x      \n", fatfs_p->n_rootdir);
    printf("last_clust:%x      \n", fatfs_p->last_clust);
    printf("free_clust:%x      \n", fatfs_p->free_clust);
    printf("n_fatent:%x        \n", fatfs_p->n_fatent);
    printf("fsize:%x           \n", fatfs_p->fsize);
    printf("volbase:%x         \n", fatfs_p->volbase);
    printf("fatbase:%x         \n", fatfs_p->fatbase);
    printf("dirbase:%x         \n", fatfs_p->dirbase);
    printf("winsect:%x         \n", fatfs_p->winsect);

}

int main(void)
{
    FATFS fs;
    f_mount(&fs,"0:",1); 
    dump_fatfs(&fs);

}

打印结果,dump出来的信息和表1所列出的信息一致

fs_type: 3              FAT32
drv: 0                  0号设备
csize: 8                每个cluster占用8个sector
n_fats: 2               一共有两个FAT,一个FAT作为备份FAT
wflag: 0                
fsi_flag: 0       
id: 1                   
n_rootdir: 0            
last_clust:c            最后一个使用的cluster是编号为c的cluster
free_clust:1d8bf6       空闲cluster个数为:1d8bf6
n_fatent:1d8c02         FAT表中entry个数为1d8c02      
fsize:3b19              一个FAT占用3b19个sector
volbase:0               分区起始sector为0
fatbase:9ce             FAT起始sector是9ce
dirbase:2               根目录起始簇号是2
winsect:1               

f_open与文件/目录项

文件/目录项

根目录的数据如下

从中可以看到在根目录下方有两个文件/目录项,aa.txt和bb目录
aa.txt

目录bb

从该两个目录项中解析得表2和表3

表2

字节偏移 字节长度(字节) 字段内容及含义
0x0 8 文件名 AA
0x8 3 扩展名 TXT
0xB 1 文件属性 0x20->存档
0xC 1 系统保留 0x18
0xD 1 校验值 0x86
0xE 2 文件创建时间 0x8C44
0x10 2 文件创建日期 0x4E5E
0x12 2 文件最后访问日期 0x4CA3
0x14 2 文件起始簇号高16位 0x0
0x16 2 文件最近修改时间 0x8C4A
0x18 2 文件最近修改日期 0x4CA3
0x1A 2 文件开始簇号低16位 0x0006
0x1C 4 文件长度 0x00000008

表3

字节偏移 字节长度(字节) 字段内容及含义
0x0 8 文件名 BB
0x8 3 扩展名
0xB 1 文件属性 0x10->子目录
0xC 1 系统保留 0x08
0xD 1 校验值 0xB6
0xE 2 文件创建时间 0x8C4B
0x10 2 文件创建日期 0x4CA3
0x12 2 文件最后访问日期 0x4CA4
0x14 2 文件起始簇号高16位 0x0
0x16 2 文件最近修改时间 0x8C4C
0x18 2 文件最近修改日期 0x4CA3
0x1A 2 文件开始簇号低16位 0x0007
0x1C 4 文件长度 0x00000000

f_open

f_open函数比较繁琐,由于mode的原因,它有许多分支。本文仅以f_open(&file, “0:/aa.txt”, FA_READ)为例,将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 file open mode flags */
)
{
    FRESULT res;
    DIR dj;
    BYTE *dir;
    DEF_NAMEBUF;

    /* 根据文件路径解析出文件系统,文件路径为"0:/aa.txt",所以这里逻辑卷是0盘,0盘的FATFS对象已经存在
     *  了FatFs数组中,find_volume会把FatFs[vol]取出来赋值给目录项结构体中的fs对象dj.fs。
     */
    mode &= FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW;
    res = find_volume(&dj.fs, &path, (BYTE)(mode & ~FA_READ));


    if (res == FR_OK) {
        INIT_BUF(dj);
        /*这个函数会根据path解析出DIR目录项所需要的数据,follow path在下面分析,这个函数是解析文件信息最重要的函数
         *它将文件/目录项的信息存放到DIR结构体中。 只要搞懂了follow_path的实现,就搞明白了f_open的实现。
         */
        res = follow_path(&dj, path);
        /* Create or Open a file */
        if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) {
            /*这里以FA_READ为例,所以创建文件的这个分支没走到*/
            ... 
        else {  /* Open an existing file */
            /*这些代码是一些边界条件的判断,在这个例子中,不会走到*/
            if (res == FR_OK) {                 /* Follow succeeded */
                if (dir[DIR_Attr] & AM_DIR) {   /* It is a directory */
                    res = FR_NO_FILE;
                } else {
                    if ((mode & FA_WRITE) && (dir[DIR_Attr] & AM_RDO)) /* R/O violation */
                        res = FR_DENIED;
                }
            }
        }
        if (res == FR_OK) {
            if (mode & FA_CREATE_ALWAYS)        /* Set file change flag if created or overwritten */
                mode |= FA__WRITTEN;
            fp->dir_sect = dj.fs->winsect;      /* Pointer to the directory entry */
            fp->dir_ptr = dir;
        }
        /*最后就是把相应的字段填入到fp中*/
        if (res == FR_OK) {
            fp->flag = mode;                    /* mode是传入的参数 */
            fp->err = 0;                        /* error flag打开时清除 */
            fp->sclust = ld_clust(dj.fs, dir);  /* 从目录项dj中获取起始的cluster号*/
            fp->fsize = LD_DWORD(dir+DIR_FileSize); /* 从目录项dj中获取文件大小 */
            fp->fptr = 0;                       /* 文件指针在打开时为0 */
            fp->dsect = 0;                      
            fp->fs = dj.fs;                     /* 将文件系统相关的信息赋值给文件对象 */
            fp->id = fp->fs->id;
        }
    }

下面分析f_open中的关键函数follow_path,该函数将从文件系统中DATA区查找到相应的文件/目录项,将其参数填入到DIR结构中。

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 *dir, ns;

    /*走到这里的时候,path已经是去掉盘符的path了,以"0:/aa.txt"为例
     *到这里的时候,传入的path是"/aa.txt"
     */

    if (*path == '/' || *path == '\\')      /* Strip heading separator if exist */
        path++;
    dp->sclust = 0;                         /* 从根目录开始查找文件/目录项 */

    if ((UINT)*path < ' ') {                /* 如果是根目录,就走这里,本例中不会走到这个分支 */
        res = dir_sdi(dp, 0);
        dp->dir = 0;
    } else {                                /* Follow path */
        for (;;) {
            /* create_name这个函数主要是处理字符串,根据path解析出短文件名,并保存到DIR中fn数组。
             *  还是以aa.txt为例,这个函数走完之后,DIR dp中fn文件名数组如下
             *  FN|0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|
             *    |A|A| | | | | | | | |  |  |T |X |T |  |
             *  这个函数就不展开了,里面都是一些关于字符串处理的代码。对理解文件系统并没有大碍。
             */
            res = create_name(dp, &path);   /* Get a segment name of the path */
            /* dir_find这个函数也非常重要,这个函数根据文件名从根目录开始查找相应的文件的entry。
             * 然后将entry中的数据填充到DIR dp中。
             * 这个函数在下面展开。
             */
            res = dir_find(dp);             /* Find an object with the sagment name */
            ns = dp->fn[NS];

            dir = dp->dir;                      /* Follow the sub-directory */
            if (!(dir[DIR_Attr] & AM_DIR)) {    /* It is not a sub-directory and cannot follow */
                /* 在这里aa.txt不是目录,所以会走到这个分支,至此,文件中的所需要的信息如开始簇,文件大小
                    文件名等都已经被解析到了DIR dp中*/
                res = FR_NO_PATH; break;
            }
            dp->sclust = ld_clust(dp->fs, dir);
        }
    }

    return res;
}

下面分析dir_find函数,这个函数在上面讲过就从根目录开始查找对应的目录项,并将信息存到DIR中。

static
FRESULT dir_find (
    DIR* dp         /* Pointer to the directory object linked to the file name */
)
{
    FRESULT res;
    BYTE c, *dir;

    /*dir_sdi会把根目录相关的信息存到dp中,如根目录起始扇区等等*/
    res = dir_sdi(dp, 0);           /* Rewind directory object */
    if (res != FR_OK) return res;

    do {
        res = move_window(dp->fs, dp->sect); 
        /* 走到这,根目录的第0扇区就被读到dp->fs->win 缓冲区中
         * 此时,dp->dir指向的就是dp->fs->win缓冲区
         */
        dir = dp->dir;              
        c = dir[DIR_Name];      /*读取目录项的第一个字节*/

        if (c == 0) { res = FR_NO_FILE; break; }    /* 如果第一个字节是0,那么就返回 */

        if (!(dir[DIR_Attr] & AM_VOL) && !mem_cmp(dir, dp->fn, 11)) /* 然后比较entry中文件名和解析的文件名是否一致,
                                                                     * 如果一致,那么就找到了该文件/目录项entry,返回
                                                                     * 此时dp->dir指向的就是缓冲区中该文件的文件/目录项
                                                                     */
            break;

        /* 如果发现该文件/目录项不是要寻找的文件,那么就增加0x20个byte,因为一个目录项entry就是20个byte
         * 如果超过了一个扇区大小,那么就增加扇区的位置,在下一次move_window的时候会读取下一个扇区。
         */
        res = dir_next(dp, 0);      /* Next entry */
    } while (res == FR_OK);

    return res;
}

dir_find查找的过程如下图所示

测试

测试代码如下:

void dump_file(FIL *file_p)
{
    printf("======dump_file======\n");
    printf("fs%x       \n", file_p->fs);
    printf("id:%x      \n", file_p->id);
    printf("flag:%x    \n", file_p->flag);
    printf("err:%x     \n", file_p->err);
    printf("fptr:%x    \n", file_p->fptr);
    printf("fsize:%x   \n", file_p->fsize);
    printf("sclust:%x  \n", file_p->sclust);
    printf("clust:%x   \n", file_p->clust);
    printf("dsect:%x   \n", file_p->dsect);
    printf("dir_sect:%x \n", file_p->dir_sect);
    printf("dir_ptr:%x \n", file_p->dir_ptr);
    printf("cltbl:%x \n\n\n", file_p->cltbl);

}


    f_open(&file, "0:/aa.txt", FA_READ);
    dump_file(&file);

打印如下:

======dump_file======
fs2000a628              //文件系统对象指针  
id:1                    //文件系统id
flag:1                  //文件打开模式
err:0                   
fptr:0    
fsize:8                 //文件大小
sclust:6                //文件开始簇号
clust:0                 //文件读写指针当前簇号
dsect:0                 //文件读写指针当前扇区号
dir_sect:8000           //文件目录项所在扇区
dir_ptr:2000a738        
cltbl:0 

f_read

在FIL结构体中存在一个buf[512],如果每次读写数据的大小都是512(一个扇区)的整数倍,那么f_read不会将数据缓存。如果一次读取的数据小于512字节,那么f_read会读取一个扇区到FIL的buf中,那么下一次读取的时候如果数据落在这个buf中,f_read是不会从磁盘上直接读,而是直接从buf中将数据copy到用户buf中。f_read分支大致如下,其中包括一些写缓存的东西,这里就不再撰述。
这里写图片描述

FRESULT f_read (
    FIL* fp,        /* Pointer to the file object */
    void* buff,     /* Pointer to data buffer */
    UINT btr,       /* Number of bytes to read */
    UINT* br        /* Pointer to number of bytes read */
)
{

    FRESULT res;
    DWORD clst, sect, remain;
    UINT rcnt, cc;
    BYTE csect, *rbuff = (BYTE*)buff;

    *br = 0;    /* Clear read byte counter */

    remain = fp->fsize - fp->fptr;              /* 计算文件未读数据大小 */
    if (btr > remain) btr = (UINT)remain;       /* 如果要读的数据大于剩余带下,那么要读的数据就等于剩余数据大小 */

    for ( ;  btr; rbuff += rcnt, fp->fptr += rcnt, *br += rcnt, btr -= rcnt) {
        if ((fp->fptr % SS(fp->fs)) == 0) {     /* 判断读取的指针是否在sector头上 */
            csect = (BYTE)(fp->fptr / SS(fp->fs) & (fp->fs->csize - 1));    /* 根据fptr计算sector在cluster中的偏移 */
            if (!csect) {                       /* 是否在cluster的头上 */
                if (fp->fptr == 0) {            /* 是否在文件头上 */
                    clst = fp->sclust;          /* clst就等于文件开始簇号 */
                } else {                        /* fptr在文件的中间 */
                    clst = get_fat(fp->fs, fp->clust);  /* 从FAT表中读取fptr的簇号 */
                }
                fp->clust = clst;               /* 把文件的当前簇号clust更新一下 */
            }
            sect = clust2sect(fp->fs, fp->clust);   /*根据簇号获取该簇的起始扇区号*/
            sect += csect;                        /*根据偏移算出簇中的扇区偏移,也就是实际要读取的扇区号*/
            cc = btr / SS(fp->fs);              /* When remaining bytes >= sector size, */

            if (cc) {                           /* 如果要读的数据大小大于一个扇区大小 */     
                if (csect + cc > fp->fs->csize) /* Clip at cluster boundary */
                    cc = fp->fs->csize - csect;
                /*从磁盘上直接读取数据存到用户buf中*/
                if (disk_read(fp->fs->drv, rbuff, sect, cc))
                    ABORT(fp->fs, FR_DISK_ERR);

                if ((fp->flag & FA__DIRTY) && fp->dsect - sect < cc)
                    mem_cpy(rbuff + ((fp->dsect - sect) * SS(fp->fs)), fp->buf, SS(fp->fs));

                rcnt = SS(fp->fs) * cc;         /* Number of bytes transferred */
                continue;
            }

            if (fp->dsect != sect) {
                /*如果要读取的数据小于一个扇区大小(512),那么从磁盘读一个扇区到文件buf中,以便下一次读取使用*/
                if (disk_read(fp->fs->drv, fp->buf, sect, 1))   /* Fill sector cache */
                    ABORT(fp->fs, FR_DISK_ERR);
            }
            fp->dsect = sect;
        }
        rcnt = SS(fp->fs) - ((UINT)fp->fptr % SS(fp->fs));  /* Get partial sector data from sector buffer */
        if (rcnt > btr) rcnt = btr;
        /*从文件buf中读取数据到用户buffer中*/
        mem_cpy(rbuff, &fp->buf[fp->fptr % SS(fp->fs)], rcnt);  /* Pick partial sector */
    }

}

f_write

f_write代码跟f_read很相近,基本上就是f_read的逆操作,最大的差别就是如果f_write写的数据超过了已有文件的大小,那么需要在FAT表建立一个新的entry。

猜你喜欢

转载自blog.csdn.net/u011280717/article/details/80205670