操作系統:rt-thread
文件系統:Fat16
硬件平臺:stm32f103c8
描述:利用mcu的ram虛擬一個U盤,用于存儲即時小數據,通過usb以u盤的方式供上位機讀取。
一:硬盤篇
1、硬盤物理結構:
盤片(platter):硬盤由很多盤片組成,每個盤片的每個面都有一個讀寫磁頭(heads)。N個盤片,就有2N個面,對應2N個磁頭,從0、1、2……開始編號;
磁道:同一個盤片不同半徑的同心圓為磁道(注意,是指圓周線或圓環);
柱面(cylinders):不同盤片相同的磁道,構成柱面,由外至里編號0、1、2……
扇區(sector):每道磁道被劃分成幾十個扇區,通常,一個扇區容量為512B,并按照一定規則編號1、2、3……
簇:另外,由于扇區實在太多了,文件分配表沒辦法一一描述,所以,就把一定數量的扇區分為一個簇。所以文件系統是以一簇為最小單位的。
注意:磁頭和柱面都是從0開始編號的,扇區搞毛特殊從1開始。
總扇區數量=柱面數×磁頭數×每道扇區
2、磁盤引導:
下圖是一個4個分區的硬盤,開頭的主引導記錄就是上面介紹的MBR,其中4個分區表分別指出了4個分區的位置,上位機獲取MBR之后,就知道了硬盤的總體結構,包括硬盤的大小、每個分區的位置、每個分區的大小等等。
隱藏區(hidden sector):在分區之前的部分,而下面介紹的MBR就是隱藏區的第一個扇區。隱藏區不是必須的,它和系統啟動有關,如果僅僅是作為存儲,那么隱藏區可以沒有。比如,標題說的用RAM虛擬的U盤。
注意:在我理解中,嚴格地說,隱藏區應該是指每個分區之內,位于保留區之前的扇區,也就是說,每個分區都有一套隱藏區+保留區。只是在唯一存儲介質的第一個分區的隱藏區才是必須的,因為它需要用于系統的啟動。而其他分區的隱藏區則一般被省略。如果不被省略,比如SD的開頭就有一部分隱藏區,這時,引導代碼部分是空的(因為不需要它來做系統啟動),只有DPT分區表才有意義,用于劃分SD的分區情況。
MBR(master boot record)扇區:即主引導記錄,有時也叫主引導扇區,位于硬盤的0柱面0磁頭1扇區,也就是所謂是第0扇區,也是整個存儲介質的首個扇區。其中前446字節為引導程序,緊跟著的是64字節的硬盤分區表DPT,最后2個字節是“0x55 0xAA”,為磁盤有效結尾標志。
MBR 是不隨操作系統的不同而不同的,具有公共引導特性。(在雙系統中,一般先裝Windows再裝linux,原因是,linux會修改這段代碼,讓用戶可以選擇進去哪個系統,但Windows卻是沒有,如果后裝Windows,那么linux的引導就會被忽略??)
(1)0x000-0x1bd:mbr引導代碼(有些地方也叫MBR??),446字節,pc的bios執行完自舉之后,會將cpu控制權交給此間的446個字節的loader程序;
(2)0x1be-0x1fd:DPT分區表,64字節,每16字節描述一個分區,所以硬盤的主分區+擴展分區不能大于4個,另外擴展分區數不能大于1。(現實中,不止4個,其實是擴展分區里面分出來的邏輯分區,對于邏輯分區的問題上面的鏈接也有提到,可以參考。)
0x1fe-0xff:0x55 0xAA。
主引導記錄部分如下表:
偏移(字節) | 長度(字節) | 說明 | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x00 | 3 | 跳轉指令(跳過開頭一段區域) | ||||||||||||||||
0x03 | 8 | OEM名稱(空格補齊)。MS-DOS檢查這個區域以確定使用啟動記錄中的哪一部分數據 [3] 。常見值是IBM 3.3 (在“IBM”和“3.3”之間有兩個空格)和MSDOS5.0 . |
||||||||||||||||
0x0b | 2 | 每個扇區的字節數。基本輸入輸出系統參數塊從這里開始。 | ||||||||||||||||
0x0d | 1 | 每簇扇區數 | ||||||||||||||||
0x0e | 2 | 保留扇區數(包括啟動扇區) | ||||||||||||||||
0x10 | 1 | 文檔分配表數目 | ||||||||||||||||
0x11 | 2 | 最大根目錄條目個數 | ||||||||||||||||
0x13 | 2 | 總扇區數(如果是0,就使用偏移0x20處的4字節值) | ||||||||||||||||
0x15 | 1 | 介質描述
同樣的介質描述必須在重復復制到每份FAT的第一個字節。有些操作系統(MSX-DOS 1.0版)全部忽略啟動扇區參數,而僅僅使用FAT的第一個字節的介質描述確定文件系統參數。 |
||||||||||||||||
0x16 | 2 | 每個文檔分配表的扇區(FAT16) | ||||||||||||||||
0x18 | 2 | 每磁道的扇區 | ||||||||||||||||
0x1a | 2 | 磁頭數 | ||||||||||||||||
0x1c | 4 | 隱藏扇區 | ||||||||||||||||
0x20 | 4 | 總扇區數(如果超過65535,參見偏移0x13) | ||||||||||||||||
0x24 | 4 | 每個文檔分配表的扇區(FAT32)。擴展基本輸入輸出系統參數塊從這里開始。 | ||||||||||||||||
0x24 | 1 | 物理驅動器個數(FAT16) | ||||||||||||||||
0x25 | 1 | 當前磁頭(FAT16) | ||||||||||||||||
0x26 | 1 | 簽名(FAT16) | ||||||||||||||||
0x27 | 4 | ID(FAT16) | ||||||||||||||||
0x28 | 2 | Flags(FAT32) | ||||||||||||||||
0x2a | 2 | 版本號(FAT32) | ||||||||||||||||
0x2c | 4 | 根目錄啟始簇(FAT32) | ||||||||||||||||
0x2b | 11 | 卷標(非FAT32) | ||||||||||||||||
0x30 | 2 | FSInfo扇區(FAT32) | ||||||||||||||||
0x32 | 2 | 啟動扇區備份(FAT32) | ||||||||||||||||
0x34 | 12 | 保留未使用(FAT32) | ||||||||||||||||
0x36 | 8 | FAT文件系統類型(如FAT、FAT12、FAT16) | ||||||||||||||||
0x3e | 2 | 操作系統自引導代碼 | ||||||||||||||||
0x40 | 1 | BIOS設備代號(FAT32) | ||||||||||||||||
0x41 | 1 | 未使用(FAT32) | ||||||||||||||||
0x42 | 1 | 標記(FAT32) | ||||||||||||||||
0x43 | 4 | 卷序號(FAT32) | ||||||||||||||||
0x47 | 11 | 卷標(FAT32) | ||||||||||||||||
0x52 | 8 | FAT文件系統類型(FAT32) | ||||||||||||||||
0x1FE | 2 | 扇區結束符(0x55 0xAA) |
表1 圖2分區表第一字段 | |||
字節位移 | 字段長度 | 值 | 字段名和定義 |
0x01BE | BYTE | 0x80 | 引導指示符(Boot Indicator) 指明該分區是否是活動分區。 |
0x01BF | BYTE | 0x01 | 開始磁頭(Starting Head) |
0x01C0 | 6位 | 0x01 | 開始扇區(Starting Sector) 只用了0~5位。后面的兩位(第6位和第7位)被開始柱面字段所使用 |
0x01C1 | 10位 | 0x00 | 開始柱面(Starting Cylinder) 除了開始扇區字段的最后兩位外,還使用了1位來組成該柱面值。開始柱面是一個10位數,最大值為1023 |
0x01C2 | BYTE | 0x07 | 系統ID(System ID) 定義了分區的類型,詳細定義,請參閱圖4 |
0x01C3 | BYTE | 0xFE | 結束磁頭(Ending Head) |
0x01C4 | 6位 | 0xFF | 結束扇區(Ending Sector) 只使用了0~5位。最后兩位(第6、7位)被結束柱面字段所使用 |
0x01C5 | 10位 | 0x7B | 結束柱面(Ending Cylinder) 除了結束扇區字段最后的兩位外,還使用了1位,以組成該柱面值。結束柱面是一個10位的數,最大值為1023 |
0x01C6 | DWORD | 0x0000003F | 相對扇區數(Relative Sectors) 從該磁盤的開始到該分區的開始的位移量,以扇區來計算 |
0x01CA | DWORD | 0x00DAA83D | 總扇區數(Total Sectors) 該分區中的扇區總數 |
3、FAT分區原理:
下面終于開始說FAT。
對于一個存儲介質,隱藏區之后,才是文件系統的起始部分,以保留區開頭。
FAT文件系統的結構是按照這個方式排列的:保留區、FAT區、副FAT區、根目錄區、數據區。
(1)、保留區(Reserved Region)
保留區就是分區的開始扇區到FAT表之前的扇區,注意,保留區是位于分區之內,每個分區的開頭都會有一個保留區。而保留區的第一個扇區必須是BPB。如果隱藏區為0,那么BPB將位于第0扇區。
DBR(Dos Boot Record):即操作系統引導記錄,通常位于分區的第0個扇區,共512個字節(特殊情況也要占用其它保留扇區)。在這512個字節中,其實又是由跳轉指令,廠商標志和操作系統版本號,BPB(BIOS Parameter Block),擴展BPB,os引導程序,結束標志幾部分組成。 以用的最多的FAT32為例說明分區DBR各字節的含義。見圖:
對應解釋見下表:
表3 FAT32分區上DBR中各部分的位置劃分 | |||
字節位移 | 字段長度 | 字段名 | 對應圖8顏色 |
0x00 | 3個字節 | 跳轉指令 | |
0x03 | 8個字節 | 廠商標志和os版本號 | |
0x0B | 53個字節 | BPB | |
0x40 | 26個字節 | 擴展BPB | |
0x5A | 420個字節 | 引導程序代碼 | |
0x01FE | 2個字節 | 有效結束標志 |
其實,圖中,除了黑色部分,其他部分同MBR是一樣的,可以直接查看上面對MBR內容的分析表格。
需要注意的是:
1)跳轉指令:EB xx 90,也就說第0和第2為分別是EB和90,至于具體意義,沒有暫時沒有深究;
2)對應MBR,最大的區別是,DBR沒有分區表DPT!
(2)、FAT區
即文件分配表,有兩份,第二份是第一份的備份。文件分配表每項代表一個簇,由于fat16使用兩字節即16位來描述一個簇,所以最多只能管理65536個簇,又由于每簇最大只有32kB,所以使用fat16的時候,每個分區最大容量是2G。分區表的大小可以在DBR里設置。
(3)、根目錄區:
第二個FAT表(即備份FAT)后面緊接著的下一個扇區,就是根目錄區的開始。存放目錄項,每個目錄下為32個字節,記錄一個文件或目錄的信息(長文件名例外)。目錄項所占的扇區與可以容納的目錄項有關,一般為512項,將占“512(目錄項數)×32/512(扇區大小)”個扇區。但是目錄項×32一定是扇區大小512的整數倍。
32字節目錄項內容如下表:
字節偏移 | 長度 | 描述 | |||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x00 | 8 | DOS文件名(附加空格)
第一個字節可以是下面的特殊數值:
|
|||||||||||||||||||||||||||
0x08 | 3 | DOS文件擴展名(空格補齊) | |||||||||||||||||||||||||||
0x0b | 1 | 文檔屬性
第一個字節可以是下面一些特殊值:
屬性值0x0F用來表示長文件名條目。 |
|||||||||||||||||||||||||||
0x0c | 1 | 保留,NT使用(參見后面) | |||||||||||||||||||||||||||
0x0d | 1 | 創建時間,最小時間分辨率:10ms單位,數值從0到199。 | |||||||||||||||||||||||||||
0x0e | 2 | 創建時間。小時、分鐘和秒根據后面的圖示描述進行編碼:
注意秒只保存了2秒的分辨率。更細分辨率的文檔創建時間在偏移0x0d處。 |
|||||||||||||||||||||||||||
0x10 | 2 | 創建日期。年、月和日根據后面的圖示編碼:
|
|||||||||||||||||||||||||||
0x12 | 2 | 最近訪問時間;參見偏移0x0e處的描述。 | |||||||||||||||||||||||||||
0x14 | 2 | FAT12和FAT16中的EA-Index(OS/2和NT使用),FAT32中第一個簇的兩個高字節 | |||||||||||||||||||||||||||
0x16 | 2 | 最后更改時間;參見偏移0x0e處的描述。 | |||||||||||||||||||||||||||
0x18 | 2 | 最后更改日期; 參見偏移0x10處的描述。 | |||||||||||||||||||||||||||
0x1a | 2 | FAT12和FAT16中的第一個簇。FAT32中第一個簇的兩個低字節。 | |||||||||||||||||||||||||||
0x1c | 4 | 文檔大小 |
目錄項接著的第一個扇區就是真正存放文件數據或是目錄的位置了。
二:FAT16
其實和上面的介紹差不多。不過這是從文件系統邏輯概念來解釋,而前面更偏于物理位置。
保留區 | 文檔 分配表#1 |
文檔 分配表#2 |
根目錄 | 其他所有數據... 剩下磁盤空間 |
- 保留扇區,位于最開始的位置。第一個保留扇區是引導區DBR(分區啟動記錄)。它包括一個稱為基本輸入輸出參數塊的區域(包括一些基本的文件系統信息尤其是它的類型和其它指向其它扇區的指針),通常包括操作系統的啟動調用代碼。保留扇區的總數記錄在引導扇區中的一個參數中。引導扇區中的重要信息可以被DOS和OS/2中稱為驅動器參數塊的操作系統結構訪問。
- FAT區域。它包含有兩份文檔分配表,這是出于系統冗余考慮,盡管它很少使用,即使是磁盤修復工具也很少使用它。它是分區信息的映射表,指示簇是如何存儲的。
- 根目錄區域。它是在根目錄中存儲文檔和目錄信息的目錄表。在FAT32下它可以存在分區中的任何位置,但是在早期的版本中它永遠緊隨FAT區域之后。
- 數據區域。這是實際的文檔和目錄數據存儲的區域,它占據了分區的絕大部分。通過簡單地在FAT中添加文檔鏈接的個數可以任意增加文檔大小和子目錄個數(只要有空簇存在)。然而需要注意的是每個簇只能被一個文檔占有,這樣的話如果在32KB大小的簇中有一個1KB大小的文檔,那么31KB的空間就浪費掉了。
總結:拿SD卡來講吧,給xp格式化之后的SD卡物理結構如下:隱藏區+保留區+FAT+副FAT+根目錄區+數據區 1、隱藏區:包括MBR(主引導記錄)和DPT(分區表),不過這里的MBR是空的,因為不需要它的系統啟動。上位機首先讀取這個512字節,根據DPT,知道分區情況,根據這些接著直接跳到第一分區的位置讀取第一個分區; 2、保留區和FAT:這是上位機讀取分區的第一部分內容,從而知道分區的情況,根據BDR知道根目錄的位置;然后去讀根目錄。 3、根目錄區:讀取分區的根目錄,可以知道分區的文件結構,然后根據對應的FAT,讀到一個文件的內容。注意,在根目錄里記錄了文件的開始扇區,而這個邏輯開始扇區,是以2開頭的,估計0和1已經被保留所用。
前面說了一大堆關于FAT16和硬盤的東西,其實是為下面做準備的。因為我覺得用RAM虛擬U盤,一要熟悉fat,二要熟悉USB協議。
前面介紹fat,下面應該就說USB了,但我對USB的了解非常膚淺,沒辦法從協議的角度來記錄,只能簡單的針對這次開發記錄一下,以備后用吧。
1、要了解USB的設備、配置、接口、端點等知識點,和對應的各種描述符;
2、然后了解枚舉的過程,數據線D+,D-,差分數據傳輸,NRZI編碼,還有一些數據包;
3、一個很好用的總線監控軟件,可以用它來監控USB的通信過程,對開發很有用:Bus Hound;
4、USB設備分類:顯示器Monitors, 通訊設備Communication device, 音頻設備Audio, 人機輸入Human input, 海量存儲Mass storage。而這次我們用的應該就是Mass storage;
……
本程序是從一個MSD卡程序中改過來的,準確來說應該是奮斗版的U盤程序修改過來的。
關鍵點:
1、建立DBR數組(操作系統引導記錄):
扇區大小:512;
每簇扇區數:1;
保留扇區數:1;
根目錄項:16,因為16×32,剛好一個扇區;
小扇區數:8,因為用RAM虛擬的U盤,自然很小,其實我只用到5個扇區;
每FAT扇區數:1;
隱藏扇區數:0,因為不需要MBR,主引導記錄;
大扇區數:0,fat16小容量沒有用到;
2、FAT數組
FAT表以“F8 FF FF FF”開頭,為介質描述單元。接著每2字節代表一簇,不過簇的標號是從2開始的,也就是說FAT表的4、5字節代表第2簇(順序其實就是首簇),6、7字節代表第3簇,以此類推;
我現在fat表如下:
F8 FF FF FF FF FF 00 00 ……
綠色就是代表第2簇,它的值代表文件的占用的下一個簇的簇號,ffff表示這是文件的最后一個簇。因為我們是一個小文件,一個文件只用一個簇,所以這個就是FFFF。0000表示此簇沒有被分配。
例如,如果這里不是FFFF,而是0500(低位在前),則表示,文件的下一個簇的簇號是5,系統就會查詢fat的第5簇的位置,重復上面的動作,知道遇到FFFF,表示這里是文件的最后一簇。
3、根目錄數組
這里根據前一篇根目錄的格式,建立一個盤符根目錄(就是插入電腦的時候盤符的名字),接著是一個文件目錄(因為這里只需要一個文件),起始簇為2;
4、數據數組
用于存文件的數據。
RAM虛擬的U盤結構是這樣的:
DBR(1扇)-|- FAT(1扇)-| -副FAT(1扇)-|- 根目錄(1扇)-|- 數據(4扇,其實只用1扇的一點而已,小文件嘛)
pc上顯示的空間大小是2k,原因是8個扇區中,數據占了4扇,而扇區的大小為512字節,所以大小是2k。
只需修改讀寫函數。讀函數這樣修改,當讀的index為512×0是,發送DBR表;當為512×1時,發FAT,當為512×3時,發送根目錄;當為512×4時,發送數據。其他用00 00 00 00 ……來填充。
寫函數的修改:只允許寫修改數據和根目錄部分,其他時候直接返回失敗標志。
需要注意的是,為了節省RAM,DBR、FAT、都可以用const定義,但根目錄和數據數組最好用非const全局定義,原因是上位機在讀取文件的時候,會修改根目錄的“最近訪問時間”。
另外,如果在pc機上把文件刪掉,再重新建立的時候,根目錄可不是簡單的覆蓋,而且還有清空FAT的對應標志。所以,我這里實現,可以刪除文件,但再重新新建的時候,這個文件是沒有被成功記錄到虛擬的U盤的,估計,把FAT也改成可修改狀態就可以實現,但是,這次項目不需要用到這點,所以沒有去試。
這次實現存入janho程序庫“Mass_Storage(用ram虛擬u盤fat16)”中,備用。
注:根目錄的文件名不能用小寫,否則出錯,暫時不知原因所在。