fatfs結構及源碼分析。

原文鏈接:https://blog.csdn.net/fhb1922702569/article/details/91368360

目錄

一、API的函數功能簡述 

二、FATFS主要數據結構 

  1、FAT32文件系統的結構

  2、FATFS主要數據結構

    ①   FATFS

    ②   DIR 

    ③  FIL

    ④  FILINFO 

    ⑤  win[512]

    ⑥  buffer 

三、函數功能與實現詳細分析

   0、move_window

   1、f_mountdrv 

   2、f_open

   3、f_read

   4、f_write 

   5、f_sync

   6、f_opendir 

   7、f_mkdir

   8、f_unlink 

   9、f_lseek

   10、f_readdir 

 四、FATFS文件系統解惑

  1、win[]和buffer

  2、無緩衝區模式

  3、_FS_READONLY模式 

五、FATFS文件系統函數使用注意事項 

 

一、API的函數功能簡述

1、  FRESULT f_open (FIL*, const char*, BYTE);                        

函數功能:打開或者創建一個文件

2、FRESULT f_read (FIL*, BYTE*, WORD, WORD*);                         

函數功能:讀一個文件

3、FRESULT f_close (FIL*);                                                                      

函數功能:關閉一個文件

4、FRESULT f_lseek (FIL*, DWORD);                                             

函數功能:移動文件的指針

5、FRESULT f_opendir (DIR*, const char*);                              

函數功能:讀一個目錄中的目錄項

6、FRESULT f_readdir (DIR*, FILINFO*);                                             

函數功能:讀取目錄的內容

7、FRESULT f_stat (const char*, FILINFO*);                             

函數功能:獲取文件的狀態

8、FRESULT f_getfree (DWORD*);                                                        

函數功能:獲得可用簇的數量

9、FRESULT f_mountdrv ();

函數功能:初始化文件系統

10、FRESULT f_write (FIL*, const BYTE*, WORD, WORD*);

函數功能:寫文件

11、FRESULT f_sync (FIL*);                                                                     

函數功能:同步文件緩衝區的內容到磁盤中

12、FRESULT f_unlink (const char*);      

函數功能:刪除一個文件或者目錄

13、FRESULT   f_mkdir (const char*);                                                   

函數功能:創建一個目錄

14、FRESULT f_chmod (const char*, BYTE, BYTE);        

函數功能:更改文件的屬性 

二、FATFS主要數據結構

1FAT32文件系統的結構

  概念與分區功能簡述:

MBR:

  保存了磁盤的分區信息(分區的起始地址、分區大小、分區結束地址)

DBR

  保存了當前分區的詳細參數(比如FAT表的位置、FAT表的的大小、簇大小、扇區大小、根目錄中最大目錄項數等等)

FAT

  以簇的形式對數據區重新劃分空間,在FAT表中建立了簇的使用情況,哪些已經被佔用,哪些沒有被佔用;簇鏈的結構,也即是簇與簇之間的連接關係。

目錄

  在目錄中,存在衆多的目錄項,目錄項記錄了文件名、大小、起始地址等等。

目錄項

  目錄中的存儲單位,記錄了每一個文件或者目錄的信息。

數據區

  數據區中純粹的文件數據

扇區

  SD卡最小的讀寫單位,512字節,也就是說一次最少讀取或者寫入的數據是512字節。

☆  說明

<1> 有些SD卡格式化後,並沒有MBR部分,而且SD卡格式化後磁盤上只存在一個磁盤分區。也就是說,即使SD卡上有MBR部分,在MBR的DPT(硬盤分區表)中也只有一個分區記錄。

<2> 位於數據區之前的可以稱之爲文件系統管理區,此區域是以物理扇區爲單位進行管理的,數據區則是以簇爲單位進行管理的。

筆者認爲1

  狹義上的文件專指普通文件,目錄則是位於普通文件的上層,用於管理處在其中的文件或者目錄欄。筆者認爲廣義上的文件包含”目錄和普通文件”。這樣說目錄,表示目錄實際上也是一個文件,只不過是一個管理文件信息的特殊文件。

  可以說文件與目錄既有區別,又有聯繫。普通文件在文件管理的時候,給文件設計了一個管理變量—文件指針,用於指示文件當前讀取或者寫入的位置,它是以字節爲單位進行計算的;而目錄其實也有一個管理讀取或者寫入的位置的變量—目錄指針,它則是以目錄項(32字節)爲單位進行計算的。

