FAT32文件系统学习(1) —— BPB的理解
FAT 32 文件系统学习
1、本文的目标
本文将通过实际读取一个FAT32格式的U盘来简单了解和学习FAT32文件系统的格式。虽然目前windwos操作系统的主流文件系统格式是NTFS,但是FAT32由于其兼容性原因,还是有一定的学习价值。为了能做出一个窗体程序提供直观的感觉,本文的代码采用c#编写,对应的c++代码也会附上。
2、本文目录
2、什么是FAT32
FAT32是Windwos系统硬盘格式分区的一种。这种格式采用32位的文件分配表,使其对磁盘的管理能力大大增强,突破了FAT16对配一个分区的容量只有2GB的限制。虽然目前已被更优异的NTFS分区格式所取代[1]。其实说白了就是FAT表的每一项长度都是32位,所以叫做FAT32。至于每一项存放的内容是什么,下面的内容会慢慢进行分析。
FAT32 文件系统将逻辑盘的空间划分为三部分,依次是引导区 (BOOT区)、文件分配表区(FAT区)、数据区(DATA区)[2]。引导区和文件分配表区又合称为系统区。本文将简单学习引导区的内容,文件分配表区和数据区将在下一篇文章中学习。
3、引导区
引导区从第一扇区开始,保存了每个扇区的字节数,一个簇的扇区数,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表区和数据区并继续更新接下来的学习心得。
参考文献:
1、http://baike.baidu.com/view/45233.htm?fr=aladdin
2、FAT32文件格式 http://blog.csdn.net/shrekmu/article/details/5950414
3、读写U盘(FAT32)引导扇区 http://blog.csdn.net/zhanglei8893/article/details/5912903
4、FAT32文件系统格式详解 http://wenku.baidu.com/link?url=zrGv8nld-bc-7KT_TKbo2vWplaiIHhmJ9_ydRZBZdZ4zy8odQFwS6komz2gz1AHX36T_EN1CKZ_16d19upW9pDauno6zEmpw10wlTSTwcoi
5、基于U盘FAT32文件系统的分析 http://wenku.baidu.com/link?url=cIKgrwV66y4CoyuOEB1-OhjRY9tnXtIAoZuYEwDCjxbyRomSIiJgBAXGxq6LudfwuopUpYhiVd8TjxrBFoVyPs0NX3OqbnoWjyn4ZAx60Wi
posted on 2014-08-06 23:25 Yosef Gao 阅读(17536) 评论(10) 编辑 收藏 举报