一个有趣的文件系统实验

418 阅读9分钟

本文节选自《实验指导手册》第二版第14.1章,下载《实验指导手册》:登陆“奔跑吧linux社区”微信公众号,输入“奔跑吧2”获取下载地址。

有趣的文件系统实验

图片

入门篇第二版第14章是新增的一章,讲文件系统相关的入门知识。实验14-1非常有趣,群里有小伙伴问笨叔,这个实验怎么做啊?这个实验是这样的:
使用dd命令创建磁盘文件file.img并格式化为ext2文件系统,然后通过mout命令挂载到Linux主机文件系统。
(1)查看文件系统的信息,比如数据块的数量、数据块的大小、inode 个数、空闲数据块的数量等信息,并画出文件系统的布局图。
(2)在文件系统中创建文件test.txt,写入一些数据。查看test.txt文件的inode编号,统计test.txt文件占用了哪几个数据块。
(3)使用dd或hexdump命令导出file.img磁盘文件的二进制数据并且分析超级块。读者可以对照Linux内核中的ext2_super_block数据结构来分析磁盘文件的二进制数据。\

实验详解

我们在QEMU+runninglinuxkernel平台上做实验。我们首先保证RLK系统能支持ext2文件系统。
修改arch/arm64/configs/debian_defconfig文件支持ext2文件系统。

CONFIG_BLK_DEV_LOOP=y
CONFIG_EXT2_FS=y

然后重新编译内核,并运行。

$ ./run_rlk_arm64.sh build_kernel
$ ./run_rlk_arm64.sh run

使用dd命令来创建一个ext2.img文件。

benshushu:benshushu# dd if=/dev/zero of=ext2.img bs=4K count=64
64+0 records in
64+0 records out
262144 bytes (262 kB, 256 KiB) copied, 0.0176809 s, 14.8 MB/s

格式化。

benshushu:benshushu# mkfs.ext2 ext2.img 
mke2fs 1.45.0 (6-Mar-2019)
Discarding device blocks: done                            
Creating filesystem with 256 1k blocks and 32 inodes

Allocating group tables: done                            
Writing inode tables: done                            
Writing superblocks and filesystem accounting information: done

我们先挂载该文件系统。

benshushu:#mkdir /home/benshushu/ext2
benshushu:ext2# mount -t ext2 -o loop ext2.img /home/benshushu/ext2

我们在ext2文件系统中新建一个test.txt文件,然后在该文件里输入一个字符串“I am benshushu”。

上面准备工作完成之后,我们来开始分析这个文件系统了。首先使用dumpe2fs命令来查看这个ext2.img文件系统的布局情况。

benshushu:benshushu# dumpe2fs ext2.img 
dumpe2fs 1.45.0 (6-Mar-2019)
Filesystem volume name:   <none>
Last mounted on:          <not available>
Filesystem UUID:          d56e86f3-afd6-4edd-b1a3-3d7c366655bf
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      ext_attr resize_inode dir_index filetype sparse_super large_file
Filesystem flags:         unsigned_directory_hash 
Default mount options:    user_xattr acl
Filesystem state:         not clean
Errors behavior:          Continue
Filesystem OS type:       Linux
Inode count:              32
Block count:              256
Reserved block count:     12
Free blocks:              233
Free inodes:              21
First block:              1
Block size:               1024
Fragment size:            1024
Blocks per group:         8192
Fragments per group:      8192
Inodes per group:         32
Inode blocks per group:   4
Filesystem created:       Fri Mar 12 08:40:28 2021
Last mount time:          n/a
Last write time:          Fri Mar 12 09:09:16 2021
Mount count:              2
Maximum mount count:      -1
Last checked:             Fri Mar 12 08:40:28 2021
Check interval:           0 (<none>)
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11
Inode size:              128
Default directory hash:   half_md4
Directory Hash Seed:      b3cabb7f-5a1e-4a8e-97fb-83b381ccab58


Group 0: (Blocks 1-255)
  Primary superblock at 1, Group descriptors at 2-2
  Block bitmap at 3 (+2)
  Inode bitmap at 4 (+3)
  Inode table at 5-8 (+4)
  232 free blocks, 20 free inodes, 2 directories
  Free blocks23-3032-255
  Free inodes12-1315-32