筆者認爲2

  文件 = 目錄中的目錄項、FAT表、文件對應的數據區內容

<1> 查看磁盤信息就是查看DBR和FAT表

<2> 讀取文件就是查看目錄項、FAT表和文件對應的數據區內容

<3> 寫文件就是修改目錄項、FAT表、文件對應的數據區內容

2FATFS主要數據結構

①   FATFS

功能:保存了SD卡和文件系統的信息,主要是記錄了DBR中的信息

複製代碼

/* 文件系統對象 */
typedef struct _FATFS {
    BYTE    fs_type;        // FAT文件系統的類型
    BYTE    files;          // 當前操作的文件數
    BYTE    sects_clust;    // 每簇扇區數
    BYTE    n_fats;         // FAT表個數
    WORD    n_rootdir;      // 根目錄中的目錄項項數
    BYTE    dirtyflag;      // 當前存儲在win[]中的內容是否修改過
    
    BYTE    pad1;
    DWORD    sects_fat;      // 每個FAT表的扇區數
    DWORD    max_clust;      // Maximum cluster# + 1
    DWORD    fatbase;        // FAT起始扇區
    DWORD    dirbase;        // 根目錄起始扇區
    DWORD    database;       // 數據區起始扇區
    DWORD    winsect;        // 保存在win[]中的當前扇區地址
    BYTE    win[512];        // 目錄和FAT分配表的緩衝區
} FATFS;  

複製代碼

  擁有參數fats、sects_fat、fatbase、dirbase、database、sects_clust就知道了文件系統的整個佈局,就可以方便的訪問文件系統的每一部分。

②   DIR

功能:作爲目錄項的指針,既可以用於記錄一個特定文件在目錄中的位置,又可以用於記錄在目錄中當前目錄項指針的位置(類似與文件指針)。

複製代碼

typedef struct _DIR {
    DWORD    sclust;      // 目錄起始簇號
    DWORD    clust;       // 當前簇號
    DWORD    sect;        // 當前扇區地址(物理扇區地址)    
    WORD    index;        // 當前索引(目錄中的邏輯索引)
                          // 需要強調一點:索引是從目錄的開始地址算起,每32Bytes加1,而且即使
                          // 切換扇區和簇,index也不會從0開始重新計數;只有當切換目錄時,纔會
                          // 重新清零
} DIR;

複製代碼

☆  說明

<1 > 記錄特定文件在目錄中的位置只需要sect和index

<2> 用於記錄目錄的位置只需要sclust;記錄目錄指針則需要clust、sect、index。

□  本文規定

  本文所說的目錄指針指的是目錄的當前目錄項位置,有clust、sect、index構成完整的目錄指針。

③  FIL

功能:記錄普通文件(不是目錄文件)的詳細信息,比如文件對應的目錄項位置,文件起始簇號,文件指針,文件大小等。

複製代碼

typedef struct _FIL {
    DWORD    fptr;            // 文件指針,從文件的起始地址開始,以字節爲單位計算
    DWORD    fsize;         // 文件大小
    DWORD    org_clust;       // 文件起始簇號
    DWORD    curr_clust;     // 當前簇號
    DWORD    curr_sect;       // 當前扇區地址,buffer緩衝區中存儲的是該扇區的內容
#ifndef _FS_READONLY

    DWORD    dir_sect;        // 文件對應的目錄項所在扇區號
    BYTE*    dir_ptr;         // 目錄項在win[]中的入口地址
    
#endif
    BYTE*    buffer;          // 指向文件讀寫緩衝區(512字節)
    BYTE    flag;             // 文件狀態標識
    BYTE    sect_clust;       // 當前簇中剩餘扇區數
} FIL;

複製代碼

☆  說明

<1> dir_sect、dir_ptr記錄了文件對應在目錄中目錄項的位置

<2> org_clust記錄了文件的起始簇號

<3> fptr爲文件指針,記錄了文件當前讀寫的相對於開始處的偏移量(以字節爲單位)

<4> curr_clust、curr_sect、sect_clust實際上也是文件讀寫指針,只不過它記錄的是物理偏移量,結合着fptr就可以在物理磁盤上確定文件指針的確切位置。

□  本文規定

  本文所說的文件指針在不同的語境中有兩種含義:廣義的文件指針指fptr、curr_clust、curr_sect、sect_clust,狹義的文件指針專指fptr。

④   FILINFO

功能:常用於需要獲取文件參數的函數中,該結構體用於保存文件的屬性,比如說文件的大小、創建時間、文件名字等。

複製代碼

