操作系统: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 |
介质描述
0xF8 |
单面、每面80磁道、每磁道9扇区 |
0xF9 |
双面、每面80磁道、每磁道9扇区 |
0xFA |
单面、每面80磁道、每磁道8扇区 |
0xFB |
双面、每面80磁道、每磁道8扇区 |
0xFC |
单面、每面40磁道、每磁道9扇区 |
0xFD |
双面、每面40磁道、每磁道9扇区 |
0xFE |
单面、每面40磁道、每磁道8扇区 |
0xFF |
双面、每面40磁道、每磁道8扇区 |
同样的介质描述必须在重复复制到每份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) |
DPT分区表:
表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) 该分区中的扇区总数 |
[page]
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文件名(附加空格)
第一个字节可以是下面的特殊数值:
0x00 |
这个条目有用并且后面没有被占用条目 |
0x05 |
最初字符确实是0xE5 |
0x2E |
'点'条目;'.'或者'..' |
0xE5 |
这个条目曾经被删除不再有用。取消删除文档工具作为取消删除的一步必须使用一个正常的字符取代它。 |
|
0x08 |
3 |
DOS文件扩展名(空格补齐) |
0x0b |
1 |
文档属性
第一个字节可以是下面一些特殊值:
位 |
掩码 |
描述 |
0 |
0x01 |
只读 |
1 |
0x02 |
隐藏 |
2 |
0x04 |
系统 |
3 |
0x08 |
卷标 |
4 |
0x10 |
子目录 |
5 |
0x20 |
档案 |
6 |
0x40 |
设备(内部使用,磁盘上看不到) |
7 |
0x80 |
没有使用 |
属性值0x0F用来表示长文件名条目。
|
0x0c |
1 |
保留,NT使用(参见后面) |
0x0d |
1 |
创建时间,最小时间分辨率:10ms单位,数值从0到199。 |
0x0e |
2 |
创建时间。小时、分钟和秒根据后面的图示描述进行编码:
位 |
描述 |
15-11 |
小时(0-23) |
10-5 |
分钟(0-59) |
4-0 |
秒/2(0-29) |
注意秒只保存了2秒的分辨率。更细分辨率的文档创建时间在偏移0x0d处。
|
0x10 |
2 |
创建日期。年、月和日根据后面的图示编码:
位 |
描述 |
15-9 |
年(0 = 1980, 127 = 2107) |
8-5 |
月(1 = 1月,12 = 12月) |
4-0 |
日(1 - 31) |
|
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 |
文档大小 |
(4)、文件和目录数据区
目录项接着的第一个扇区就是真正存放文件数据或是目录的位置了。
[page]
二:FAT16
维基百科:http://en.wikipedia.org/wiki/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)”中,备用。
注:根目录的文件名不能用小写,否则出错,暂时不知原因所在。