从上述日志可以知道这个迷你型的空闲分区的如下信息。

  • 一共有256个数据块。

  • 每个数据块的大小为1KB。

  • 最多支持32个inode。

  • 第1个数据块开始存储数据。

  • 空闲的inode为21个。

  • 空闲的数据块为233个。

  • 预留的数据块为12个。

  • 每一组(group)可以有8192个空闲块。
    ext2文件系统还把分区分成了组(Group),这个ext.img中只有一个组,这个组包含了非常重要的文件系统布局信息,如图14.4所示。

  • 超级块(superblock)在第1个块。

  • 组描述符(Group descriptors)在第2个块。

  • 块位图(Block bitmap)在第3个块。

  • inode位图(Inode bitmap)在第4个块。

  • inode表(Inode table)在第5~8个块,一共占用4个块。

  • 第8~10个块为预留的块。

  • 第23~255个块为空闲的数据块,可组成数据区。(中间第31、32个数据块是被使用了)

  • 第12~31个inode节点为空闲的。(第14个inode节点被使用了)

  • 图片

1. 分析超级块的信息

第0个数据块通常是引导块,暂时没有用来存储数据,里面全是0的数据,我们可以使用dd命令来查看。

dd if=ext2.img bs=1 count=1024 skip=0 | od -t x1 -Ax

这里首先使用dd命令来读取ext2.img的内容,其中

  • bs:设置读入/输出的块大小为bytes个字节
  • count:读取多少个块数据
  • skip:从输入文件开头跳过多少个块后再开始读取数据。

另外od命令用来显示数据的内容,其格式为:
od [-A 地址进制] [-t 显示格式] 文件名

  • A  :按指定的进制来显示地址:
  • d 十进制
  • 八进制(系统默认值)
  • x 十六进制
  • n 不打印位移值
  • t 指定数据的显示格式,主要的参数有:
  • c ASCII字符或反斜杠序列
  • d 有符号十进制数
  • f 浮点数
  • 八进制(系统默认值为02)
  • u 无符号十进制数
  • x 十六进制数

图片

接下来分析超级块的内容。

  dd if=ext2.img bs=1 count=1024 skip=1024 | od -t x1 -Ax

图片

对照struct ext2_super_block数据结构,我们可以知道:

  • s_inodes_count的值为0x20,即32,表示inode节点的个数
  • s_blocks_count的值为0x100,即256,一共有多少个块
  • s_r_blocks_count的值为0xc,即12,保留的块有多少个。
  • s_free_blocks_count的值为0xe9,即空闲的数据块,233个。
  • s_free_inodes_count的值为0x15,即21,表示空闲的inode节点个数。
  • s_first_data_block的值为0x1,即有效数据是从第1个数据块开始。
  • s_log_block_size的值为0,那么计算方法为:2^0 * 1024 = 1024 字节
  • s_log_frag_size的值为0,计算方法和s_log_block_size。
  • s_blocks_per_group的值为8192,表示每个组有多少个数据块。
  • 剩下的成员,大家可以继续来分析。

按照上面的方法,大家可以对照来找到struct ext2_super_block数据结构每个成员的值。如果看十六进制比较不方便,可以让od命令显示十进制。

图片

2. 分析组描述符

组描述符位于在超级块后面的数据块。组描述符的数据结构如下。

/*
 * Structure of a blocks group descriptor
 */
struct ext2_group_desc
{
    __le32  bg_block_bitmap;        /* Blocks bitmap block */
    __le32  bg_inode_bitmap;        /* Inodes bitmap block */
    __le32  bg_inode_table;     /* Inodes table block */
    __le16  bg_free_blocks_count;   /* Free blocks count */
    __le16  bg_free_inodes_count;   /* Free inodes count */
    __le16  bg_used_dirs_count; /* Directories count */
    __le16  bg_pad;
    __le32  bg_reserved[3];
};

接下来分析组描述符的内容。

dd if=ext2.img bs=1 count=1024 skip=2048 | od -t d -Ax

图片