typedef struct _FILINFO {
    DWORD fsize;            // 文件大小
    WORD fdate;             // 日期
    WORD ftime;             // 時間
    BYTE fattrib;           // 屬性
    char fname[8+1+3+1];    // 名字(8.3 格式)
} FILINFO;

複製代碼

☆  Fname保存了文件的名字,但是是8.3格式的,這與它在目錄項中存儲的文件名不一樣。

⑤  win[512]

  位於FATFS結構體中,作爲目錄項或者FAT分配表的讀寫緩衝區。它不是某一個文件專有的緩衝區,而是整個文件系統的公共讀寫緩衝區。

<1> 當讀取MBR、DBR的內容時,就需要藉助於這個系統緩衝區。

<2> 當讀寫FAT表時,也需要將磁盤中FAT表的數據讀取到該緩衝區中,或者將緩衝區中的內容寫入到磁盤對應的扇區中。

<3> 當讀寫某一文件的信息(而非文件的數據時),就需要在此緩衝區中操作。而讀寫另外一個文件的信息時,則需要將上一個文件在緩衝區中的內容視情況同步到磁盤中,然後加載此文件的目錄項對應扇區內容到緩衝區win[512]中。

<4> 文件包含對應的目錄項和數據空間。目錄項需要在win[]中操作在第<3>點已經說明了,而文件的數據空間的操作,則是交給了用戶緩衝區,用戶通過用戶緩衝區讀寫文件的內容。文件的數據空間,有時也會通過文件的buffer與用戶空間打交道。這將在⑥中進行講述。

<5> 目錄的操作全部都是在這個緩衝區中操作的,應用程序層也不會爲目錄開闢數據空間,所以目錄的數據緩衝空間就是在這個緩衝區。需要注意的是:目錄(目錄文件)包含目錄項(記錄於上一層目錄中)和數據空間,它的數據空間又擔當了保存目錄項的功能。不管是目錄的目錄項,還是目錄的數據空間全部使用win[]緩衝區操作。

⑥  buffer

  buffer是一個指向512字節緩衝區的指針,位於FIL結構體中,也就相當於是FIL中有一個512字節緩衝區的成員。此512字節的緩衝區,是一個文件的專有緩衝區。用於當文件的讀寫沒有按照512字節對齊的時候,作爲架接在磁盤與用戶讀寫緩衝區之間的臨時緩衝區。 

三、函數功能與實現詳細分析

0move_window

  這個函數不是一個可供用戶調用的函數,是一個靜態函數,只能被文件系統其他函數所調用。之所以首先討論這個函數是因爲它是唯一能夠操作win[](系統緩衝區)的函數,其他的函數要想操作win[],必須通過調用此函數實現。

<1> 函數原型

BOOL move_window (
         DWORD sector                  /* Sector number to make apperance in the FatFs->win */

)   

<2> 函數說明

@函數功能:win[]操作函數(DBR、FAT表、目錄項)

      ① 讀取新的扇區內容到臨時緩衝區win[]

                 ② 同步win[]中的內容到磁盤

       注意:

              <1> 如果讀取新的扇區號就是現在存儲在win[]中的扇區號,就什麼也不操作

              <2> 如果不同,則根據情況同步win[]到磁盤中,並且將新扇區中的內容讀取到win[]中

           <3> 如果sector爲0,則函數功能變爲同步win[]到磁盤中,不會讀取0扇區的內容到win[]

   @輸入參數:sector 要讀取扇區的扇區號

   @輸出參數: 無

<3> 備註

         此函數被下列函數所直接或者間接調用:

第一類:操作FAT表

① get_cluster

② put_cluster

③ remove_chain

④ create_chain

第二類:操作MBR、DBR

⑤ check_fs

第三類:操作目錄項所在扇區(目錄的數據空間)

⑥ trace_path

<4> 程序實現方法簡述

  首先判斷要讀取的扇區號是否與當前緩存在win[]中的扇區號一致。倘若一致,則無需執行任何操作。倘若不一致,再判斷緩存在win[]中的內容是否被修改過,如果修改過,就需要更新到磁盤,最後還要把新扇區中的內容加載到win[]中。

     傳入參數0,0與當前緩存在win[]的扇區號肯定不一樣,所以一定會同步win[]內容到磁盤中。 

<5> 程序執行示意圖

① 程序執行前

 

② 程序執行中

 

 ③ 程序執行後

 

 1f_mountdrv

<1> 函數原型

FRESULT f_mountdrv ()

