zoukankan      html  css  js  c++  java
  • FAT32文件系统学习(1) —— BPB的理解

    FAT 32 文件系统学习

    1、本文的目标

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

    2、本文目录

    1、本文的目标

    2、什么是FAT32

    2.1 FAT32的构成

    3、引导区

    3.1 读取引导扇区

    3.2 BPB参数

    3.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 );

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

    • BPB参数

           好了,接下来重点来了。首先,最开始的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]

    BPB参数信息
    偏移量 字节数 含义
    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、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

  • 相关阅读:
    使用PHP类库PHPqrCode生成二维码
    40+个对初学者非常有用的PHP技巧
    (高级篇)jQuery学习之jQuery Ajax用法详解
    lerna管理前端模块实践
    Node.js:深入浅出 http 与 stream
    从koa-session源码解读session本质
    Elastichsearch实践
    Linux代理搭建TinyProxy
    linux常用命令
    node前后端同构的踩坑经历
  • 原文地址:https://www.cnblogs.com/fantacity/p/3895771.html
Copyright © 2011-2022 走看看