本文以ext2文件系统为例来剖析一个真实的文件系统如何查找文件,这对于深入理解文件系统至关重要。
1.准备文件系统镜像
所用工具:dd、mkfs.ext2、hexdump、dumpe2fs、mount等工具
1)制作100k大小镜像文件
$ dd if=/dev/zero of=ext2.img bs=1k count=100
记录了100+0 的读入
记录了100+0 的写出
102400 bytes (102 kB, 100 KiB) copied, 0.00125457 s, 81.6 MB/s
2)格式化为ext2文件系统格式
$ mkfs.ext2 ext2.img
mke2fs 1.44.1 (24-Mar-2018)
丢弃设备块: 完成
创建含有 100 个块(每块 1k)和 16 个inode的文件系统
正在分配组表: 完成
正在写入inode表: 完成
写入超级块和文件系统账户统计信息: 已完成
3)查看文件系统信息
$ dumpe2fs ext2.img
dumpe2fs 1.44.1 (24-Mar-2018)
Filesystem volume name: <none>
Last mounted on: <not available>
Filesystem UUID: 3680e1d5-7f58-4324-9cbd-c7d382f0c3df
Filesystem magic number: 0xEF53
Filesystem revision #: 1 (dynamic)
Filesystem features: ext_attr resize_inode dir_index filetype sparse_super large_file
Filesystem flags: signed_directory_hash
Default mount options: user_xattr acl
Filesystem state: clean
Errors behavior: Continue
Filesystem OS type: Linux
Inode count: 16
Block count: 100
Reserved block count: 5
Free blocks: 79
Free inodes: 5
First block: 1
Block size: 1024
Fragment size: 1024
Blocks per group: 8192
Fragments per group: 8192
Inodes per group: 16
Inode blocks per group: 2
Filesystem created: Wed May 26 15:23:33 2021
Last mount time: n/a
Last write time: Wed May 26 15:23:33 2021
Mount count: 0
Maximum mount count: -1
Last checked: Wed May 26 15:23:33 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: 5b0daa29-c2a0-4ab1-b09e-50992d3b070d
组 0:(块 1-99)
主 超级块位于 1,组描述符位于 2-2
块位图位于 3 (+2)
Inode 位图位于 4 (+3)
Inode表位于 5-6 (+4)
79 个可用 块,5 个可用inode,2 个目录
可用块数: 21-99
可用inode数: 12-16
2.解析文件系统镜像
1)dump文件系统镜像
$ hexdump -C ext2.img
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000400 10 00 00 00 64 00 00 00 05 00 00 00 4f 00 00 00 |....d.......O...|
00000410 05 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 |................|
00000420 00 20 00 00 00 20 00 00 10 00 00 00 ae f8 ad 60 |. ... .........`|
00000430 ae f8 ad 60 01 00 ff ff 53 ef 00 00 01 00 00 00 |...`....S.......|
00000440 75 f7 ad 60 00 00 00 00 00 00 00 00 01 00 00 00 |u..`............|
...
*
00018c00 0c 00 00 00 0c 00 01 02 2e 00 00 00 02 00 00 00 |................|
00018c10 0c 00 02 02 2e 2e 00 00 0d 00 00 00 e8 03 08 01 |................|
00018c20 74 65 73 74 2e 74 78 74 00 00 00 00 00 00 00 00 |test.txt........|
00018c30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00019000
根据之前dumpe2fs的信息我们知道:
镜像文件中(均为16进制显示)
00000000 开始的1k大小 保留的引导块
块1 00000400 开始的1k大小 保存磁盘的超级块 (dumpe2fs的部分信息从这里获得)
块2 00000800 开始的1k大小 保存块组描述符 (dumpe2fs的部分信息从这里获得)
块3 00000c00开始的1k大小 保存块位图
块4 00001000 开始的1k大小 保存 Inode 位图
块5 块6 00001400 开始的2k大小 保存 Inode表
剩下的为数据块
磁盘中的文件系统对象结构在内核如下文件定义:
fs/ext2/ext2.h
磁盘超级块:
struct ext2_super_block {
__le32 s_inodes_count; /* Inodes count */
__le32 s_blocks_count; /* Blocks count */
__le32 s_r_blocks_count; /* Reserved blocks count */
__le32 s_free_blocks_count; /* Free blocks count */
__le32 s_free_inodes_count; /* Free inodes count */
__le32 s_first_data_block; /* First Data Block */
__le32 s_log_block_size; /* Block size */
__le32 s_log_frag_size; /* Fragment size */
__le32 s_blocks_per_group; /* # Blocks per group */
__le32 s_frags_per_group; /* # Fragments per group */
__le32 s_inodes_per_group; /* # Inodes per group */
...
}
磁盘块组描述符:
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];
};
磁盘inode:
struct ext2_inode {
__le16 i_mode; /* File mode */
__le16 i_uid; /* Low 16 bits of Owner Uid */
__le32 i_size; /* Size in bytes */
__le32 i_atime; /* Access time */
__le32 i_ctime; /* Creation time */
__le32 i_mtime; /* Modification time */
__le32 i_dtime; /* Deletion Time */
__le16 i_gid; /* Low 16 bits of Group Id */
__le16 i_links_count; /* Links count */
__le32 i_blocks; /* Blocks count */
...
__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
...
};
磁盘目录项:
struct ext2_dir_entry_2 {
__le32 inode; /* Inode number */
__le16 rec_len; /* Directory entry length */
__u8 name_len; /* Name length */
__u8 file_type;
char name[]; /* File name, up to EXT2_NAME_LEN */
};
大家可以对照磁盘镜像文件和磁盘数据结构定义来解析出文件系统的超级块和块组描述符信息(可以发现和dumpe2fs工具显示的是一致的,例如镜像文件00000400 处四字节为10 00 00 00 是小端存储,所以为0x00000010=16);
对照ext2文件系统磁盘inode结构,可知i_block为磁盘inode结构的偏移40B处,内容即为0x63。
于是我们知道,dir目录数据块的块号 为0x63(偏移为 0x400 * 0x63= 0x18c00),这个数据块中保存的是dir目录中包含的所有目录和文件的目录项(我们知道这里为"."、".."、"test.txt"三个目录项)。
dir目录数据块的内容:
*
00018c00 0c 00 00 00 0c 00 01 02 2e 00 00 00 02 00 00 00 |................|
00018c10 0c 00 02 02 2e 2e 00 00 0d 00 00 00 e8 03 08 01 |................|
00018c20 74 65 73 74 2e 74 78 74 00 00 00 00 00 00 00 00 |test.txt........|
00018c30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
对照目录项ext2_dir_entry_2 结构,查找文件名为test.txt的inode号,即为0x0d(和我们之前通ls -lai显示的dir目录inode号13是一致)。
于是我们知道,test.txt文件的inode号为0x0d(13)。
2)查找test.txt文件
和上面查询根inode一样的原理,计算过程如下:
test.txt文件inode所在块组 编号: block_group = (ino - 1) / EXT2_INODES_PER_GROUP(sb) = (13 - 1) / 16 = 0
test.txt文件inode在 inode表中的inode偏移 : offset = ((ino - 1) % EXT2_INODES_PER_GROUP(sb)) * EXT2_INODE_SIZE(sb
= ( 12 % 16 ) * 128 = 1536(0x600)
文件系统中的test.txt文件inode所在块号 : block = le32_to_cpu(gdp->bg_inode_table) + (offset >> EXT2_BLOCK_SIZE_BITS(sb))
= 5 + (1536 >> 10) = 5 +1 =6
test.txt文件inode所在块号中偏移:offset &= (EXT2_BLOCK_SIZE(sb) - 1) = 1408(0x600)& (0x400 -1) = 0x200
inode中位置 = bh->b_data + offset = 所在块 + 0x200
所以:test.txt文件inode所在的镜像文件中偏移为:= 6 * 0x400 + 0x200 = 0x1800 + 0x200 = 0x1a00
查看 0x1a00偏移处内容如下(即是test.txt文件的磁盘inode内容):
00001a00 a4 81 00 00 06 00 00 00 85 ff ad 60 66 ff ad 60 |...........`f..`|
00001a10 66 ff ad 60 00 00 00 00 00 00 01 00 02 00 00 00 |f..`............|
00001a20 00 00 00 00 01 00 00 00 15 00 00 00 00 00 00 00 |................|
00001a30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
对照ext2文件系统磁盘inode结构,可知i_block为磁盘inode结构的偏移40B处,内容即为0x15。
于是我们知道,test.txt文件数据块的块号 为0x15(偏移为0x15 * 0x400 = 0x5400)。
查看 0x5400偏移处内容如下(test.txt文件数据块的内容):
*
00005400 68 65 6c 6c 6f 0a 00 00 00 00 00 00 00 00 00 00 |hello...........|
00005410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
最终可以看到文件数据为"hello"。
4)查找过程图解
以下为 dir/test.txt查找过程:
已知根目录inode号(ext2为2) -> 查找根目录磁盘inode(文件系统挂载时查找) -> 查找根目录的数据块 -> 查找dir目录的目录项找到其inode号 (为12) -> 查找dir目录的磁盘inode -> 查找dir目录的数据块 -> 查找test.txt文件的inode号(为13) -> 查找test.txt文件的磁盘inode -> 查找test.txt文件的数据块
下面为查找图解:
4.总结
对于ext2文件系统,路径名查找中,实际上是解析路径名的各个分量,查找每个分量的目录项,然后通过目录项找到inode号,通过inode号找到对应的磁盘inode,然后通过磁盘inode获得目录/文件的数据块, 最终查找到对应目录/文件的磁盘inode,而磁盘inode的i_block中保存着文件的逻辑块号和磁盘的逻辑块号映射关系,读写文件时就可以访问到整个文件。
1.准备文件系统镜像
所用工具:dd、mkfs.ext2、hexdump、dumpe2fs、mount等工具
1)制作100k大小镜像文件
$ dd if=/dev/zero of=ext2.img bs=1k count=100
记录了100+0 的读入
记录了100+0 的写出
102400 bytes (102 kB, 100 KiB) copied, 0.00125457 s, 81.6 MB/s
2)格式化为ext2文件系统格式
$ mkfs.ext2 ext2.img
mke2fs 1.44.1 (24-Mar-2018)
丢弃设备块: 完成
创建含有 100 个块(每块 1k)和 16 个inode的文件系统
正在分配组表: 完成
正在写入inode表: 完成
写入超级块和文件系统账户统计信息: 已完成
3)查看文件系统信息
$ dumpe2fs ext2.img
dumpe2fs 1.44.1 (24-Mar-2018)
Filesystem volume name: <none>
Last mounted on: <not available>
Filesystem UUID: 3680e1d5-7f58-4324-9cbd-c7d382f0c3df
Filesystem magic number: 0xEF53
Filesystem revision #: 1 (dynamic)
Filesystem features: ext_attr resize_inode dir_index filetype sparse_super large_file
Filesystem flags: signed_directory_hash
Default mount options: user_xattr acl
Filesystem state: clean
Errors behavior: Continue
Filesystem OS type: Linux
Inode count: 16
Block count: 100
Reserved block count: 5
Free blocks: 79
Free inodes: 5
First block: 1
Block size: 1024
Fragment size: 1024
Blocks per group: 8192
Fragments per group: 8192
Inodes per group: 16
Inode blocks per group: 2
Filesystem created: Wed May 26 15:23:33 2021
Last mount time: n/a
Last write time: Wed May 26 15:23:33 2021
Mount count: 0
Maximum mount count: -1
Last checked: Wed May 26 15:23:33 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: 5b0daa29-c2a0-4ab1-b09e-50992d3b070d
组 0:(块 1-99)
主 超级块位于 1,组描述符位于 2-2
块位图位于 3 (+2)
Inode 位图位于 4 (+3)
Inode表位于 5-6 (+4)
79 个可用 块,5 个可用inode,2 个目录
可用块数: 21-99
可用inode数: 12-16
2.解析文件系统镜像
1)dump文件系统镜像
$ hexdump -C ext2.img
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000400 10 00 00 00 64 00 00 00 05 00 00 00 4f 00 00 00 |....d.......O...|
00000410 05 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 |................|
00000420 00 20 00 00 00 20 00 00 10 00 00 00 ae f8 ad 60 |. ... .........`|
00000430 ae f8 ad 60 01 00 ff ff 53 ef 00 00 01 00 00 00 |...`....S.......|
00000440 75 f7 ad 60 00 00 00 00 00 00 00 00 01 00 00 00 |u..`............|
...
*
00018c00 0c 00 00 00 0c 00 01 02 2e 00 00 00 02 00 00 00 |................|
00018c10 0c 00 02 02 2e 2e 00 00 0d 00 00 00 e8 03 08 01 |................|
00018c20 74 65 73 74 2e 74 78 74 00 00 00 00 00 00 00 00 |test.txt........|
00018c30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00019000
根据之前dumpe2fs的信息我们知道:
镜像文件中(均为16进制显示)
00000000 开始的1k大小 保留的引导块
块1 00000400 开始的1k大小 保存磁盘的超级块 (dumpe2fs的部分信息从这里获得)
块2 00000800 开始的1k大小 保存块组描述符 (dumpe2fs的部分信息从这里获得)
块3 00000c00开始的1k大小 保存块位图
块4 00001000 开始的1k大小 保存 Inode 位图
块5 块6 00001400 开始的2k大小 保存 Inode表
剩下的为数据块
磁盘中的文件系统对象结构在内核如下文件定义:
fs/ext2/ext2.h
磁盘超级块:
struct ext2_super_block {
__le32 s_inodes_count; /* Inodes count */
__le32 s_blocks_count; /* Blocks count */
__le32 s_r_blocks_count; /* Reserved blocks count */
__le32 s_free_blocks_count; /* Free blocks count */
__le32 s_free_inodes_count; /* Free inodes count */
__le32 s_first_data_block; /* First Data Block */
__le32 s_log_block_size; /* Block size */
__le32 s_log_frag_size; /* Fragment size */
__le32 s_blocks_per_group; /* # Blocks per group */
__le32 s_frags_per_group; /* # Fragments per group */
__le32 s_inodes_per_group; /* # Inodes per group */
...
}
磁盘块组描述符:
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];
};
磁盘inode:
struct ext2_inode {
__le16 i_mode; /* File mode */
__le16 i_uid; /* Low 16 bits of Owner Uid */
__le32 i_size; /* Size in bytes */
__le32 i_atime; /* Access time */
__le32 i_ctime; /* Creation time */
__le32 i_mtime; /* Modification time */
__le32 i_dtime; /* Deletion Time */
__le16 i_gid; /* Low 16 bits of Group Id */
__le16 i_links_count; /* Links count */
__le32 i_blocks; /* Blocks count */
...
__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
...
};
磁盘目录项:
struct ext2_dir_entry_2 {
__le32 inode; /* Inode number */
__le16 rec_len; /* Directory entry length */
__u8 name_len; /* Name length */
__u8 file_type;
char name[]; /* File name, up to EXT2_NAME_LEN */
};
大家可以对照磁盘镜像文件和磁盘数据结构定义来解析出文件系统的超级块和块组描述符信息(可以发现和dumpe2fs工具显示的是一致的,例如镜像文件00000400 处四字节为10 00 00 00 是小端存储,所以为0x00000010=16);
对照ext2文件系统磁盘inode结构,可知i_block为磁盘inode结构的偏移40B处,内容即为0x63。
于是我们知道,dir目录数据块的块号 为0x63(偏移为 0x400 * 0x63= 0x18c00),这个数据块中保存的是dir目录中包含的所有目录和文件的目录项(我们知道这里为"."、".."、"test.txt"三个目录项)。
dir目录数据块的内容:
*
00018c00 0c 00 00 00 0c 00 01 02 2e 00 00 00 02 00 00 00 |................|
00018c10 0c 00 02 02 2e 2e 00 00 0d 00 00 00 e8 03 08 01 |................|
00018c20 74 65 73 74 2e 74 78 74 00 00 00 00 00 00 00 00 |test.txt........|
00018c30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
对照目录项ext2_dir_entry_2 结构,查找文件名为test.txt的inode号,即为0x0d(和我们之前通ls -lai显示的dir目录inode号13是一致)。
于是我们知道,test.txt文件的inode号为0x0d(13)。
2)查找test.txt文件
和上面查询根inode一样的原理,计算过程如下:
test.txt文件inode所在块组 编号: block_group = (ino - 1) / EXT2_INODES_PER_GROUP(sb) = (13 - 1) / 16 = 0
test.txt文件inode在 inode表中的inode偏移 : offset = ((ino - 1) % EXT2_INODES_PER_GROUP(sb)) * EXT2_INODE_SIZE(sb
= ( 12 % 16 ) * 128 = 1536(0x600)
文件系统中的test.txt文件inode所在块号 : block = le32_to_cpu(gdp->bg_inode_table) + (offset >> EXT2_BLOCK_SIZE_BITS(sb))
= 5 + (1536 >> 10) = 5 +1 =6
test.txt文件inode所在块号中偏移:offset &= (EXT2_BLOCK_SIZE(sb) - 1) = 1408(0x600)& (0x400 -1) = 0x200
inode中位置 = bh->b_data + offset = 所在块 + 0x200
所以:test.txt文件inode所在的镜像文件中偏移为:= 6 * 0x400 + 0x200 = 0x1800 + 0x200 = 0x1a00
查看 0x1a00偏移处内容如下(即是test.txt文件的磁盘inode内容):
00001a00 a4 81 00 00 06 00 00 00 85 ff ad 60 66 ff ad 60 |...........`f..`|
00001a10 66 ff ad 60 00 00 00 00 00 00 01 00 02 00 00 00 |f..`............|
00001a20 00 00 00 00 01 00 00 00 15 00 00 00 00 00 00 00 |................|
00001a30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
对照ext2文件系统磁盘inode结构,可知i_block为磁盘inode结构的偏移40B处,内容即为0x15。
于是我们知道,test.txt文件数据块的块号 为0x15(偏移为0x15 * 0x400 = 0x5400)。
查看 0x5400偏移处内容如下(test.txt文件数据块的内容):
*
00005400 68 65 6c 6c 6f 0a 00 00 00 00 00 00 00 00 00 00 |hello...........|
00005410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
最终可以看到文件数据为"hello"。
4)查找过程图解
以下为 dir/test.txt查找过程:
已知根目录inode号(ext2为2) -> 查找根目录磁盘inode(文件系统挂载时查找) -> 查找根目录的数据块 -> 查找dir目录的目录项找到其inode号 (为12) -> 查找dir目录的磁盘inode -> 查找dir目录的数据块 -> 查找test.txt文件的inode号(为13) -> 查找test.txt文件的磁盘inode -> 查找test.txt文件的数据块
下面为查找图解:
4.总结
对于ext2文件系统,路径名查找中,实际上是解析路径名的各个分量,查找每个分量的目录项,然后通过目录项找到inode号,通过inode号找到对应的磁盘inode,然后通过磁盘inode获得目录/文件的数据块, 最终查找到对应目录/文件的磁盘inode,而磁盘inode的i_block中保存着文件的逻辑块号和磁盘的逻辑块号映射关系,读写文件时就可以访问到整个文件。