<2> 函數說明

@函數功能:

  1.初始化SD卡

  2、填充FatFs對象,即記錄物理磁盤的相關參數

@輸入參數 :無

@輸出參數: 無

<3> 備註

<4> 程序實現方法簡述

     首先調用SD卡初始化函數,對SD卡進行初始化。然後讀取物理磁盤0號扇區的內容,判斷是否是DBR扇區。如果不是DBR扇區,那麼肯定就是MBR扇區,再從MBR扇區中獲取DBR扇區的地址,將DBR扇區的內容調取到win[]中。

     接下來從win[]中,填充FatFs類型的系統對象,這樣物理磁盤和文件系統的參數就被保存到了這個對象中。以後,程序就可以從全局變量--FatFs類型的變量,訪問文件系統的每一個區域。 

<5> 程序執行示意圖

 

2f_open

<1> 函數原型

FRESULT f_open (
         FIL *fp,                       /* 指向文件結構體變量 */
         const char *path,              /* 指向文件路徑 */
         BYTE mode                      /* 存取方式和打開方式 */

)

<2> 函數說明

@函數功能:以指定的方式打開或者新建一個文件。如果打開或者創建成功,

            會填充fp指向的文件信息變量(包含文件的目錄項確切位置和文件的信息)。

@輸入參數:fp              指向文件信息變量的指針

                 path         指向文件的路徑

                 mode       打開方式

@輸出參數:FR_OK      打開或者創建成功

               其他值  打開或者創建失敗

<3> 備註

<4> 程序實現方法簡述

① 以只讀的方式打開一個已經存在的文件

     首先調用函數trace_path搜索文件系統中是否存在目標文件,如果不存在就返回失敗;如果存在就返回文件的目錄項位置(dirscan、dir),並且將目錄項所在扇區的內容加載到win[]中。

     接下來就是從win[]中,將文件目錄項的參數稍作轉化後傳入FIL類型的變量中。到此,一個文件就算完整的打開了。注意打開文件並不是打開文件的內容,而是文件的目錄項,知道了文件的目錄項就知道了如何去查看文件的內容。

     以後,通過FIL類型的變量就可以操作對應的文件

② 新建一個文件

  首先調用函數trace_path搜索文件系統中是否存在目標文件,因爲是新建文件肯定不存在。那麼不存在的文件就返回新建文件當前文件夾的目錄指針位置(dirscan、dir)--第一個空目錄項所在位置,並且將當前目錄指針所在扇區的內容加載到win[]中。

  首先給新建文件在當前文件夾中預定一個目錄項位置,然後填入新建文件的目錄項初始值(文件名、擴展名、屬性、創建時間、更新時間)到win[]中。注意這裏並不會將新建文件目錄項所在扇區同步到磁盤中,只有當調用f_sync函數時纔會將文件的目錄項所在扇區同步到磁盤。

  創建一個新文件,只會在其上一層目錄中添加對應的目錄項並初始化,並不會給文件分配數據空間,當然文件的大小肯定是0。

③ 重建一個文件

  首先調用函數trace_path搜索文件系統中是否存在目標文件,因爲是重建文件肯定存在。那麼就返回文件的目錄項位置(dirscan、dir),並且將目錄項所在扇區的內容加載到win[]中。

  重建首先將文件的簇鏈刪除,然後設置文件起始位置和文件大小爲空,還需要初始化文件的屬性、創建時間和修改時間。這裏的修改都只是在win[]中進行的,並沒有同步到磁盤。只有當調用f_sync函數時纔會將文件的目錄項所在扇區同步到磁盤。

  重建文件更改了原來文件在目錄中的目錄項信息,重建文件並沒有分配簇,也就是沒有分配數據空間。

 <5> 程序執行示意圖

☆  以只讀的方式打開一個已經存在的文件

 

 ☆  新建一個文件的過程

① 程序剛執行

 

② 程序執行中

 

 ③ 程序執行後

 

 3f_read

<1> 函數原型

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

<2> 函數說明

@函數功能 :文件讀操作

@輸入參數:         fp              文件信息指針

                          buff  指向用戶緩衝區

                          btr             準備讀取的字節數

                          br               指向實際讀取字節數的變量

@輸出參數:FRESULT 成功與否

<3> 備註

       此函數在讀取文件內容後,還會移動文件指針到下一此讀寫操作的起點。

