最近做的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中的幾個結構體十分重要,列舉如下,首先是最基礎的文件系統結構體:
view plaincopy to clipboardprint?
/*?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 system object structure (FATFS) */typedef struct {BYTEfs_type;/* FAT子類型,一般在mount時用,置0表示未掛載*/BYTEdrv;/* 物理驅動號,一般為0*/BYTEcsize;/* 每個簇的扇區數目(1,2,4...128) */BYTEn_fats;/* 文件分配表的數目(1,2) *//*FAT文件系統依次為:引導扇區、兩個文件分配表、根目錄區和數據區*/BYTEwflag;/* 標記文件是否被改動過,為1時要回寫*/BYTEfsi_flag;/* 標記文件系統信息是否被改動過,為1時要回寫*/WORDid;/* 文件系統掛載ID */WORDn_rootdir;/* 根目錄區入口(目錄項)的個數(用于FAT12/16)*/#if _MAX_SS != 512WORDssize;/* 每扇區的字節數(用于扇區大于512Byte的flash) */#endif#if _FS_REENTRANT_SYNC_tsobj;/* 允許重入,即定義同步對象,用在tiny中*/#endif#if !_FS_READONLYDWORDlast_clust;/* 最后一個被分配的簇*/DWORDfree_clust;/* 空閑簇的個數*/DWORDfsi_sector;/* 存放fsinfo的扇區(用于FAT32) */#endif#if _FS_RPATHDWORDcdir;/* 允許相對路徑時用,存儲當前目錄起始簇(0:root)*/#endifDWORDn_fatent;/* FAT入口數(簇的數目 + 2)*/DWORDfsize;/* 每個FAT所占扇區*/DWORDfatbase;/* FAT起始扇區*/DWORDdirbase;/* 根目錄起始扇區(FAT32:Cluster#) */DWORDdatabase;/* 數據目錄起始扇區*/DWORDwinsect;/* 當前緩沖區中存儲的扇區號*/BYTEwin[_MAX_SS];/* 單個扇區緩存*/} FATFS;
然后是與之相關的文件和文件夾結構體,附上具體注釋:
view plaincopy to clipboardprint?
/*?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;??
/* File object structure (FIL) */typedef struct {FATFS*fs;/* 所在的fs指針*/WORDid;/* 所在的fs掛載編號*/BYTEflag;/* 文件狀態*/BYTEpad1; /* 不知道含義,也未見程序使用*/DWORDfptr;/* 文件讀寫指針*/DWORDfsize;/* 大小*/DWORDsclust;/* 文件起始簇(fsize=0時為0) */DWORDclust;/* 當前簇*/DWORDdsect;/* 當前數據扇區*/#if !_FS_READONLYDWORDdir_sect;/* 包含目錄項的扇區 */BYTE*dir_ptr;/* Ponter to the directory entry in the window */#endif#if _USE_FASTSEEKDWORD*cltbl;/*指向簇鏈接映射表的指針*/#endif#if _FS_SHAREUINTlockid;/* File lock ID (index of file semaphore table) */#endif#if !_FS_TINYBYTEbuf[_MAX_SS];/* File data read/write buffer */#endif} FIL;
下面是目錄的:
view plaincopy to clipboardprint?
/*?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;??
/* Directory object structure (DIR) */typedef struct {FATFS*fs;/* 同上*/WORDid;WORDindex;/* 當前讀寫索引號 */DWORDsclust;/* 文件數據區開始簇*/DWORDclust;/* 當前簇*/DWORDsect;/* 當前扇區*/BYTE*dir;/* 扇區緩存中當前SFN入口指針,SFN含義未知,猜測和LFN類似,與文件名相關*/BYTE*fn;/* Pointer to the SFN (in/out) {file[8],ext[3],status[1]} */#if _USE_LFNWCHAR*lfn;/* Pointer to the LFN working buffer */WORDlfn_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文件系統移植的文章”就可以搞定了,那里有詳細的解決辦法。
?
評論