http://blog.csdn.net/juana1/article/details/6867829
最近做的spi flash,本打算弄個文件系統,由於之前用過了JFFS、YAFFS和TrueFFS,代碼量都相當的大,這次想找款代碼量不那么嚇人的,學習一下,聽說配置會相對復雜一些。選來選去,最終選定了FatFS,代碼量足夠的小,最新的R0.09版本只有1個.c文件(當然,還有一個底層的要自己寫,option文件夾里的無視),老點版本就更小了。而且更新很頻繁,用戶量也夠大,就選定它了。盡管最后由於硬件和項目原因未能實際的移植它到vxWorks,但學過的還是要記錄下。
在這里http://elm-chan.org/fsw/ff/00index_e.html下載源碼,只有800多K,小的可憐,還可以下載示例程序,有AVR、Win32、lpc等多平台已實現的方案。打開看src文件夾,一個option文件夾、00readme.txt、diskio.h、ff.c、ff.h、ffconf.h和interger.h。移植時需要修改的文件主要包括ffconf.h和interger.h,后者是在它的定義與目標平台上的有沖突,或者用的不習慣時修改的。
在做具體修改之前,先大概閱讀下FatFS的源代碼,可以先讀integer.h,了解所用的數據類型,然后是ff.h,了解文件系統所用的數據結構和各種函數聲明,再就是diskio.h,了解與介質相關的數據結構和操作函數。ff.c這個文件相對較大,可以在最后將所實現的函數大致掃描一遍,之后根據用戶應用層程序調用函數的次序仔細閱讀相關代碼。各個文件都可以直接用記事本打開查閱,非常方便。ff.h中的幾個結構體十分重要,列舉如下,首先是最基礎的文件系統結構體:
- /* File system object structure (FATFS) */
- typedef struct {
- BYTE fs_type; /* FAT子類型,一般在mount時用,置0表示未掛載*/
- BYTE drv; /* 物理驅動號,一般為0*/
- BYTE csize; /* 每個簇的扇區數目(1,2,4...128) */
- BYTE n_fats; /* 文件分配表的數目(1,2) */
- /*FAT文件系統依次為:引導扇區、兩個文件分配表、根目錄區和數據區*/
- BYTE wflag; /* 標記文件是否被改動過,為1時要回寫*/
- BYTE fsi_flag; /* 標記文件系統信息是否被改動過,為1時要回寫*/
- WORD id; /* 文件系統掛載ID */
- WORD n_rootdir; /* 根目錄區入口(目錄項)的個數(用於FAT12/16)*/
- #if _MAX_SS != 512
- WORD ssize; /* 每扇區的字節數(用於扇區大於512Byte的flash) */
- #endif
- #if _FS_REENTRANT
- _SYNC_t sobj; /* 允許重入,即定義同步對象,用在tiny中*/
- #endif
- #if !_FS_READONLY
- DWORD last_clust; /* 最后一個被分配的簇*/
- DWORD free_clust; /* 空閑簇的個數*/
- DWORD fsi_sector; /* 存放fsinfo的扇區(用於FAT32) */
- #endif
- #if _FS_RPATH
- DWORD cdir; /* 允許相對路徑時用,存儲當前目錄起始簇(0:root)*/
- #endif
- DWORD n_fatent; /* FAT入口數(簇的數目 + 2)*/
- DWORD fsize; /* 每個FAT所占扇區*/
- DWORD fatbase; /* FAT起始扇區*/
- DWORD dirbase; /* 根目錄起始扇區(FAT32:Cluster#) */
- DWORD database; /* 數據目錄起始扇區*/
- DWORD winsect; /* 當前緩沖區中存儲的扇區號*/
- BYTE win[_MAX_SS]; /* 單個扇區緩存*/
- } FATFS;
然后是與之相關的文件和文件夾結構體,附上具體注釋:
- /* File object structure (FIL) */
- typedef struct {
- FATFS* fs; /* 所在的fs指針*/
- WORD id; /* 所在的fs掛載編號*/
- BYTE flag; /* 文件狀態*/
- BYTE pad1; /* 不知道含義,也未見程序使用*/
- DWORD fptr; /* 文件讀寫指針*/
- DWORD fsize; /* 大小*/
- DWORD sclust; /* 文件起始簇(fsize=0時為0) */
- DWORD clust; /* 當前簇*/
- DWORD dsect; /* 當前數據扇區*/
- #if !_FS_READONLY
- DWORD dir_sect; /* 包含目錄項的扇區 */
- BYTE* dir_ptr; /* Ponter to the directory entry in the window */
- #endif
- #if _USE_FASTSEEK
- DWORD* cltbl; /*指向簇鏈接映射表的指針*/
- #endif
- #if _FS_SHARE
- UINT lockid; /* File lock ID (index of file semaphore table) */
- #endif
- #if !_FS_TINY
- BYTE buf[_MAX_SS]; /* File data read/write buffer */
- #endif
- } FIL;
下面是目錄的:
- /* Directory object structure (DIR) */
- typedef struct {
- FATFS* fs; /* 同上*/
- WORD id;
- WORD index; /* 當前讀寫索引號 */
- DWORD sclust; /* 文件數據區開始簇*/
- DWORD clust; /* 當前簇*/
- DWORD sect; /* 當前扇區*/
- BYTE* dir; /* 扇區緩存中當前SFN入口指針,SFN含義未知,猜測和LFN類似,與文件名相關*/
- BYTE* fn; /* Pointer to the SFN (in/out) {file[8],ext[3],status[1]} */
- #if _USE_LFN
- WCHAR* lfn; /* Pointer to the LFN working buffer */
- WORD lfn_idx; /* Last matched LFN index number (0xFFFF:No LFN) */
- #endif
- } DIR;
其他類似f_mount、f_open等接口API就不細說了,在掛載的時候其實真正起作用的是chk_mounted函數,在這里才會將掛載分區的相關信息分配到FatFS結構體中;還有一個get_fat函數,也比較重要,在f_open和許多目錄操作的函數中都有用到,而且FAT入口這個表達也十分晦澀,而它又調用了一個move_window的函數,也是十分晦澀難懂,可能是我英語太爛的緣故吧。實際上,move_window的作用是改變文件系統的當前工作扇區,如果要遷移到的是當前扇區,直接返回,如果不是,就將原扇區寫回,若是FAT表,還要寫進備份區。
熟悉了代碼結構后,現在開始作修改了,首先修改ffconf.h文件配置與硬件相關的文件系統特性,然后自己添加一套底層操作即可。先看ffconf.h,里面定義了很多宏,可以根據自己需要一一配置:
先看功能配置:
_FS_TINY:文件系統為標准的還是微型的,默認為標准的(0);
_FS_READONLY:文件系統是否為只讀,默認為可讀寫(0),若只讀則f_write、f_sync、 f_unlink、f_mkdir、f_chmod、f_rename、f_truncate和f_getfree不可用;
_FS_MINIMIZE:裁剪文件系統的功能,默認為全部功能(0),若為1、2則會移除大部分鏈接、目錄等功能;
_USE_STRFUNC:是否允許字符串操作,默認為不允許(0),這個看個人需求,一般情況下設置為1即可,如果工作在windows下,為保證文件兼容性(如換行符’\n’和回車符’\r’)建議將此項設置為2;
_USE_MKFS:是否允許使用f_mkfs函數,默認為0,用於創建文件夾,建議開啟;
_USE_FORWARD:用於允許f_forward函數,只有開啟tiny文件系統時才用到,該函數用於將讀寫的數據立即轉存到數據流中,以節省RAM空間;
_USE_FASTSEEK:是否開啟快速索引,默認為0,開啟后,會使用FIL結構體中的cltbl元素來加快搜索;
_CODE_PAGE:指定目標系統使用的OEM代碼頁,默認為日語(932),改為936簡體中文;OEM是什么意思呢?在OS編碼中,unicode是一種雙字節字符編碼,無論中文還是英文,或者其他語言統一到2個字節,它與現有的任何編碼(ASCII,GB等)都不兼容。WindowsNT(2000)的內核即使用該編碼,所有數據進入內核前轉換成UNICODE,退出內核后在轉換成版本相關的編碼(通常稱為OEM,在簡體中文版下即為GB);
_USE_LEN、_MAX_LEN、_LFN_UNICODE:這三個的意思不是很清楚,但是確定是與長文件名有關的,不建議開啟,否則又要多加函數,麻煩;
_FS_RPATH:是否允許相對路徑,讓我選擇就不開啟,否則邏輯變得復雜不說,代碼量也變多了一些;
再看硬件相關配置:
_VOLUMES:磁盤(flash)邏輯卷數,默認為1,不建議修改;
_MAX_SS:扇區大小,默認512Byte,最大可設置4096Byte;
_MULTI_PARTITION:分區選項,默認為0,即一個分區,若想要多分區可自行設置;
_USE_ERASE:是否允許扇區擦除,默認為0,若開啟則要在disk_ioctl函數中添加擦除命令代碼;
最后是文件系統配置:
_WORD_ACCESS:數據遞進格式,默認為0,即以字節為單位遞進,兼容性更強,若你的系統最新單位為字(2Byte),則可設為1;
_FS_REENTRANT、_FS_TIMEOUT、_SYNC_t:這三個選項與文件系統是否允許重入有關,所直白點,就是能否被多線程同時訪問,像RTOS中,一般建議開啟,_SYNC_t可定義為對應OS中的操作對象,windows下為HANDLE,uCos中為OS_EVENT,vxWorks中為SEMAPHORE。另外,開啟后還需要添加ff_req_grant、ff_rel_grant和ff_del_syncobj三個函數,實際上實現的功能就是申請互斥量、釋放互斥量和刪除互斥量的意思,可以定義OS封裝即可;
_FS_SHARE:和上面的類似,表示文件系統最大允許同時打開多少文件,默認為0,即只能打開一個。
在配置這些選項的時候,可以根據定義閱讀ff.c文件中的相關代碼,基本上能對整體的結果有了了解,完成了ffconf.h后,再就是編寫底層接口了,在新一點的FatFs中,並未提供函數接口模版,可以下老版的拷過來,也可以打開doc文件夾下的幫助文檔00index_e.htm文件,里面有底層函數接口的格式及各個參數的描述。至於底層驅動,我只做過spi flash的,這個可以參考我上一篇文章。需要注意的是,底層讀寫函數中的參數sector指的是扇區的序號,需要自己換算成驅動接口中的字節位置。
到這里,移植基本完成了,如果你的文件系統出現LD_WORD(ptr) (WORD)(*(WORD*)(BYTE*)(ptr))有問題(數據異常終止DATA ABORT exception之類的)的情況,請百度搜索“轉一篇比較詳細介紹FatFS文件系統移植的文章”就可以搞定了,那里有詳細的解決辦法。