<4> 程序實現方法簡述

  讀文件的情況有些複雜,不同的情況有不同的處理方法。在“<5>程序執行示意圖”中,我展示了一種還算全面的情況,就以這種情況爲例進行說明。開始讀的時候,文件指針並沒有位於扇區邊界上(512字節對齊),讀取的跨度爲3個簇。

      首先讀沒有對齊扇區的剩餘內容,其實這個內容在以前的函數(以前的函數移動了文件指針)已經將這個扇區的內容加載到了buffer中。所以,直接從緩衝區buffer中讀取此扇區文件指針以後的剩餘內容到用戶緩衝區。

   接下來,讀取第一個簇的剩餘一個扇區的內容到用戶緩衝區。通過get_cluster函數從FAT表中,獲取第二個簇鏈的位置。然後一次性的將一個簇鏈的所有扇區內容讀取到用戶緩衝區中。再通過get_cluster函數從FAT表中,獲取第三個簇鏈的位置。然後將第三個簇鏈的第一個扇區內容讀取到用戶緩衝區中。

  最後,將最後所需要讀取剩餘內容所在的扇區(剩餘部分不夠一個扇區)讀取到buffer中,然後再從buffer中讀取所需要的剩餘內容到用戶緩衝區中。到這裏爲止,整個讀取操作已經完成。

  由於buffer中還有一部分內容沒讀,假設繼續調用函數f_read函數讀取數據,那麼肯定先從這個buffer緩衝區中將文件指針以後的扇區剩餘內容讀取到用戶緩衝區。

5> 程序執行示意圖

 4f_write

<1> 函數原型

FRESULT f_write (
         FIL *fp,                        /* Pointer to the file object */
         const BYTE *buff,           /* Pointer to the data to be written */
         WORD btw,                       /* Number of bytes to write */
         WORD *bw                        /* Pointer to number of bytes written */
)

<2> 函數說明

@函數功能 :文件寫操作,只對文件的數據區進行寫入,並沒有更新對應的目錄項。

                 如果寫入時,最後寫入的數據字節沒有完美的扇區對齊,那麼肯定會將需要寫入磁盤的一個扇區

              在文件緩衝區中進行緩存

@輸入參數:           fp              文件信息指針

                           buff   指向讀取的用戶緩衝區

                           btw           準備寫入的字節數                         

                           bw             返回實際寫入的字節數

@輸出參數:FRESULT 成功與否

<3> 備註

  此函數在寫完文件內容後,還會移動文件指針到下一此讀寫操作的起點。

<4> 程序實現方法簡述

  寫文件的情況與讀取文件內容類似,不同的情況有不同的處理方法。在“<5>程序執行示意圖”中,我展示了一個全面的情況,就以這種情況爲例進行說明。開始寫的時候,文件指針並沒有位於扇區邊界上(512字節對齊),寫入數據的跨度爲3個簇。

     首先寫入沒有對齊扇區的剩餘內容,其實這個內容在以前的函數(以前的函數移動了文件指針)已經將這個扇區的內容加載到了buffer中。所以,將用戶緩衝區中對應的內容寫入到buffer中(從文件指針開始到buffer結束的這部分空間)。然後再將buffer中的內容寫入到磁盤對應的扇區。

  接下來,將用戶緩衝區寫入到第一個簇的剩餘一個扇區中。通過creat_chain函數從FAT表中,獲取第二個簇鏈的位置(如果是文件有剩餘簇鏈則使用文件的剩餘簇鏈,如果已經用完則重新從FAT表中搜索一個空的簇鏈連接到此文件中,也就是更改了文件的大小)。然後一次性的將用戶緩衝區寫入到第二個簇鏈的所有扇區中。再通過get_cluster函數從FAT表中,獲取第三個簇鏈的位置。然後將用戶緩衝區寫入到第三個簇鏈的第一個扇區中。

  最後,將最後所需要寫入剩餘內容所在的扇區(剩餘部分不夠一個扇區)讀取到buffer中,然後再將用戶緩衝區中剩餘內容寫入到buffer中。到這裏爲止,整個讀取操作已經完成。注意這裏並沒有將buffer的內容寫入到磁盤中。當調用f_sync函數的時候纔會將buffer的內容同步到磁盤。

  在函數返回之前,還需要判斷文件大小是否更改了,如果大小更改了則要更新文件的大小,並將FA__WRITTEN記錄到文件的flag中。這樣做的目的是爲了當執行f_sync時,可以根據FA__WRITTEN判斷出文件修改過,從而更新文件的目錄項。

☆  假設

  由於buffer中還有一部分內容沒操作,

