首页 > 代码库 > FAT32文件系统学习(1) —— BPB的理解

FAT32文件系统学习(1) —— BPB的理解

FAT 32 文件系统学习

 

1、本文的目标

       本文将通过实际读取一个FAT32格式的U盘来简单了解和学习FAT32文件系统的格式。虽然目前windwos操作系统的主流文件系统格式是NTFS,但是FAT32由于其兼容性原因,还是有一定的学习价值。为了能做出一个窗体程序提供直观的感觉,本文的代码采用c#编写,对应的c++代码也会附上。

2、本文目录

1、本文的目标

2、什么是FAT32

3、引导区

2、什么是FAT32

      FAT32是Windwos系统硬盘格式分区的一种。这种格式采用32位的文件分配表,使其对磁盘的管理能力大大增强,突破了FAT16对配一个分区的容量只有2GB的限制。虽然目前已被更优异的NTFS分区格式所取代[1]。其实说白了就是FAT表的每一项长度都是32位,所以叫做FAT32。至于每一项存放的内容是什么,下面的内容会慢慢进行分析。

  • 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 );

       这个函数在msnd上可以查到,这里需要说明一下的是,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 lpOverlapped7     //如文件打开时指定了FILE_FLAG_OVERLAPPED,那么必须,用这个参  数引用一个特殊的结构。8     //该结构定义了一次异步读取操作。否则,应将这个参数设为NULL9 );

       该函数用于读取文件,这里指的是读取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个字节的数据保存到了文件中,用二进制文件读取软件打开来看了下,部分数据如下图所示:

  • BPB参数

       好了,接下来重点来了。首先,最开始的3各字节的数据分别是跳转指令空指令,因为在汇编当中0xEB是跳转指令,0x58是跳转的地址,而0x90则是空指令。至于为什么要在这里放上一句跳转指令呢,这个还得从启动区开始讲起,为了节约篇幅,我就简单介绍一下:一般第一个扇区叫做启动区,cpu把扇区当中的数据当作指令来执行,当读取到EB 58 这个指令时,遍跳转到0x58这个地址并继续读取指令来执行,而0x58地址之后的内容通常都是载入操作系统的指令。如果希望知道详细内容的读者不妨去看一下《30天自制操作系统》这本书,第一天结尾部分有很详细的说明。总之这边的话FAT32规定这个3各字节的内容必须是EB 58 90,只要记住就行了(笑)。

       而从0x03~0x0A这9个字节的数据表示OEM,这里即为“MSDOS5.0”。

       我们把从0x000B开始的79个字节的数据叫做BPB(BIOS Paramter Block),关于BPB的详细说明请参见下表[5]

BPB参数信息
偏移量字节数含义
0x00B2每扇区字数0x0200
0x00D1每簇扇区数0x08
0x00E2保留扇区数0x03F8
0x0101 FAT个数 0x02
0x0112根目录项数,FAT32以突破该限制,无效 0x0000 
0x0132扇区总数,小于32M使用 0x0000 
0x0151存储介质描述负 0x0F8 
0x0162每FAT表占用扇区数 ,小于32M使用0x0000 
0x0182逻辑每磁道扇区数 0x003F 
0x01A2逻辑磁头数  0x00FF
0x01C4系统隐含扇区数  0x00000080
0x0204扇区总数,大于32M使用  0x00784F80
0x0244每FAT表扇区数,大于32M使用 0x00001E04 
0x0282标记 0x0000
0x02A2版本 (通常为零)0x0000
0x02C4根目录起始簇 0x00000002 
0x0302Boot占用扇区数 0x0001 
0x0322备份引导扇区位置 0x0006
0x03414保留14个字节的0x00 
0x0421扩展引导标记 0x29 
0x0434序列号 0x6A9C4125 
0x04710卷标转成字符即“NO NAME”
0x0528文件系统 转成字符即“FAT32” 

 

        为了用窗体程序直观的显示各个参数,接下来把上面的程序改写为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、F?A?T?3?2?文?件?系?统?格?式?详?解 http://wenku.baidu.com/link?url=zrGv8nld-bc-7KT_TKbo2vWplaiIHhmJ9_ydRZBZdZ4zy8odQFwS6komz2gz1AHX36T_EN1CKZ_16d19upW9pDauno6zEmpw10wlTSTwcoi

5、基?于?U?盘?F?A?T?3?2?文?件?系?统?的?分?析 http://wenku.baidu.com/link?url=cIKgrwV66y4CoyuOEB1-OhjRY9tnXtIAoZuYEwDCjxbyRomSIiJgBAXGxq6LudfwuopUpYhiVd8TjxrBFoVyPs0NX3OqbnoWjyn4ZAx60Wi