深入理解ext4(一)----extent区段
来源:互联网 发布:淘宝互刷是怎么刷的 编辑:程序博客网 时间:2024/04/29 18:41
文件系统是操作系统的一个重要组成部分,也有着举足轻重的地位。本系列文章主要讲述了linux ext4文件系统的一些实现原理。笔者参考了2.6.32.60的内核源代码。在写这篇的文章时,最新的内核已经去到了3.7.9。
ext4是替代ext2/3的Linux文件系统。从2.6.28版本开始,被正式认定进入稳定(stable)。本文主要介绍ext4文件系统在硬盘层面上的存储结构及原理。
读者对象:对Linux有一定基础,希望了解ext4底层原理,和它与ext2/3系统区别。
关键词汇
先回顾几个基本的概念,如果不是特别清楚下面几个概念的话,可以去google一下。
inode:索引节点
superblock:超级块
block:文件系统块
block group:文件系统块组
disk block:磁盘块(512字节)
block device:块设备
VFS:虚拟文件系统
EXT4存储结构
假如把整个超级块比如一本书,那么文件系统的工作就是把要记录的内容,按页码,行段记录在这本书里。这当然也包括书的目录了。我们使用dumpe2fs工具输出:
Reserved GDT blocks: 609
Blocks per group: 32768
Fragments per group: 32768
Inodes per group: 8128
Inode blocks per group: 508
Flex block group size: 16
Filesystem created: Mon May 14 13:30:51 2012
Last mount time: Sun Jan 6 18:51:16 2013
Last write time: Sun Jan 6 18:51:16 2013
Mount count: 282
Maximum mount count: -1
Last checked: Mon May 14 13:30:51 2012
Check interval: 0 (<none>)
Lifetime writes: 20 GB
Reserved blocks uid: 0 (user root)
Reserved blocks gid: 0 (group root)
First inode: 11
Inode size: 256
Required extra isize: 28
Desired extra isize: 28
Journal inode: 8
首先,映入眼帘的是该超级块的相关重要参数,比如inode大小,块组含块数,块组inode数目等等。这些数据是存在ext4_super_block,ext4_sb_info这两个结构体中,定义在ext4.h头文件里,它们不是本篇重要讨论的内容。我们只需要知道它们是存放一些超级块信息的结构体即可。
接下来,可以看到ext4硬盘上的存储结构:
Group 0: (Blocks 0-32767) [ITABLE_ZEROED]
Checksum 0x7cf3, unused inodes 0
Primary superblock at 0, Group descriptors at 1-2
Reserved GDT blocks at 3-611
Block bitmap at 612 (+612), Inode bitmap at 628 (+628)
Inode table at 644-1151 (+644)
2720 free blocks, 0 free inodes, 1383 directories
Free blocks: 8888-8959, 9068, 9071-9135, 9144-9175, 9200-9207, 9213-9214, 9279, 9700-10120, 11823-11964, 12213-12870, 12879-13043, 13139-13254, 18432-19021, 22748-22975, 32549-32767
Free inodes:
这是块组0的情况,它表明块组0由块号为0-32767这32768个块组成,超级块基本信息存在块0,块组描述符在块1-2,预留的块组描述符表在块3-611,块位图在块612中,inode位图在块628中,Inode表在块644-1151中,空闲的块有很多,空闲的inode没了。
接下来,我们将重点分析这句废话中每个词的含义
超级块基本信息:
我们在前面已经讲过了。顾名思义,不多解释。
块组描述符
在内核中就是结构体ext4_group_desc,它包括的内容为:块位图块号,inode位图块号,inode表块号,空闲块计数,自由块计数等等。
预留的块组描述符表
为以后要使用所留下来的空间。
块位图
这个就是一个块使用情况记录表。记录哪些块使用,哪些块未使用。它的原理就是对整个块组中0-32767这总共32768个块中作一个映射。根据一个bytes有8个位00000000,一个块有4096bytes也就是有4096*8=32768个位,这32768个位刚好对应了块组中32768的块。如果第N个块被使用了则标记第N位为1,否则为0。
inode位图
和上面的块位图一样,这个是inode的使用情况记录表。由超级块基本信息可以看到每个块组有8128个inode,这里对inode的映射原理和块位图也是一样,只不过 没有用满一个块。
inode表
inode表就是具体存放inode信息的地方。在ext4中,inode的大小为256字节(ext2/3中仅有它的一半,128字节),一个块可以存放16个inode,由于一个块组有8128 个inode,总共需要8128/16=508个块存放inode表。这个值可以在超级块基本信息中的Inode blocks per group中看到。
讲完了这些词的含义,我们对group 0有了初步的了解。那么group 1呢?
Group 1: (Blocks 32768-65535) [ITABLE_ZEROED]
Checksum 0xbb99, unused inodes 0
Backup superblock at 32768, Group descriptors at 32769-32770
Reserved GDT blocks at 32771-33379
Block bitmap at 613 (+4294935141), Inode bitmap at 629 (+4294935157)
Inode table at 1152-1659 (+4294935680)
598 free blocks, 0 free inodes, 648 directories
Free blocks: 33424-33439, 33442-33443, 33564-33627, 33644-33647, 33652-33663, 33725-33871, 33878-33931, 33934-33973, 33976-33983, 34046-34047, 34176-34303, 36008, 36015, 36019, 36412, 40299-40415
Free inodes:
我们看到group 1 中,primary superblock 变为了backup superblock,由于超级块基本信息对于文件系统至关重要,为了系统的健壮性,ext文件系统在每个块组中都进行了备份。ext4考虑到在每个块组中都备份有点多余,尤其是组描述符表所以就仅在块号以3,5,7为幂的块组上进行备份。
用个表格表示超级块中块组的结构:
ext4 超级块
块组描述符
Reserved GDT Blocks
数据块位图
Inode位图
inode 表
数据块
1 block
若干blocks
若干 blocks
1 block
1 block
若干
好多好多块
inode
Purpose
0
Doesn't exist; there is no inode 0.
1
List of defective blocks.
2
Root directory.
3
ACL index.
4
ACL data.
5
Boot loader.
6
Undelete directory.
7
Reserved group descriptors inode.
8
Journal inode.
11
First non-reserved inode. Usually this is the lost+found directory.
块寻址
ext4的块寻址已经改为48位。这种设计改动是为了支持更大的文件系统大小。EXT4使用了区段(extents)这个概念,取代了过去早期UNIX文件系统中(ext2/3)中低效的非直接块映射机制。区段和NTFS上的cluster有点类似,它们都是选定了一个特定的块地址并把数个块组合一个区间。一个文件如果是碎片化的,那么就意味它着拥有多个区段, ext4会尽它自己的努力保持文件连续。
这种新的块寻址策略导致了先前工具的大部分问题。举个列子:
[root@localhost Desktop]# stat math.c
File: `math.c'
Size: 1477 Blocks: 8 IO Block: 4096 regular file
Device: fd00h/64768d Inode:420402 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2013-01-05 15:07:11.815541582 +0800
Modify: 2012-08-20 13:40:02.496797954 +0800
Change: 2012-12-30 11:28:54.751357610 +0800
由上面得到文件math.c的Inode号为420402
[root@localhost Desktop]# istat /dev/mapper/VolGroup-lv_root 420402
inode: 420402
Allocated
Group: 51
Generation Id: 1062005310
uid / gid: 0 / 0
mode: rrw-r--r--
Flags:
size: 1477
num of links: 1
Inode Times:
Accessed: 2013-01-05 15:07:11 (CST)
File Modified: 2012-08-20 13:40:02 (CST)
Inode Modified: 2012-12-30 11:28:54 (CST)
Direct Blocks:
127754
由上面的命令结果可以看到,Inode位于节点上块组51上,留意上面命令最下面有Direct Blocks这一行,这一行写着127754。在ext4的文件系统中,由于direct block映射的块寻址机制被取代,而采取的是extent区段树的块寻址。这个地方的值基本上是无效的。127754这个值十六进制表示为0x1f30a,我们在稍后的讨论这个值的来源。
我们知道了math.c这个文件的inode号码为420402,那么怎样知道它数据块是拿一个呢?
由前面的内容我们知道,每个块可以存16个inode,那么420402则在第420402/16=26275.125个块中,也就是位于第26275个块的第二个位置。每个块组有508个inode块,那么26725/508=51.72可以得知,位于块组51号之中,这个值可以在我们之前istat中可以验证。
那么具体是51块组中的哪个块呢?我们先确定这个inode块是在块组中的第几个块。因为每个块组有508个inode块,51块组前面共有51*508=25908个块。第26275个inode块在51块组中排在26275-25908=367的位置。查看51块组的描述:
Group 51: (Blocks 1671168-1703935) [ITABLE_ZEROED]
Checksum 0x5ffd, unused inodes 0
Block bitmap at 1572867 (+4294868995), Inode bitmap at 1572883 (+4294869011)
Inode table at 1574420-1574927 (+4294870548)
34 free blocks, 1 free inodes, 541 directories
Free blocks: 1672899, 1673339, 1673344, 1674035, 1674054, 1674062, 1674077, 16
74334, 1674353-1674354, 1674423, 1675259, 1675754-1675755, 1675763, 1675860-1675
861, 1675867, 1675979, 1676183, 1676287, 1676367, 1676507, 1676526-1676527, 1676
567, 1676711, 1676743, 1676924-1676925, 1676934, 1691649, 1691658, 1691707
Free inodes: 422608
看到inode的起点位于1574420,由此,我们想要找的inode信息的块就存在于1574787inode块中的第二个。
可以使用blkcat查看1574787的内容,我们用vi切换到16进制模式打开如下:
0000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000080: 1c00 0000 4430 c1a7 c8f1 733d fc8a 6cb1 ....D0....s=..l.
0000090: 58a8 b04f 04a0 6538 0000 0000 0000 02ea X..O..e8........
00000a0: 0706 3c00 0000 0000 2200 0000 0000 0000 ..<.....".......
00000b0: 7365 6c69 6e75 7800 0000 0000 0000 0000 selinux.........
00000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000d0: 0000 0000 0000 0000 0000 0000 7379 7374 ............syst
00000e0: 656d 5f75 3a6f 626a 6563 745f 723a 6164 em_u:object_r:ad
00000f0: 6d69 6e5f 686f 6d65 5f74 3a73 3000 0000 min_home_t:s0...
0000100: a481 0000 c505 0000 1fd1 e750 f6b4 df50 ...........P...P
0000110: b2cd 3150 0000 0000 0000 0100 0800 0000 ..1P............
0000120: 0000 0800 0100 0000 0af3 0100 0400 0000 ................
0000130: 0000 0000 0000 0000 0100 0000 d64a 1c00 .............J..
0000140: 0000 0000 0000 0000 0000 0000 0000 0000 ................
我们知道inode的size大小为256字节,那么第二个inode的起始位置也就是256=0x100处。
这个时候,我们看下inode的数据结构:
位置
值
名称
描述
0x0
__le16
i_mode
文件模式
0x2
__le16
i_uid
所有者UID.
0x4
__le32
i_size_lo
文件大小.
0x8
__le32
i_atime
读取时间.
0xC
__le32
i_ctime
Inode修改时间
0x10
__le32
i_mtime
文件修改时间.
0x14
__le32
i_dtime
删除时间
0x18
__le16
i_gid
GID.
0x1A
__le16
i_links_count
硬链接计数.
0x1C
__le32
i_blocks_lo
块计数(512字节)
0x20
__le32
i_flags
文件标识(ext4使用extent需要标记0x80000)
...
0x28
__le32
i_block[EXT4_N_BLOCKS=15]
块映射(ext2/3)或区段树(ext4)
...
我们按照表中的结构,对照上面的块码:
偏移
大小
名称
描述
0x0
0x81a4
i_mode
文件模式
0x2
0x0000
i_uid
所有者UID.
0x4
0x0000 05c5
i_size_lo
文件大小.
0x8
0x50e7 d11f
i_atime
读取时间.
0xC
0x50df b4f6
i_ctime
Inode修改时间
0x10
0x5031 b2cd
i_mtime
文件修改时间.
0x14
0x0000 0000
i_dtime
删除时间
0x18
0x0000
i_gid
GID.
0x1A
0x0001
i_links_count
硬链接计数.
0x1C
0x0000 0008
i_blocks_lo
块计数(512字节)
0x20
0x0080 0000
i_flags
文件标识(ext4使用extent需要标记0x80000)
...
0x28
...
i_block[EXT4_N_BLOCKS=15]
块映射(ext2/3)或区段树(ext4)
...
细心的同学会发现大小的顺序是倒过来的,这是因为__lexx类型,le是little endian小端开始的缩写,意思就是从小到大的顺序。我们看到文件的大小为0x5c5=1477,这说明我们找的正是math.c的inode。
因为ext4 使用区段去代替了块映射去查找文件的内容。从40-99这60个字节过去是用作块映射的,如今用作存储extent信息。extent结构体有12字节的大小,反应快的同学马上会说,那么一个inode可以存放最多5个extent。然而这是不对的,因为前12个字节(40-51)被段头(extent header)所占据,所以,一个inode中的区段数最多只能是4。
现在,我们重点开始讲区段树(extent tree)
在ext4中,区段树取代了旧式的逻辑块映射。这是因为在老的模式中,分配连续的1000个块需要映射这1000个块的地址。但使用了区段,只需要映射一个区段并把区段的长度标记为1000(ee_len=1000)。如果起用了flex_bg的功能,一个区段可以分配一个很大的文件,这降低了元数据的大小,也提高了硬盘的效率。inode必须使用区段标记0x80000开启区段的功能。
区段的结构是树形的。每个树节点的起始为:struct ext4_extent_header
(这是一个结构体,我们接下来会给大家描述它的内容)。如果一个节点是树的内部节点(即eh.eh_depth>0),那么eh.eh_entries的指针将指向struct ext4_extent_idx;每个这些索引都指向一个块,块中包含更多的区段树中的节点。如果节点是树的叶子节点(eh.eh_depth=0),那么eh.eh_entries的指针将指向struct ext4_extent;这些结构体中指向文件的数据块。区段树的根节点存在inode.i_block,也就是我们在前面讨论的从40-99的那60个字节里。
说了这么多,我们还是赶紧看看extent的结构吧;
首先出场的是段头(extent header)
偏移
大小
名称
描述
0x0
__le16
eh_magic
幻数magic number, 0xF30A.
0x2
__le16
eh_entries
区段数.
0x4
__le16
eh_max
最大的区段数.
0x6
__le16
eh_depth
段节点在段树中的深度。0则表示为叶子节点,指向数据块;否则指向其它段节点。
0x8
__le32
eh_generation
暂不讨论
同样的,对照我们的实际数据看看
偏移
大小
名称
描述
0x0
0xf30a
eh_magic
幻数magic number, 0xF30A.
0x2
0x0001
eh_entries
区段数.
0x4
0x0004
eh_max
最大的区段数.
0x6
0x0000
eh_depth
段节点在段树中的深度。0则表示为叶子节点,指向数据块;否则指向其它段节点。
0x8
0x0000 0000
eh_generation
暂不讨论
接下来我们先看struct ext4_extent_idx
,这个结构在前面我们有提到过,用于extent树的内部节点。
偏移
大小
名称
描述
0x0
__le32
ei_block
逻辑块号.
0x4
__le32
ei_leaf_lo
区段树中下一层的区段节点块地址(低32位),可以指向叶子节点或者内部节点。
0x8
__le16
ei_leaf_hi
上一栏的高16位地址
0xA
__u16
ei_unused
未使用
我们接着看struct ext4_extent,叶子节点的结构体
偏移
大小
名称
描述
0x0
__le32
ee_block
此区段的第一个块号,起始块号
0x4
__le16
ee_len
区段内包含的块数.
0x6
__le16
ee_start_hi
此区段所指向的块号(高16位)
0x8
__le32
ee_start_lo
此区段所指向的块号(低32位)
对照我们的实际数据看看
偏移
大小
名称
描述
0x0
0x0000 0000
ee_block
此区段的第一个块号,起始块号
0x4
0x0001
ee_len
区段内包含的块数.
0x6
0x0000
ee_start_hi
此区段所指向的块号(高16位)
0x8
0x001c 4ad6
ee_start_lo
此区段所指向的块号(低32位)
由上表可以看到,因为我们的文件较小,这里作为叶子节点直接指向了文件数据块。数据块号为0x001c4ad6=1854166。我们使用命令查看块中的内容:
[root@localhost Desktop]# blkcat /dev/mapper/VolGroup-lv_root 1854166
#include <stdlib.h>
#include <math.h>
...
呵呵,可以看到,这就是我们的math.c文件。
思考:
请读者找一个大于4k的文件,看看能不能找到它的数据块。
删除文件
执行rm后删除文件,数据块并没有被清除,inode被释放,有下面3项会改变: 1. 文件大小被置为0
2. 段头中的区段值被设为0
3. 区段被清空
清空了区段意味着我们会失去文件起始物理块的地址和区段的长度。也就是说,在inode中已经不存在元数据可以帮我们恢复文件。这种行为和ext3回收inode时清除inode中的块指针很相似。这样就意味着我们只能靠传统的file-carving去恢复文件。
参考资料:
http://lxr.linux.no/linux+v2.6.32.60/fs/ext4/ext4.h
https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
http://computer-forensics.sans.org/blog/2010/12/20/digital-forensics-understanding-ext4-part-1-extents
- 深入理解ext4(一)----extent区段
- 深入理解ext4(二)-- 区段树 ( extent tree )
- 理解EXT4,第三部分 extent树
- 深入理解ext4
- 深入理解ext4
- 深入理解ext4文件系统
- ext4的Extent解析
- ext4的extent解析
- [ext4] 磁盘布局 - extent tree
- Linux.ext4文件系统.inode和extent
- 深入理解REST(一)
- 深入理解字符串(一)
- 深入理解缓冲区(一)
- 《深入理解计算机系统》(一)
- GCD 深入理解(一)
- GCD 深入理解(一)
- GCD 深入理解(一)
- 深入理解 GCD(一)
- 第五部分:IDropTarget实现
- 子衿的事(73)
- android webview实现文本选择
- 获取本进程所在目录
- ASP.NET和PHP全面对比
- 深入理解ext4(一)----extent区段
- 删除文件到回收站
- POJ 3096 Surprising Strings
- 验证IP地址格式是否正确的js函数
- 创建进程的三种方式
- 全文索引的使用(二)--使用同义词库
- 解决“使用驱动器F中的光盘前需要将其格式化 双击后提示:文件目录损坏且无法读取”问题
- 【web开发】两个spring mvc配置的问题(maven project)
- 把Proactor与Reactor事件集成的演示代码