假設1繼續調用函數f_write函數寫入數據

  那麼肯定先將用戶緩衝區的內容寫入到這個buffer緩衝區中。只有超出了buffer緩衝區的範圍,纔會將這個buffer緩衝區的內容同步到磁盤,並且讀取下一個扇區的內容到buffer中(假設文件指針仍然沒有對齊)。

假設2:調用函數f_read函數讀取數據

  先從這個buffer緩衝區中將文件指針以後的扇區剩餘內容讀取到用戶緩衝區,而不會從磁盤中讀取。

☆  總結

  buffer的妙處,提高了讀寫的效率,避免了重複讀寫磁盤。

<5> 程序執行示意圖

 

 5f_sync

<1> 函數原型

FRESULT f_sync (
         FIL *fp                /* Pointer to the file object */
)

<2> 函數說明

@函數功能 :在關閉文件之前,同步文件緩衝區中的內容到磁盤,同步文件目錄項信息到磁盤

@輸入參數: fp    文件信息指針

@輸出參數:FRESULT 成功與否

<3> 備註

<4> 程序實現方法簡述

       判斷文件是否修改過,如果修改過再判斷文件buffer緩衝區是否修改過,如果修改過則同步到磁盤中文件對應的數據空間中。如果文件修改過,還要更新文件的目錄項,這時的修改也是在win[]中的。

       最後通過調用move_window(0),將文件目錄項信息同步到磁盤中。 

<5> 程序執行示意圖

 

 6f_opendir

<1> 函數原型

FRESULT f_opendir (
         DIR *scan,            /* Pointer to directory object to initialize */
         const char *path      /* Pointer to the directory path, null str means the root */

)

<2> 函數說明

@函數功能 :打開一個目錄

@輸入參數: scan:指向返回找到的目錄項結構體

                  path  指向路徑

@輸出參數:FRESULT 成功與否

<3> 備註

<4> 程序實現方法簡述

  首先調用函數trace_path搜索文件系統中是否存在所要打開的目錄,如果不存在就返回失敗;如果存在就返回目錄對應目錄項的位置(dirscan、dir),並且將目錄對應目錄項所在扇區的內容加載到win[]中。

     接下來判斷找到的是不是一個目錄。如果就是一個目錄的話,就從win[]中將目錄對應目錄項的參數稍作轉化後傳入DIR類型的變量中。到此,一個目錄就算完整的打開了。注意打開目錄並不是打開目錄的內容,而是目錄對應的目錄項,知道了目錄對應的目錄項就知道了如何去查看目錄的內容。

     以後,通過DIR類型的變量就可以操作對應的目錄 

<5> 程序執行示意圖

 7f_mkdir

<1> 函數原型

FRESULT f_mkdir (
         const char *path               /* Pointer to the directory path */
)

<2> 函數說明

@函數功能 :創建一個目錄

         注意:新建一個目錄,它雖然是一個空目錄(有效存儲內容爲0),但是

         系統已經爲它分配了一個簇的數據空間,用於保存它的目錄項。這是與新建一個

         普通文件區別很大的地方。

         另外,新建一個目錄時,對新建目錄在上一層目錄的目錄項以及新建目錄中的目

         錄項的初始化,全部都在win[]中進行操作。

@輸入參數:          path  指向路徑的指針

@輸出參數:FRESULT 成功與否

<3> 備註

<4> 程序實現方法簡述

  首先調用函數trace_path搜索文件系統中是否存在目標目錄,因爲是新建目錄肯定不存在。那麼不存在目錄時就返回新建目錄所在當前文件夾的目錄指針(dirscan、dir)--第一個空目錄項位置,並且將當前目錄指針所在扇區的內容加載到win[]中。

  接下來給新建目錄在當前文件夾中預定一個目錄項位置。然後調用creat_chain函數在FAT表中爲新建目錄找到一個可用的數據簇,再調用move_window(0)同步FAT表到磁盤中。爲新建目錄的數據簇初始化,並且初始化第一個目錄項。最後,填入新建目錄的目錄項初始值(目錄名、屬性、創建時間 、數據簇起始位置)到win[]中。然後同步到磁盤中,完成整個新建目錄的工作。

☆  注意

<1> 創建一個新目錄,不僅會在其上一層目錄中添加對應的目錄項並初始化,並且會給新建目錄分配一個簇的數據空間,並進行初始化。

<2> 新建一個目錄時,會將新建目錄的數據簇和對應目錄項所在扇區都同步到磁盤中,這與文件必須通過調用f_sync才能同步是不一樣的。