从dd打印的内容可知:

  • bg_block_bitmap的值为3,表示块位图(Block bitmap)在第3个块。
  • bg_inode_bitmap的值为4,表示inode位图(Inode bitmap)在第4个块。
  • bg_inode_table的值为5,表示inode表(Inode table)在第5块。
  • bg_free_blocks_count成员是16位,它的值为0xe8,即232个空闲数据块。
  • bg_free_inodes_count成员也是16位数据,它的值为0x14,即20个空闲的inode节点。
  • bg_used_dirs_count表示已经存在的目录,目前为0。

3. 分析inode表

Inode表是在第5个数据块,一共有4个数据块存储inode表。Ext2文件系统使用struct ext2_inode数据结构来表示一个inode节点,其中struct ext2_inode数据结构的大小为128字节。

那为啥要使用4个数据块来存储inode表呢?
从3.1节分析超级块可知,这个ext2文件系统最多支持32个inode节点,那么

                   128 * 32 = 4096

正好是4个数据块。

接下来我们需要通过stat命令来确定test.txt的inode节点号。
进入ext2目录,使用stat命令来查看。

图片

从stat命令可以看出test.txt使用的inode节点号为14。
那么,我们需要读取第14个inode节点的struct ext2_inode数据结构的内容。
那么第14个inode节点在inode表的位置,计算公式如下:

             (14 – 1) * 128 = 1664

               1664 – 1024 = 640 = 0x280

所以,第14个inode节点,位于inode表中第二个数据块的0x280地址处。使用dd命令来读取第6个数据块的内容。

图片

那么地址0x280处开始的数据就是第14号inode节点的内容了。

图片

Ext2文件系统采用直接和间接索引的方式来索引数据块,详见《奔跑吧linux内核 入门篇》第二版第14.2.2章内容。

/*
 * Structure of an inode on the disk
 */
struct ext2_inode {
      …
    __le32  i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
…    
}

i_block数组是位于struct ext2_inode数据结构中第40个字节开始的地方。
从上图可知,0x280 + 40 = 0x2A8,也就是说i_block数组存储在0x2ab地址处,这个值为0x1f,就31,也就是test.txt数据存储在第31个数据块里。

我们马上使用dd命令来查看,发现“I am benshushu”字符串果然存储在第31个数据块里。

图片

总结,我们从这个实验完成了对ext2文件系统的静态分析和动态分析,相信会对大家理解文件系统有帮助。

4. e2fsprogs工具

e2fsprogs是一个Ext2(及Ext3/4)文件系统工具(Ext2Filesystems Utilities),它包含了诸如创建、修复、配置、调试ext2文件系统等的标准工具。我们可以使用这个工具来分析某个文件是占用的哪些数据块,不过我们还是建议大家学习前面的分析方法。
首先在QEMU+runninglinuxkernel里安装e2fsprogs工具。

benshushu:benshushu# apt update
benshushu:benshushu# apt install e2fsprogs

e2fsprogs工具里内置了很多有用的工具,我们接下来使用叫做debugfs的小工具。
直接输入debugfs打开这个小工具。

benshushu:benshushu# debugfs 
debugfs 1.46.2 (28-Feb-2021)

然后使用open子命令来打开文件系统。

debugfs:  open ext2.img

使用block子命令来查看test.txt文件占用了哪些数据块。

debugfs:  blocks test.txt
31

Debugfs命令很快找出test.txt文件占用的是第31个数据块,和我们前面的分析结论一样。
另外,我们还可以使用imap子命令来查看test.txt文件的inode节点情况。

debugfs:  imap test.txt
Inode 14 is part of block group 0
    located at block 6, offset 0x0280

从上述信息可知,test.txt文件使用的是第14个inode节点,位于Group 0中的第6个数据块,偏移为0x280,和我们前面的分析结论一样。

图片

下载实验指导手册

入门篇配套的实验指导手册pdf版本是基于开源精髓,大家可以免费下载,免费传阅,自由打印。如果小伙伴需要纸质版本,请自行打印。

图片

图片

图片

下载《实验指导手册》:登陆“奔跑吧linux社区”微信公众号,输入“奔跑吧2”获取下载地址。

图片