FatFs原始碼剖析 FAT16圖文詳解 FatFs官網:http://www.elm-chan.org/fsw/ff/00index_e.html 本文文件形式檔案下載:
FatFsVersion0.01原始碼分析
目錄
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);
函式功能:更改檔案的屬性
概念與分割槽功能簡述:
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表、檔案對應的資料區內容
功能:儲存了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就知道了檔案系統的整個佈局,就可以方便的訪問檔案系統的每一部分。
功能:作為目錄項的指標,既可以用於記錄一個特定檔案在目錄中的位置,又可以用於記錄在目錄中當前目錄項指標的位置(類似與檔案指標)。
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構成完整的目錄指標。
功能:記錄普通檔案(不是目錄檔案)的詳細資訊,比如檔案對應的目錄項位置,檔案起始簇號,檔案指標,檔案大小等。
typedef struct _FIL { DWORD fptr; // 檔案指標,從檔案的起始地址開始,以位元組為單位計算 DWORD fsize; // 檔案大小 DWORD org_clust; // 檔案起始簇號 DWORD curr_clust; // 當前簇號 DWORD curr_sect; // 當前扇區地址,buffer緩衝區中儲存的是該扇區的內容 #ifndef _FS_READONLYDWORD 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。
功能:常用於需要獲取檔案引數的函式中,該結構體用於儲存檔案的屬性,比如說檔案的大小、建立時間、檔名字等。
typedef struct _FILINFO { DWORD fsize; // 檔案大小 WORD fdate; // 日期 WORD ftime; // 時間 BYTE fattrib; // 屬性 char fname[8+1+3+1]; // 名字(8.3 格式) } FILINFO;
☆ Fname儲存了檔案的名字,但是是8.3格式的,這與它在目錄項中儲存的檔名不一樣。
位於FATFS結構體中,作為目錄項或者FAT分配表的讀寫緩衝區。它不是某一個檔案專有的緩衝區,而是整個檔案系統的公共讀寫緩衝區。
<1> 當讀取MBR、DBR的內容時,就需要藉助於這個系統緩衝區。
<2> 當讀寫FAT表時,也需要將磁碟中FAT表的資料讀取到該緩衝區中,或者將緩衝區中的內容寫入到磁碟對應的扇區中。
<3> 當讀寫某一檔案的資訊(而非檔案的資料時),就需要在此緩衝區中操作。而讀寫另外一個檔案的資訊時,則需要將上一個檔案在緩衝區中的內容視情況同步到磁碟中,然後載入此檔案的目錄項對應扇區內容到緩衝區win[512]中。
<4> 檔案包含對應的目錄項和資料空間。目錄項需要在win[]中操作在第<3>點已經說明了,而檔案的資料空間的操作,則是交給了使用者緩衝區,使用者通過使用者緩衝區讀寫檔案的內容。檔案的資料空間,有時也會通過檔案的buffer與使用者空間打交道。這將在⑥中進行講述。
<5> 目錄的操作全部都是在這個緩衝區中操作的,應用程式層也不會為目錄開闢資料空間,所以目錄的資料緩衝空間就是在這個緩衝區。需要注意的是:目錄(目錄檔案)包含目錄項(記錄於上一層目錄中)和資料空間,它的資料空間又擔當了儲存目錄項的功能。不管是目錄的目錄項,還是目錄的資料空間全部使用win[]緩衝區操作。
buffer是一個指向512位元組緩衝區的指標,位於FIL結構體中,也就相當於是FIL中有一個512位元組緩衝區的成員。此512位元組的緩衝區,是一個檔案的專有緩衝區。用於當檔案的讀寫沒有按照512位元組對齊的時候,作為架接在磁碟與使用者讀寫緩衝區之間的臨時緩衝區。
這個函式不是一個可供使用者呼叫的函式,是一個靜態函式,只能被檔案系統其他函式所呼叫。之所以首先討論這個函式是因為它是唯一能夠操作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> 程式執行示意圖
① 程式執行前
② 程式執行中
③ 程式執行後
<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> 程式執行示意圖
<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> 程式執行示意圖
☆ 以只讀的方式開啟一個已經存在的檔案
☆ 新建一個檔案的過程
① 程式剛執行
② 程式執行中
③ 程式執行後
<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> 程式執行示意圖
<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> 程式執行示意圖
<1> 函式原型
FRESULT f_sync ( FIL *fp /* Pointer to the file object */ )
<2> 函式說明
@函式功能 :在關閉檔案之前,同步檔案緩衝區中的內容到磁碟,同步檔案目錄項資訊到磁碟
@輸入引數: fp 檔案資訊指標
@輸出引數:FRESULT 成功與否
<3> 備註
<4> 程式實現方法簡述
判斷檔案是否修改過,如果修改過再判斷檔案buffer緩衝區是否修改過,如果修改過則同步到磁碟中檔案對應的資料空間中。如果檔案修改過,還要更新檔案的目錄項,這時的修改也是在win[]中的。
最後通過呼叫move_window(0),將檔案目錄項資訊同步到磁碟中。
<5> 程式執行示意圖
<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> 程式執行示意圖
<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> 程式執行示意圖
① 程式剛執行
② 程式執行中
③ 程式執行後
<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> 程式執行示意圖
① 程式剛執行
② 程式執行中
③ 程式執行後
<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> 程式執行示意圖
① 程式執行前
② 程式執行中和程式執行後
<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> 程式執行示意圖
① 程式執行
② 程式執行後
檔案系統用了兩種重要的緩衝區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作為磁碟到使用者空間之間的過渡橋樑,使得使用者的零碎讀寫操作,變成了磁碟的整存整取操作,提高了檔案系統的效率。
緩衝區演算法實現關鍵:命中緩衝區就用緩衝區的操作,沒命中替換緩衝區。
FatFs可裁剪成無緩衝區模式的檔案系統,也就是閹割掉buffer,但win[]仍然需要。這樣對於記憶體小的MCU來說,是一件非常有益的事情,當然也會為之付出代價—--不能零存領取。
沒有buffer,這樣使用者空間和檔案資料空間是直通的,讀寫一次至少512位元組,所以使用者的操作都必須是512位元組對齊的,也就是說檔案指標要512位元組對齊,而不像有buffer那樣的任意數值。
由於DBR/FAT/目錄項這些引數的修改,必須是零存領取,所以win[]就必須需要了。
Readonly模式,也就是使用者對於磁碟只有讀取資料的需要,而沒有寫磁碟的要求。這種模式,閹割了程式碼量,對於ROM比較小的MCU來說也是一件非常有益的事情。但是這種模式對於減少RAM消耗量卻起不到大的作用,要想減少RAM只能使用無緩衝buffer的模式。
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原始碼剖析
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>