1. 程式人生 > >FatFs原始碼剖析 FAT16圖文詳解 FatFs官網:http://www.elm-chan.org/fsw/ff/00index_e.html 本文文件形式檔案下載:

FatFs原始碼剖析 FAT16圖文詳解 FatFs官網:http://www.elm-chan.org/fsw/ff/00index_e.html 本文文件形式檔案下載:

FatFsVersion0.01原始碼分析

目錄

一、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;        </span><span style="color: #008000;">//</span><span style="color: #008000;"> 檔案對應的目錄項所在扇區號</span>
BYTE*    dir_ptr;         <span style="color: #008000;">//</span><span style="color: #008000;"> 目錄項在win[]中的入口地址</span>

#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

本文文件形式檔案下載:

  本文PDF :FatFsVersion0.01原始碼註釋.pdf    

  原始碼註釋: FatFsVersion0.01原始碼註釋.zip

分類: 檔案系統, Stm32 好文要頂 關注我 收藏該文 amanlikethis
關注 - 13
粉絲 - 88 +加關注 1 0 « 上一篇: const用法
» 下一篇: C語言字元知識狹區
<div class="postDesc">posted on <span id="post-date">2014-06-17 16:48</span> <a href="https://www.cnblogs.com/amanlikethis/">amanlikethis</a> 閱讀(<span id="post_view_count">2388</span>) 評論(<span id="post_comment_count">2</span>)  <a href="https://i.cnblogs.com/EditPosts.aspx?postid=3793077" rel="nofollow">編輯</a> <a href="#" onclick="AddToWz(3793077);return false;">收藏</a></div>