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(这里用于读取磁盘扇区)。首先来看一下这两个函数的定义:

 HANDLE WINAPI CreateFile(
_In_ LPCTSTR lpFileName, // 要打开的文件的名或设备名。
_In_ DWORD dwDesiredAccess, // 指定类型的访问对象。
_In_ DWORD dwShareMode, // 文件共享模式
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 定义了文件的安全特性
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile // 返回句柄
);

这个函数在msdn上可以查到,这里需要说明一下的是,dwShareMode需要指定为FILE_SHARE_WRITE才能读取扇区的数据(这里是为什么我也不太清楚为什么,希望高人指教)。dwCreationDisposition需要制定为OPEN_EXISTING ,表示文件必须已经存在,由设备提出要求。函数如执行成功,则返回文件句柄。否则返回的句柄 = INVALID_HANDLE_VALUE表示出错,会设置GetLastError。具体失败原因可以查询ErrorCode。

第二个函数是ReadFile,其定义如下:

 BOOL ReadFile(
HANDLE hFile, //文件的句柄
LPVOIDl pBuffer, //用于保存读入数据的一个缓冲区
DWORD nNumberOfBytesToRead, //要读入的字节数
LPDWORD lpNumberOfBytesRead,//指向实际读取字节数的指针
LPOVERLAPPED lpOverlapped
//如文件打开时指定了FILE_FLAG_OVERLAPPED,那么必须,用这个参 数引用一个特殊的结构。
//该结构定义了一次异步读取操作。否则,应将这个参数设为NULL
);

该函数用于读取文件,这里指的是读取U盘扇区。其中lpOverlapped设为NULL即可。需要特别注意的是,由于磁盘是以扇区为单位进行读写的,所以这里读取的字节数必须是512的倍数!其他参数注释写的很明白,这里就不再解释了。配合SetFilePointer函数可以读取指定起始位置上指定字节的数据。

首先先来看一下用c++是如何读取引导扇区的数据的。

     // 笔者的U盘盘符为G
WCHAR szDiscFile[] = _T("\\\\.\\G:");
// 打开设备句柄
HANDLE hDisc = CreateFile(szDiscFile, GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, , NULL);
if (hDisc == INVALID_HANDLE_VALUE)
{
// 打开设备失败
return ;
}
// 从第一个扇区起始位置开始读取
SetFilePointer(hDisc, , , FILE_BEGIN);
// 需要读取字节数
DWORD dwNumber2Read = ;
// 实际读取的字节数
DWORD dwRealNumber;
// 分配缓冲区
char* buffer = new char[];
bool bRet = ReadFile(hDisc, buffer, dwNumber2Read, &dwRealNumber, NULL); // 扫尾工作,释放缓冲区,关闭句柄
delete[] buffer;
CloseHandle(hDisc);

如果我们在21行处下各断点的话可以看到buffer在内存中的数据,这里为了方便,笔者直接把这512个字节的数据保存到了文件中,用二进制文件读取软件打开来看了下,部分数据如下图所示:

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

  • 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表的起始位置。我们可以先来看一下下面这张图,有助于更好的理解保留扇区数的意思。

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

可以看到引导扇区后面紧跟这若干保留扇区,至于保留扇区的作用会在下一篇中分析,这里先跳过。而保留扇区的后面紧跟着的是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,
,
OPEN_EXISTING,
, ); if (handle != System.IntPtr.Zero)
{
return true;
}
else
{
return false;
}

由于本文的重点不是学习c#窗体编程,所以略过这部分,直接通过调用FileReader提供的系统API并编写窗体程序,效果如下图所示(本程序参考[3]),需要完整工程的读者可以点我下载,请我VS2012及以上打开。

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

好了,今天这篇文章就先写到这边。由于笔者才疏学浅,对于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

上一篇:zabbix3.0安装教程


下一篇:Beta冲刺随笔——Day_Seven