今天繼續學習FAT32文件系統的數據區部分(Data區)。其實這一篇應該是最有意思的,咱們能夠經過在U盤內放入一些文件,而後在程序中讀取出來;反過來也能夠用程序在U盤內寫入一下數據,而後在windows下能夠看到寫入的文件。這些筆者都會在這篇文章中演示(後來發現並無成功,不過筆者也找到相關的緣由,詳見後來的更新部分吧:) )。同時,在寫這篇文章的時候筆者也發現了許多意想不到的規律。html
一、讀取根目錄算法
二、短文件名目錄項windows
三、長文件名目錄項數據結構
四、U盤寫入文件夾ide
五、參考文獻函數
兩張FAT以後的全部扇區都是數據區部分。咱們再經過圖1來回顧一下整個FAT32文件系統的分佈規則。學習
一般狀況下根目錄都是位於數據區頭部的,前面也提到過,有文獻上說是由於一旦U盤格式化完畢以後,根目錄就建立好了。本着探究的精神,筆者嘗試格式化了一下U盤,發現其實並無建立根目錄。不過一旦有文件操做,立刻就會建立根目錄,由於這時整個數據區都是空的,因此天然是寫入數據區的頭部。到頭來其實道理是同樣的,也就是說根目錄通常狀況下都是在數據區的頭部(第2簇)。ui
通過前兩篇關於BPB和FAT部分學習以後,咱們就能夠計算出數據區頭部的偏移:this
數據區偏移 = (保留扇區數 + FAT表扇區數 * FAT表個數(一般爲2) + (起始簇號-2) * 每簇扇區數) * 每扇區字節數
筆者首先格式化了U盤,經過偏移讀取出了數據區的頭部,發現都是0x00。
這裏又要插一些題外話了,筆者試着改了一下U盤的卷標,把它更名爲「FAT」。而後還記得BPB當中有一個參數叫作卷標嗎?筆者看了下發現卷標這個參數仍是「NO NAME」並無改變。這時筆者把數據區的頭部讀取了出來,如圖2所示:
原來被寫在了這裏。最後經筆者的測試,卷標最長長度是11個字節,偏移從0x00~0x0A,而偏移0x0B處的值0x08值的意思就是卷標(關於此處值的意思相面還會詳細描述)。由於這個U盤其實尚未寫入過任何數據,因此卷標纔會顯示在數據區的開頭,可是若是換種狀況呢,文件系統又是如何找到卷標的呢?筆者查閱了相關資料後發現,卷標通常都是寫在根目錄的下的,若是發現根目錄項的其中一項其0x0B偏移處的值爲0x08那麼讀取該項的前11個字節即爲卷標。
好,回到正題。先來說一下理論的東西:目錄區是由一個個目錄項構成,相似於FAT表。其中每個目錄項佔用32個字節,能夠是表明長文件名目錄項、文件目錄項、子目錄項等。對於短文件名格式的目錄項,其參數的含義如表1所示(不會畫這種表,從別處引用了一個)[1]:
用一個實際的例子來解釋一下這些參數的意思,首先建立一個短文件名文件,如「file1.txt」,讀取根目錄,如圖3所示:
先無論其餘數據,咱們來看一下紅色矩形框部分的數據,它就是一個短目錄項。咱們來一個個對比表1的參數進行說明:
字節偏移 | 參數含義 | 值 |
0x00~0x07 | 文件名 | 對應字符串「FILE1」 |
0x08~0x0A | 後綴名 | 對應字符串「TXT」 |
0x0B | 屬性字節 | 0x20 = 00100000(2進制) 表示歸檔 |
0x0C | 系統保留 | 無 |
0x0D | 建立時間的10毫秒位 | 88,即0x88 * 10ms = 1360ms(10進制) |
0x0E~0x0F | 文件建立時間 | 0x785C = (0111100001011100)(2進制) 即爲 15:02:57(註釋1) |
0x10~0x11 | 文件建立日期 | 0x4508 = (0100010100001000)(2進制) 即爲 2014/8/8(註釋2) |
0x12~0x13 | 文件最後訪問日期 | 0x4508 = (0100010100001000)(2 進制) 即爲 2014/8/8 算法參考建立日期 |
0x14~0x15 | 文件起始簇號高16位 | 0x0000,能夠用來計算出文件實際內容的偏移值, 這個放到後面單獨計算。 |
0x16~0x17 | 文件最近修改時間 | 0x7869 = (0111100001101001)(2進制) 即爲 15:03:18 算法參考建立時間 |
0x18~0x19 | 文件最近修改日期 | 0x4508 = (0100010100001000)(2進制) 即爲 2014/8/8 算法參考建立日期 |
0x1A~0x1B | 文件起始簇號低16位 | 0x0005 |
0x1C~0x0F | 文件長度 | 0x0000000C = 12 |
表2 file1.txt 參數解釋
1)這裏高5位表明小時,因爲2^5 = 32,足夠表示24小時,這邊01111(2進制) = 15(10進制);
2)次6位表明分鐘,同理2^6 = 64,足夠表示60分鐘,這邊000010(2進制) = 2;
3)低5位表示秒的1/2, 計算結果須要加上毫秒位上的進位,這邊11100(2進制) = 28(10進制),因此秒數 = 28*2 = 56,再加上毫秒上的進位1因此結果爲57。
1)這裏高7位表明從1980年開始的年數,筆者計算了下能夠到2108年,總之還有90多年可使用,這邊0100010(2進制) = 34,因此年份 = 1980+34 = 2014;
2)次4位表明月份,2^4=16,能夠表示12個月份,這邊 1000(2進制) = 8(10進制);
3)低5位表明日期,2^5 = 32,能夠表示28~31天,這邊 01000(2進制) = 8(10進制)。
這樣除了文件起始簇號字段,其餘字段的意思和計算方法都弄清楚了。下面來看一下文件起始簇號,首先根據高低各16位,計算出完整的文件起始簇號 = 0x00000005 ,文件起始地址偏移的計算:
文件起始地址 = (保留扇區數 + FAT表扇區數 * FAT表個數(2) + (文件起始簇號-2)*每簇扇區數)*每扇區字節數
本例中計算結果爲0x4010,而後到這個地址讀取內容並切入到磁盤文件中(詳細操做參考第一篇文章),如圖4所示,windows下打開內容如圖5所示:
這個時候再去看一下FAT表的5號簇,計算方式在上一篇當中,結果如圖6所示:
紅色矩形框的位置就是5號簇的位置,能夠看到值0x0FFFFFFF,意思就是文件在這一簇結束了。 (具體不一樣數值的含義詳見上一篇)。若是這裏文件大小超過1簇,那麼這個值應該是下一簇的簇號,繼續讀取下一簇的內容便可。雖然咱們知道了文件佔用的空間是1簇,可是怎麼知道文件具體的大小呢?再回過頭來看上面的短文件目錄項,最後一個屬性是文件長度,上面已經計算獲得爲12,「Hello World!」的長度正好是12 :)。
至此短文件目錄項應該已經分析的差很少了。
下面是長文件名目錄項,筆者思考了很久該怎麼把它講清楚,畢竟理解是一回事,講清楚就是另外一回事了。
在講長文件目錄項以前先來講一下FAT32的一個很重要的特性,支持長文件名。長文件名也是記錄在目錄項當中的,區別與短目錄項的是,前者可能會佔據好幾個目錄項。爲了兼容低版本的OS或程序能正確讀取長文件名文件,系統自動爲全部長文件名文件建立了一個對應的短文件名,使對應數據既能夠用長文件名尋址,也能夠用短文件名尋址。不支持長文件名的OS或程序會忽略它認爲不合法的長文件名字短,而支持長文件名的OS或程序則會以長文件名爲顯式項來記錄和編輯,並隱藏起短文件名[2]。
當建立一個長文件名文件時,系統會自動加上對應的短文件名,其原則以下:
(1)、取長文件名的前6個字符加上"~1"造成短文件名,擴展名不變。
(2)、若是已存在這個文件名,則符號"~"後的數字遞增,直到5。
那麼系統是如何判斷當前目錄項是短文件名目錄項呢仍是長文件名目錄項,這裏關鍵是看目錄項的第12個字節的值,若是爲0x0F時則系統認爲是長目錄項。而若是是舊版本的系統看到第12個字節是0x0F則認爲是異常而忽略掉。這裏能夠回過頭去看一下短文件名目錄項,第12個字節是文件屬性字節,0x0F即爲全1是無效的,因此係統認爲是異常。系統將長文件名以13個字符爲單位進行切割,每一組佔據一個目錄項。因此可能一個文件須要多個目錄項,這時長文件名的各個目錄項按倒序排列在目錄表中,以防與其餘文件名混淆。
這樣講可能仍是很抽象,先來看一下長文件名目錄項的參數,如表3所示[1]:
而後仍是以一個實際的例子來講明,在根目錄區讀入一個長文件名目錄項,如圖7所示:
圖中選定部分即爲多個長文件名目錄項。咱們來慢慢分析。系統在讀入一個目錄項的時候首先查看它的第12個字節,發現是0x0F,因此認爲這是一個長文件名目錄項。咱們來看長文件名目錄項的參數,如表4所示:
偏移 | 字段含義 | 值 |
0x00 | 屬性字節位 | 0x42 = (01000010)(2進制)(註釋1) |
0x01~0x0A | 10個字節的Unicode碼 | 即字符串」ename」 (註釋2) |
0x0B | 長文件名目錄項 | 0x0F前面已經講過 |
0x0C | 系統保留 | 無 |
0x0D | 校驗值 | 這個等整個文件名讀取完再講 |
0x0E~0x19 | 12字節Unicode | 即字符串"Test" |
0x1A~0x1B | 文件起始簇號 | 常置0 |
0x1C~0x1F | 4字節Unicode | 0xFFFFFFFF 若是文件名已經結束的話則所有爲0xFF |
第7位爲1,說明是文件最後一個目錄項目,
低5位爲順序 0010(2進制) = 2(10進制),說明這是第2個長目錄項,且是最後一個目錄項。即爲這個長文件名佔用了兩個目錄項。
註釋2:Unicode 百度百科Unicode 點我詳細解釋
這邊有3個Unicode區,加起來正好是26個字節即13個Unicode碼,因此這就是爲何上面講的以13個字符爲單位切割。由於這是第2個目錄項,因此後面應該還有第1個目錄項,繼續分析下一個目錄項其他參數同上,看一下3個Unicode分別是「LongL」 「engthF」 「il」而0x00的屬性字節是01,說明這是第一個。至此這個長文件名讀取完畢了。按照倒序(這裏也解釋了前面說的倒序的意思)的順序拼接起來的話就是」LongLengthFilename」——這就是這個文件的文件名。
下面再來看一下下一個目錄項,長文件名目錄項後面還會跟一個短文件名目錄項,這個目錄項記錄了除文件名之外的這個文件的信息,而文件名部分則用上面提到的短文件名目錄項替換。因此讀取方法和短文件名目錄項是同樣的,這裏只看一下文件屬性字節,偏移爲0x0B,值爲0x10=(00010000) 根據短文件名目錄項參數的意思,這個文件是一個子目錄。其他參數讀者能夠根據上面提到的計算方法得出。
最後再來補上剛纔的校驗碼計算方法:
1 int i, j = 0, chksum=0; 2 for (i = 11; i > 0; i--) 3 chksum = ((chksum & 1) ? 0x80 : 0) + (chksum >> 1) + shortname[j++];
其中shortname即長文件名目錄項對應的短文件名,因此這個校驗碼須要等到讀完短文件名目錄項以後才能夠計算。這一段程序是筆者從網上摘來的,尚未時間驗證一下。
這樣關於數據區的部分差很少就講完了。
最後在作一點有趣的事情,嘗試向磁盤的扇區中寫入一些數據,而後看是否會生成這個文件。爲了方便起見,這裏直接在根目錄建立一個文件夾好了,
文件夾的名字叫作root,
建立時間日期2014/8/8 18:18:18
訪問日期 2014/8/8
最近修改時間日期 2014/8/8 18:18:18
起始簇低16位 04 00
起始簇高16位 00 00
文件長度 0
同時修改2個FAT表第4項爲0x0FFFFFFF
這樣應該就能夠了,好了,開始編碼:
1 // 短文件名目錄項數據結構 2 typedef struct ShortDirItem 3 { 4 char strFilename[8]; 5 char strExtension[3]; 6 char attribute; 7 char reserved; 8 char millisecond; 9 unsigned short createTime; 10 unsigned short createDate; 11 unsigned short accessDate; 12 unsigned short highWordCluster; 13 unsigned short updateTime; 14 unsigned short updateDate; 15 unsigned short lowWordCluster; 16 unsigned int filesize; 17 }ShortDirItem;
1 // 定位到FAT1表 2 SetFilePointer(hDisc, 1016*512, 0, FILE_BEGIN); 3 DWORD dwNumber2Read = 512; 4 // 實際讀取的字節數 5 DWORD dwRealNumber; 6 // 分配緩衝區 7 char* buffer = new char[512]; 8 // 讀取一個扇區的數據 9 BOOL bRet = ReadFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL); 10 // 把4第項改成 0x0FFFFFFF 11 buffer[12] = buffer[13] = buffer[14] = 0xFF; 12 buffer[15] = 0x0F; 13 // 寫回FAT1 14 bRet = WriteFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL); 15 // 定位到FAT2表 16 SetFilePointer(hDisc, (1016+7684)*512, 0, FILE_BEGIN); 17 // 把4第項改成 0x0FFFFFFF 18 bRet = WriteFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL); 19 // 定位到根目錄 20 SetFilePointer(hDisc, (1016+7684*2)*512, 0, FILE_BEGIN); 21 // 讀取根目錄扇區 22 bRet = ReadFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL); 23 // 準備數據 24 ShortDirItem* item = new ShortDirItem(); 25 strcpy(item->strFilename, "root"); 26 strcpy(item->strExtension, " "); 27 item->attribute = 0x10; 28 item->millisecond = 0x00; 29 item->createTime = 0x9249; 30 item->createDate = 0x4508; 31 item->accessDate = 0x4508; 32 item->highWordCluster = 0x0000; 33 item->updateTime = 0x9249; 34 item->updateDate = 0x4508; 35 item->lowWordCluster = 0x0004; 36 item->filesize = 0x00; 37 38 // 修改根目錄數據 39 char* pData = (char*)item; 40 for (int i = 32; i < 64; ++i) 41 { 42 buffer[i] = *(pData++); 43 } 44 // 寫回根目錄 45 bRet = WriteFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL); 46 // 掃尾工做,釋放緩衝區,關閉句柄 47 delete[] buffer; 48 delete item; 49 CloseHandle(hDisc);
可是筆者發如今win7 (準確的說是win七、vista、win8,XP下獲取管理員權限便可執行)下調用WriteFile函數沒法將數據寫入U盤,多是因爲系統保護措施的關係,因爲時間關係,筆者也沒去深究。
-----------------------------------------------------------2014/8/9-----------------------------------------------------------------
後來筆者專門去查找了相關資料,總的來講緣由確實是由於系統保護措施的關係致使WriteFile函數操做的失敗,具體的解釋以下:
首先是msdn上的解釋 http://msdn.microsoft.com/en-us/library/windows/hardware/ff551353(v=vs.85).aspx 大概意思是說在win7和vista上加入了一些新的特性,爲了可以更好得保護系統,若是應用程序沒有獨佔的權限就直接對裝有文件系統的存儲設備進行寫入操做的話,這個操做是會被拒絕的。筆者上面的程序經過GetLastError()函數獲得的ErroeCode=5,意思也確實是拒絕訪問。那麼到底要如何寫入呢,msdn上給出瞭如下幾種狀況:
Write operations on a DASD volume handle will succeed if the file system is not mounted, or if:
The sectors being written to are the boot sectors.
The sectors being written to reside outside file system space.
The file system has been locked implicitly by requesting exclusive write access.
The file system has been locked explicitly by sending down a lock/dismount request.
The write request has been flagged by a kernel-mode driver that indicates that this check should be bypassed. The flag is called SL_FORCE_DIRECT_WRITE and it is in the IrpSp->flags field. This flag is checked by both the file system and storage drivers.
這裏比較方便的作法能夠採用第4種,即顯示地發送一個鎖定驅動的請求,而後再嘗試寫入。具體作法參考這個帖子22L吧,筆者打算去嘗試一下,成功的話再來更新結果。 http://bbs.csdn.net/topics/390731448?page=1
-------------------------------------------------------------------------------------------------------------------------------------
好了,看了下篇幅這篇文章也差很少能夠結束了。FAT32文件系統其實差很少也都學習完了,爲了鞏固學習內容,筆者打算接下去根據前面所學的知識,並去了解一下windows快速格式化FAT32的機制,嘗試本身格式化U盤,還能夠根據FAT32的原理嘗試刪除數據的恢復等,總之仍是有不少事情能夠作的。
最後的最後,若是文章當中有任何錯誤或者遺漏指出,歡迎指出,謝謝。
一、FAT32系統中長文件名的存儲 http://blog.csdn.net/yanpingsz/article/details/5597893
二、FAT32文件系統的存儲組織結構(一) http://blog.chinaunix.net/uid-26913704-id-3213948.html
三、Blocking Direct Write Operations to Volumes and Disks http://msdn.microsoft.com/en-us/library/windows/hardware/ff551353(v=vs.85).aspx
四、vc 直接寫物理磁盤,writefile 失敗 錯誤返回5 拒絕訪問 http://bbs.csdn.net/topics/390731448?page=1