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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章