<3> 新建一個目錄會給目錄分配數據空間,而新建文件則是沒有的,這也是一個巨大的差別。

<4> 新建一個目錄的所有操作都是在win[]中進行的,不管是新建目錄的對應目錄項,還是新建目錄的數據空間都是在win[]中進行的。

<5> 程序執行示意圖

① 程序剛執行

 

② 程序執行中

 

 ③ 程序執行後

 

 8f_unlink

<1> 函數原型

FRESULT f_unlink (
         const char *path                         /* Pointer to the file or directory path */
)

<2> 函數說明

@函數功能 :刪除一個文件或者目錄

           1、刪除目錄或者文件的簇鏈(回收數據空間)

           2、文件或者目錄的目錄項被設置成爲刪除(0xE5),注意目錄項並沒有回收,只是標記爲刪除

@輸入參數: path  指向路徑的指針

@輸出參數:FRESULT 成功與否

<3> 備註

<4> 程序實現方法簡述

  首先調用函數trace_path搜索文件系統中是否存在所要刪除的目錄或者文件,如果不存在就返回失敗;如果存在就返回對應目錄項的位置(dirscan、dir),並且將對應目錄項所在扇區的內容加載到win[]中。

      判斷要刪除的是不是目錄,如果是目錄還要判斷是不是非空目錄,如果是非空目錄則不允許刪除。如果是空目錄,那麼就可以刪除。

      刪除文件或者目錄時,首先刪除簇鏈(數據空間),然後修改目錄項爲刪除狀態(0xE5),最後同步目錄項所在扇區win[]緩衝區到磁盤中,完成刪除。    

<5> 程序執行示意圖

① 程序剛執行

 

② 程序執行中

 

 ③ 程序執行後

 

 9f_lseek

<1> 函數原型

FRESULT f_lseek (
         FIL *fp,              /* Pointer to the file object */
         DWORD ofs               /* File pointer from top of file */
)

<2> 函數說明

@函數功能 :移動文件指針,實際上就是修改文件指針(當前簇號、當前扇區號、文件指針fptr)

@輸入參數: fp    文件信息指針

              ofs 定位文件指針的位置(從文件頭部開始的偏移量)

@輸出參數: fp 返回重新定位後的文件信息(包含文件指針)

                  FRESULT 成功與否

<3> 備註

<4> 程序實現方法簡述

     首先要對緩衝區buffer進行同步,因爲文件指針中的curr_sect代表的是當前處在buffer中物理扇區號。現在要移動指針,也就是要移動當前扇區號curr_sect,所以要先將buffer進行同步。

  偏移量進行修正,因爲可能偏移量超過了文件的大小,修正後的偏移量直接賦給fptr。

     接下來根據偏移量,結合着當前的文件信息FIL類型的對象計算出移動指針後的簇號、扇區號。倘若移動後的文件指針沒有512字節對齊,則還需要將curr_sect指向的物理扇區內容讀取到buffer中。這樣接下來的文件讀寫操作纔不會出錯。

 <5> 程序執行示意圖

① 程序執行前

 

 ② 程序執行中和程序執行後

 

 10f_readdir

<1> 函數原型

FRESULT  f_readdir (
         DIR *scan,               /* Pointer to the directory object */
         FILINFO *finfo           /* Pointer to file information to return */
)

<2> 函數說明

@函數功能 :從當前目錄項指針處讀取一個目錄項,並且移動目錄指針到下一個索引

@輸入參數: scan:要讀取的目錄

                  finfo 目錄的信息

                       finfo->fname[0] = 0           :這是一個空目錄項

                       finfo->fname[0] = others :這是一個非空目錄項

@輸出參數:FRESULT 成功與否

<3> 備註

<4> 程序實現方法簡述

         首先將目錄指針當前所在物理扇區讀取到win[]中,然後調用get_fileinifo函數從當前目錄指針處讀取當前目錄項並處理後存入finfo中。最後,還要移動目錄項指針到下一個索引位置。

<5> 程序執行示意圖

① 程序執行

 

② 程序執行後

 

 四、FATFS文件系統解惑

1win[]buffer

     文件系統用了兩種重要的緩衝區win[]和buffer。這兩種緩衝區的用途在第二章和第三章中已經闡述。但是深層次的思考,它們對應磁盤扇區的讀寫時機是什麼?也即是說什麼時候需要從磁盤中讀取數據以更新緩衝區,又什麼時候需要將緩衝區中的內容同步到磁盤。

     常見的磁盤比如說SD卡、硬盤,它們的扇區大小512字節,對磁盤進行一次讀寫的字節數要是512的倍數,而非隨意的幾個字節都可以。

