本文將經過實際讀取一個FAT32格式的U盤來簡單瞭解和學習FAT32文件系統的格式。雖然目前windwos操做系統的主流文件系統格式是NTFS,可是FAT32因爲其兼容性緣由,仍是有必定的學習價值。爲了能作出一個窗體程序提供直觀的感受,本文的代碼採用c#編寫,對應的c++代碼也會附上。c++
一、本文的目標編程
2.1 FAT32的構成windows
三、引導區api
3.2 BPB參數app
3.3 程序實現異步
FAT32是Windwos系統硬盤格式分區的一種。這種格式採用32位的文件分配表,使其對磁盤的管理能力大大加強,突破了FAT16對配一個分區的容量只有2GB的限制。雖然目前已被更優異的NTFS分區格式所取代[1]。其實說白了就是FAT表的每一項長度都是32位,因此叫作FAT32。至於每一項存放的內容是什麼,下面的內容會慢慢進行分析。函數
FAT32 文件系統將邏輯盤的空間劃分爲三部分,依次是引導區 (BOOT區)、文件分配表區(FAT區)、數據區(DATA區)[2]。引導區和文件分配表區又合稱爲系統區。本文將簡單學習引導區的內容,文件分配表區和數據區將在下一篇文章中學習。學習
引導區從第一扇區開始,保存了每一個扇區的字節數,一個簇的扇區數,FAT表的起始位置,FAT表的個數以及FAT表的扇區數等信息。以後還留有若干保留扇區。首先咱們先看一下如何從一個U盤當中讀取引導區(這裏只讀取第一個扇區,接下來的讀取方法相同)。爲了直接讀取磁盤的邏輯扇區,咱們須要用到windows api當中的幾個函數,分別是CreateFile(這裏用來建立磁盤的句柄),ReadFile(這裏用於讀取磁盤扇區)。首先來看一下這兩個函數的定義:
1 HANDLE WINAPI CreateFile( 2 _In_ LPCTSTR lpFileName, // 要打開的文件的名或設備名。 3 _In_ DWORD dwDesiredAccess, // 指定類型的訪問對象。 4 _In_ DWORD dwShareMode, // 文件共享模式 5 _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 定義了文件的安全特性 6 _In_ DWORD dwCreationDisposition, 7 _In_ DWORD dwFlagsAndAttributes, 8 _In_opt_ HANDLE hTemplateFile // 返回句柄 9 );
這個函數在msdn上能夠查到,這裏須要說明一下的是,dwShareMode須要指定爲FILE_SHARE_WRITE才能讀取扇區的數據(這裏是爲何我也不太清楚爲何,但願高人指教)。dwCreationDisposition須要制定爲OPEN_EXISTING ,表示文件必須已經存在,由設備提出要求。函數如執行成功,則返回文件句柄。不然返回的句柄 = INVALID_HANDLE_VALUE表示出錯,會設置GetLastError。具體失敗緣由能夠查詢ErrorCode。
第二個函數是ReadFile,其定義以下:
1 BOOL ReadFile( 2 HANDLE hFile, //文件的句柄 3 LPVOIDl pBuffer, //用於保存讀入數據的一個緩衝區 4 DWORD nNumberOfBytesToRead, //要讀入的字節數 5 LPDWORD lpNumberOfBytesRead,//指向實際讀取字節數的指針 6 LPOVERLAPPED lpOverlapped 7 //如文件打開時指定了FILE_FLAG_OVERLAPPED,那麼必須,用這個參 數引用一個特殊的結構。 8 //該結構定義了一次異步讀取操做。不然,應將這個參數設爲NULL 9 );
該函數用於讀取文件,這裏指的是讀取U盤扇區。其中lpOverlapped設爲NULL便可。須要特別注意的是,因爲磁盤是以扇區爲單位進行讀寫的,因此這裏讀取的字節數必須是512的倍數!其餘參數註釋寫的很明白,這裏就再也不解釋了。配合SetFilePointer函數能夠讀取指定起始位置上指定字節的數據。
首先先來看一下用c++是如何讀取引導扇區的數據的。
1 // 筆者的U盤盤符爲G 2 WCHAR szDiscFile[] = _T("\\\\.\\G:"); 3 // 打開設備句柄 4 HANDLE hDisc = CreateFile(szDiscFile, GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); 5 if (hDisc == INVALID_HANDLE_VALUE) 6 { 7 // 打開設備失敗 8 return 0; 9 } 10 // 從第一個扇區起始位置開始讀取 11 SetFilePointer(hDisc, 0, 0, FILE_BEGIN); 12 // 須要讀取字節數 13 DWORD dwNumber2Read = 512; 14 // 實際讀取的字節數 15 DWORD dwRealNumber; 16 // 分配緩衝區 17 char* buffer = new char[512]; 18 bool bRet = ReadFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL); 19 20 // 掃尾工做,釋放緩衝區,關閉句柄 21 delete[] buffer; 22 CloseHandle(hDisc);
若是咱們在21行處下各斷點的話能夠看到buffer在內存中的數據,這裏爲了方便,筆者直接把這512個字節的數據保存到了文件中,用二進制文件讀取軟件打開來看了下,部分數據以下圖所示:
好了,接下來重點來了。首先,最開始的3各字節的數據分別是跳轉指令與空指令,由於在彙編當中0xEB是跳轉指令,0x58是跳轉的地址,而0x90則是空指令。至於爲何要在這裏放上一句跳轉指令呢,這個還得從啓動區開始講起,爲了節約篇幅,我就簡單介紹一下:通常第一個扇區叫作啓動區,cpu把扇區當中的數據看成指令來執行,當讀取到EB 58 這個指令時,遍跳轉到0x58這個地址並繼續讀取指令來執行,而0x58地址以後的內容一般都是載入操做系統的指令。若是但願知道詳細內容的讀者不妨去看一下《30天自制操做系統》這本書,第一天結尾部分有很詳細的說明。總之這邊的話FAT32規定這個3各字節的內容必須是EB 58 90,只要記住就好了(笑)。(如1L所說,EB 58 90 對應彙編代碼即爲JUMP 0x58; NOP;)。
而從0x03~0x0A這8個字節的數據表示OEM,這裏即爲「MSDOS5.0」。
咱們把從0x000B開始的79個字節的數據叫作BPB(BIOS Paramter Block),關於BPB的詳細說明請參見下表[5]:
偏移量 | 字節數 | 含義 | 值 |
0x00B | 2 | 每扇區字數 | 0x0200 |
0x00D | 1 | 每簇扇區數 | 0x08 |
0x00E | 2 | 保留扇區數 | 0x03F8 |
0x010 | 1 | FAT個數 | 0x02 |
0x011 | 2 | 根目錄項數,FAT32以突破該限制,無效 | 0x0000 |
0x013 | 2 | 扇區總數,小於32M使用 | 0x0000 |
0x015 | 1 | 存儲介質描述負 | 0x0F8 |
0x016 | 2 | 每FAT表佔用扇區數 ,小於32M使用 | 0x0000 |
0x018 | 2 | 邏輯每磁道扇區數 | 0x003F |
0x01A | 2 | 邏輯磁頭數 | 0x00FF |
0x01C | 4 | 系統隱含扇區數 | 0x00000080 |
0x020 | 4 | 扇區總數,大於32M使用 | 0x00784F80 |
0x024 | 4 | 每FAT表扇區數,大於32M使用 | 0x00001E04 |
0x028 | 2 | 標記 | 0x0000 |
0x02A | 2 | 版本 (一般爲零) | 0x0000 |
0x02C | 4 | 根目錄起始簇 | 0x00000002 |
0x030 | 2 | Boot佔用扇區數 | 0x0001 |
0x032 | 2 | 備份引導扇區位置 | 0x0006 |
0x034 | 14 | 保留 | 14個字節的0x00 |
0x042 | 1 | 擴展引導標記 | 0x29 |
0x043 | 4 | 序列號 | 0x6A9C4125 |
0x047 | 10 | 卷標 | 轉成字符即「NO NAME」 |
0x052 | 8 | 文件系統 | 轉成字符即「FAT32」 |
我來解釋一下其中的幾個參數。首先是保留扇區數,它能夠理解爲是FAT表的起始位置。咱們能夠先來看一下下面這張圖,有助於更好的理解保留扇區數的意思。
能夠看到引導扇區後面緊跟這若干保留扇區,至於保留扇區的做用會在下一篇中分析,這裏先跳過。而保留扇區的後面緊跟着的是FAT1和FAT2。因此FAT1表的起始地址是(引導扇區+保留扇區)*扇區字節數?不對。這裏有個須要注意的地方是,保留扇區數這個參數其實已經包含了引導扇區了,因此在計算FAT1表位置的時候直接經過保留扇區數這個參數來計算偏移就好了。這一點須要特別注意。(這裏筆者看了幾篇文獻的說法不一,有說須要加上保留扇區的,有說不用加的,多是版本不同的關係。可是筆者親自實踐以後發現是不須要加上的。)
至於FAT表留到下一篇再講,這裏說明一下爲何FAT會有兩張。文件分配表區共保存了兩個相同的文件分配表,由於文件所佔用的存儲空間(簇鏈)及空閒空間的管理都是經過FAT實現的,FAT如此重要,保存兩個以便第一個損壞時,還有第二個可用[4]。這樣FAT表個數這個參數也解釋了。
爲了用窗體程序直觀的顯示各個參數,接下來把上面的程序改寫爲c#。衆所周知,c#調用系統函數大多都須要靠間接調用c的動態庫來實現,這裏的CreateFile和ReadFile也不例外。下面咱們先編寫一個FileReader類來提供這些系統api調用。部分代碼以下,完整的FileReader點我下載
class FileReader { [System.Runtime.InteropServices.DllImport("kernel32", SetLastError = true, ThrowOnUnmappableChar = true, CharSet = System.Runtime.InteropServices.CharSet.Unicode)] static extern unsafe System.IntPtr CreateFile ( string FileName, // file name uint DesiredAccess, // access mode uint ShareMode, // share mode uint SecurityAttributes, // Security Attributes uint CreationDisposition, // how to create uint FlagsAndAttributes, // file attributes int hTemplateFile // handle to template file );
public bool Open(string FileName) { // open the existing file for reading handle = CreateFile ( FileName, GENERIC_READ, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0 ); if (handle != System.IntPtr.Zero) { return true; } else { return false; } }
}
因爲本文的重點不是學習c#窗體編程,因此略過這部分,直接經過調用FileReader提供的系統API並編寫窗體程序,效果以下圖所示(本程序參考[3]),須要完整工程的讀者能夠點我下載,請我VS2012及以上打開。
好了,今天這篇文章就先寫到這邊。因爲筆者才疏學淺,對於FAT32也是剛開始學習,若是有錯誤的地方歡迎批評指正。同時這也是我第一次發隨筆,若是排版等方面有不妥的地方也歡迎提出。接下來會繼續學習接下去的FAT表區和數據區並繼續更新接下來的學習心得。
參考文獻:
一、http://baike.baidu.com/view/45233.htm?fr=aladdin
二、FAT32文件格式 http://blog.csdn.net/shrekmu/article/details/5950414
三、讀寫U盤(FAT32)引導扇區 http://blog.csdn.net/zhanglei8893/article/details/5912903
四、FAT32文件系統格式詳解 http://wenku.baidu.com/link?url=zrGv8nld-bc-7KT_TKbo2vWplaiIHhmJ9_ydRZBZdZ4zy8odQFwS6komz2gz1AHX36T_EN1CKZ_16d19upW9pDauno6zEmpw10wlTSTwcoi
五、基於U盤FAT32文件系統的分析 http://wenku.baidu.com/link?url=cIKgrwV66y4CoyuOEB1-OhjRY9tnXtIAoZuYEwDCjxbyRomSIiJgBAXGxq6LudfwuopUpYhiVd8TjxrBFoVyPs0NX3OqbnoWjyn4ZAx60Wi