前言
前段时间做linux虚拟化相关的项目,需要实现一个功能,通过linuxExt3\4文件系统中文件的inode号,计算出该文件在linux文件系统的磁盘块号(blocks)以及扇区号(sectors),基本上算是对linux的EXT文件系统有了较深的理解。这样的一个工作,花费了我很长的时间,网上只是关于文件系统的资料比较多,但牵扯到具体的计算,基本上没有资料。因此,我想在这里,把自己前一段时间的工作成果分享出来,或许能给以后在这方面研究的朋友们一些帮助。
文件inode号
在介绍EXT文件系统之前,我想先对文件的inode号进行一下说明。
文件存储在硬盘上,硬盘的最小存储单位叫做扇区(sector)。每个扇区的大小不一,常见512KB。操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块"(block)。这种由多个扇区组成的"块",是文件存取的最小单位。"块"的大小,最常见的是4KB,即连续八个 sector组成一个 block。文件数据就存放在磁盘块中,inode作为文件的元信息,存储则文件创建者,创建日期,大小,读写权限等。每个文件对应一个唯一inode号。
每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。
EXT文件系统结构
EXT文件系统的结构如上图所示,首先第一块是启动块——Boot Block,这个里面基本上为0值。
接下来就是一个紧挨着另外一个的块组——Block group(上文提到的块,多个块组成的块组)。每个块组都是相互独立的存储着信息。从图中可以看出,每个块组都会包含下列信息(EXT4稍微有改动,稍后提及)。
- 一个文件系统超级块的拷贝(占用1块)
- 一个文件系统组描述符的拷贝(占用n块)
- 一个数据块位图(占用1块)
- 一个索引节点位图(占用1块)
- 一个索引节表(占用n块)
- 一个大数据块(占用n块)
由上可知,超级块以及块组描述符内容一样,且只有块组0中所包含的超级块和组描述符才有内核使用,即使这样也会拷贝到所有的块组中,如果出现数据损坏,并且0块组的主超级块和主描述符变为无效,那么系统管理员就可以命令e2fsck引用存放在摸个块组(除了第一个块组)中的超级块和组描述符的旧拷贝(ext4 考虑到在每个块组中都备份有点多余,尤其是组描述符表所以就仅在块号以 3 , 5 , 7 为幂的块组上进行备份。)。那么有多少块组呢?这将取决于文件系统分区的大小和块的大小。
文件系统信息查看
在深入到文件系统的数据结构之前,我想先介绍文件系统的查看工具dumpe2fs。
dumpe2fs /dev/sda1
Filesystem volume name: <none>
Last mounted on: /
Filesystem UUID: 90dcc170-957c-41c6-a0ba-2c3d045fc481
Filesystem magic number: 0xEF53
Filesystem revision #:1 (dynamic)
Filesystem flags: signed_directory_hash
Default mount options:user_xattr acl
Filesystem state: clean
Errors behavior: Continue
Filesystem OS type: Linux
Inode count: 30334976
Block count: 121309952
Reserved block count: 6065497
Free blocks: 85495976
Free inodes: 30080455
First block: 0
Block size: 4096
Fragment size:4096
Reserved GDT blocks: 995
Blocks per group: 32768
Fragments per group: 32768
Inodes per group: 8192
Inode blocks per group: 512
Flex block group size:16
Filesystem created: Thu Mar 31 02:34:34 2016
Last mount time: Wed Aug 24 10:24:20 2016
Last write time: Wed Aug 24 10:24:20 2016
Mount count: 70
Maximum mount count: -1
Last checked: Thu Mar 31 02:34:34 2016
Reserved blocks gid: 0 (group root)
First inode: 11
Inode size: 256
Required extra isize: 28
Desired extra isize: 28
Journal inode:8
Journal backup: inode blocks
Journal features: journal_incompat_revoke
Journal size: 128M
Journal length: 32768
Journal sequence: 0x0001117a
Journal start:1
Group 0: (Blocks 0-32767) [ITABLE_ZEROED]
Checksum 0x9d44, unused inodes 8178
Primary superblock at 0, Group descriptors at 1-29
Reserved GDT blocks at 30-1024
Block bitmap at 1025 (+1025), Inode bitmap at 1041 (+1041)
Inode table at 1057-1568 (+1057)
23485 free blocks, 8179 free inodes, 2 directories, 8178 unused inodes
Free blocks: 9283-32767
Free inodes: 14-8192
Group 1: (Blocks 32768-65535) [INODE_UNINIT, ITABLE_ZEROED]
Checksum 0x9df4, unused inodes 8192
Backup superblock at 32768, Group descriptors at 32769-32797
Reserved GDT blocks at 32798-33792
Block bitmap at 1026 (bg #0 + 1026), Inode bitmap at 1042 (bg #0 + 1042)
Inode table at 1569-2080 (bg #0 + 1569)
959 free blocks, 8192 free inodes, 0 directories, 8192 unused inodes
Free blocks: 34762-34783, 34810-34815, 56835-56863, 56881-57023, 57047-57055, 57081-57087, 57259-57343, 62126-62431, 62452-62687, 62705-62719, 62911, 62929-62943, 62961-62975, 63281-63295, 63313-63327, 63345-63359, 63463-63487
Free inodes: 8193-16384
`</pre>
       以上是使用dumpe2fs对本地磁盘的部分查看结果,会列出所有的块组信息,现在只显示前两个块组。这里有一个小细节,我们查看这些块组信息时每次都要重新使用dumpe2fs工具,而且想要查看后面的块组,还要一点一点翻着看,这时我们就可以借助linux的数据流重定向功能,把这些信息定位到一个文件中去。
<pre>`dumpe2fs /dev/sda1 > dump
`</pre>
       这样我们的磁盘sda1的基本信息就存储在了dump文件中,可以随时快速查看。
       从上面的信息中可以看出文件inode总数,磁盘块大小(block size),每个块组磁盘块数(block per group),每个块组inode数,inode大小(inode size)。
## 超级块
       超级块存放在一个ext_super_block的结构中。在内核结构中,使用了__le16、__le32、__be16、__be32的数据类型,前两种类型分别表示字或双字的“小尾”即小端序的排序方式(低阶字节在高位地址),而后两种类型分别表示字或双字的“大尾”即大端序的排序方式(高阶字节在高位地址)。接下来介绍一下超级块的字段,这里我们引用《深入理解linux内核》这本书中的数据结构截图。
![](http://i.imgur.com/Q2s4lUR.png)
![](http://i.imgur.com/Y1LRhnm.png)
       在我们对超级块的数据结构有些了解之后,我们开始对磁盘进行字节方面的探索,依旧是使用linux的数据流重定向功能,把文件系统的字节信息输入的指定的文件中去,以便之后查看。
<pre>`od -tx1 -Ax -N2000 /dev/sda1 > blockinfo
上述代码的意思就是查看/dev/sda1磁盘块的2000字节的信息,并输出到文件blockinfo中。在这里我的磁盘信息如下图:
可以看出来,这里的数字全是以16进制进行的。跟我们之前分析的文件系统磁盘结构一样,刚开始有1000字节的空白,代表的是启动块。从第0X400(即第1000字节)开始,就到了第一个块组的范围,第一个块组的头部是超级块,也就是说,从0X400开始,就是第一个超级块。前四个字节是01cee000=30334976(之前说过的小端序),这四个字节代表的是inode count,inode的个数,可以看出来,与刚才在块组信息里面的inode个数一致;接下来四个字节是block count总块数,0X073b0b00=121309952,同样和之前的块组信息里面的总块数一致。这里我只是给大家提供一种方法,如何找到文件系统的数据结构中的各个字段,下面还有很多,朋友们可以自行验证找到所有字段来获取想要得到的信息。需要注意的是各个字段的长度。
块组描述符
Group Descriptor Tabel 每个块组都有一个专门的数据结构用来描述整个块组的相关信息,即块组描述表,它紧跟在超级块后面的一个块,其每一项称为组描述符,是一个大小为32字节的数据结构。记录组中块位图所在的块号、索引节点位图所在的块号、索引节点表的第一个块号等。
块组描述符的数据结构如下图:
还是从之前的块组信息中看到 :Primary superblock at 0, Group descriptors at 1-29,第二块就是快组描述符的位置了。由于每个块大小为4096字节,所以在0x1000开始为第二块,即快组描述符的开始。
这样我们可以对照数据结构,在这些字节码中,找到需要的信息。
其他结构
Block Bitmap 即此块组中的块位图,记录此组中的哪些块已经被占用,在分配块时使用。
Inode Bitmap 即此块组中的索引节点位图,记录本组中的哪些索引节点已经被占用。
Inode Table 即此块组中的索引节点表,每一项是一个文件的索引,即Inode,记录此文件的相关信息,其中本文最为关心的是地41~100字节,记录着文件所占数据库的信息。
Data Blocks 即此块组中的数据块,可供分配给各个文件使用。
在这篇文章中说明了如何查找文件系统中的相关信息的数据结构,接下来探讨如何通过inode计算出文件所在的磁盘块号和扇区号。