buffer的魅力:

  如果文件系統設計成這樣:從當前文件指針處,讀取2個字節到用戶緩衝區,需要將磁盤對應的扇區讀一次;再次讀2個字節到用戶緩衝區,又從磁盤的對應扇區讀一次,那麼效率肯定很慢,頻繁的讀取相同的扇區是一件很蠢的事。

  在第二次讀2個字節到用戶緩衝區中的時候,可以看看需要的數據是否就處於buffer緩衝區中(第一次已經將一個扇區讀如buffer),如果在就直接從buffer中讀,如果不在再從磁盤中讀。顯然,這樣做可以避免重複讀取相同的扇區,只有到了迫不得已的時候纔會讀取磁盤其他的扇區。這樣做提高了系統的效率。

  對於寫文件時,buffer的作用也是類似的:第一次寫入2個字節,需要先將文件指針對應磁盤的扇區讀入buffer,然後通過用戶緩衝區修改對應2個字節的內容;第二次寫入2個字節的時候,如果寫入的位置仍然在當前扇區的話,可以直接將用戶緩衝區的內容替換掉接下來的2個字節的內容………如果迫不得已需要更換扇區,那將緩衝區中的內容同步到磁盤中,然後讀入下一個扇區的內容到緩衝區中。

win[]的魅力:

     操作win[]最底層的函數move_window,如果讀取的扇區號不變那麼沒必要重新讀取磁盤。如果讀取扇區號改變了,先將win[]同步到磁盤,然後讀取另外一個扇區的內容到win[]中。

  這樣的好處就是:假設我們如果不需要切換扇區,只對一個扇區進行讀寫操作。只在第一次讀寫的時候,將磁盤的內容讀取到緩衝區中,接下來的讀寫操作實際上只是在緩衝區中進行的,而並沒有實時的同步到磁盤中。一旦當我們切換扇區號的時候,就將win[]中的數據同步到磁盤中。顯然,這樣的效率很高,要比每一次都同步到磁盤中快的多。

緩衝區在常見情況的功效:

     通常我們讀寫一個文件,都不是一下很大範圍的操作看,經常都是局部範圍的讀寫。局部範圍的讀寫只在第一次讀寫時,將磁盤中的數據讀取到緩衝區中,接下來的操作全部都在緩衝區中進行。最後同步文件時,纔將緩衝區的內容寫入到磁盤。

總結:

  win[]和buffer作爲磁盤到用戶空間之間的過渡橋樑,使得用戶的零碎讀寫操作,變成了磁盤的整存整取操作,提高了文件系統的效率。

  緩衝區算法實現關鍵:命中緩衝區就用緩衝區的操作,沒命中替換緩衝區。

2、無緩衝區模式

     FatFs可裁剪成無緩衝區模式的文件系統,也就是閹割掉buffer,但win[]仍然需要。這樣對於內存小的MCU來說,是一件非常有益的事情,當然也會爲之付出代價—--不能零存領取。

     沒有buffer,這樣用戶空間和文件數據空間是直通的,讀寫一次至少512字節,所以用戶的操作都必須是512字節對齊的,也就是說文件指針要512字節對齊,而不像有buffer那樣的任意數值。

     由於DBR/FAT/目錄項這些參數的修改,必須是零存領取,所以win[]就必須需要了。

3_FS_READONLY模式

     Readonly模式,也就是用戶對於磁盤只有讀取數據的需要,而沒有寫磁盤的要求。這種模式,閹割了代碼量,對於ROM比較小的MCU來說也是一件非常有益的事情。但是這種模式對於減少RAM消耗量卻起不到大的作用,要想減少RAM只能使用無緩衝buffer的模式。 

五、FATFS文件系統函數使用注意事項

1、不使用一個文件的時候,要調用f_close或者f_sync函數將文件同步到磁盤中。

2、f_read、f_write、f_lseek、f_sync、f_close在使用前要先打開文件,也即是調用f_open函數。

3、f_chmod、f_stat無需事先打開文件,可以直接使用

3、f_readdir使用前要先打開目錄,也就是調用函數f_opendir

 

參考博客: FatFs源碼剖析

               FAT16圖文詳解

FatFs官網:http://www.elm-chan.org/fsw/ff/00index_e.html

.com/amanlikethis/p/3793077.html

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