转载:http://blog.csdn.net/stringnewname/article/details/73740155
- 最近在看ext4系统的extent相关内容
- 对于文件系统,每个文件会对应一系列磁盘块,通过在inode中有序的存放磁盘块号,也就保存下了<文件逻辑块号, 磁盘块号>的映射关系
- 一个文件的逻辑块号必然是连续的,而磁盘块号则不必连续
- 通常一个block大小为4KB,所以一个比较大的文件,就需要存相当多的块号——而这是一个十分笨拙的办法
- 对于很大的文件,有一种解决办法就是间接存放块号,也就是说inode中有部分块号指向的block不是存放这个file的数据,而是存放块号——即一种间接寻址的逻辑。通常会包括三级,一部分直接指向文件的数据块,一部分指向存有块号的块,一部分指向存有“存有块号的块的块号”的块,哈哈
- 而ext4采用的办法是使用extent来保存<文件逻辑块号, 磁盘块号>的映射关系:一个extent对应一系列连续的块号,故可以想到,一个extent最基本的几个域有——文件逻辑块号,起始磁盘块号,块数量
- ext4中一个inode可以直接存放4个extent
- 对于很大的文件,ext4采用extent_tree的方式,其本质同样是一种间接寻址的关系
- 那接下来就详细道来
术语简介
- 磁盘块:块设备对磁盘的一个抽象,对于文件系统而言,磁盘就是一个一个连续的块,每个块通常大小为4KB,并且磁盘块有序编码,每个块对应有一个磁盘块号
- 文件逻辑块号:从逻辑上讲,一个文件可以看做一系列连续的数据块,每个数据块的大小和磁盘块大小相同;
- 从物理上讲,一个文件可以对应磁盘上若干个磁盘块,这些磁盘块可以在物理上不连续。
- 逻辑快号和磁盘块号的对应关系,很像虚拟地址和物理地址的关系:文件给你的感觉就是连续的数据,也就是连续的逻辑块,但实际上文件对应的磁盘块是可以不连续的,也就是不连续的物理块
- inode:保存文件的元数据,元数据可以描述一个文件
- 很可惜inode里没有一下子就能想到的filename。关于filename下一节会讲到
- 一个inode最基本也最重要的信息就两个
- 用来标识inode的inode号,每个inode都不一样
- 用来指明这个文件对应着哪些磁盘块的信息——即逻辑块号和磁盘块号的对应关系
从文件名到磁盘块
- 前面有提到,inode存放文件元数据,但是并没有存放filename——那么ext4是如何把一个filename和一个inode绑定在一起呢?
- 也就是说存在一个filename和inode号之间的对应关系,而这个关系也是存放在一个文件里——目录文件。如根目录
/
就是一个文件,这个文件也对应一个inode,文件的数据就是根目录下的文件名和对应的inode号
简略的过程
- 基于文件系统,我们看到的就是一个个文件,比如我现在有一个文件
/home/niugen/testfile
- 1
- 2
- 3
- 4
- 5
- 当我们输入
cat testfile
时,cat命令接收到testfile
参数,进而根据当前工作目录计算出这个文件的绝对路径为/home/niugen/testfile
- 解析这个路径,首先是
/
即根目录,根目录这个文件对应的inode号固定为2,所以可以直接找到根目录的inode - 根据根目录的inode中存放的磁盘块号信息,可以知道数据存放在哪些磁盘块中,于是从这些磁盘块里读出数据
- 目录文件的数据,简单的看来就是一个表,有两列,一列是文件名,一列是对应的inode号。对于根目录,文件名就是常见的
dev、usr、home
等等,于是找到了home
对应的inode号 - 于是读出了
/home
这个文件的inode,发现这也是一个目录文件,继续读出数据,找到niugen
对应的inode号 - 于是读出了
/home/niugen
这个文件的inode,发现这还是一个目录文件,继续读出数据,找到testfile
对应的inode号 - 于是读出了
/home/niugen/testfile
这个文件的inode,发现这是一个普通文件,可以使用cat命令,于是读出数据并打印在屏幕上
实际的过程
- 以上过程也就是核心部分了
- 关于根目录的inode号——Why is root directory always stored in inode two?
- 实际中不可能对于每个路径都要读取路径上所有的目录文件来层层深入最终找到目标文件,故在内存中会有目录文件的缓存,称为DEntry。这个数据结构是内存数据结构,用来存放一个文件路径(如/home)和其对应的数据信息(文件名和inode号的表)。
- Linux对文件系统有一个统一的抽象模型,即VFS,主要有四种数据结构:
- 超级快对象 Superblock:文件系统信息
- 索引节点对象 Inode:文件元数据
- 目录项对象 DEntry:提供目录缓存
- 文件对象 File:进程视角,用来描述打开的文件
- VFS的这四种数据结构都是内存数据结构
- 对于具体的文件系统比如Ext4,在挂载的时候根据其磁盘布局来构造出这四种数据结构
- 应用程序(如cat命令)均基于VFS提供的文件操作接口进行编程,每种具体的文件系统的实现,如ext4、fat32、ntfs等,需要对其特有的磁盘数据结构进行封装来实现VFS的这四种数据结构
- VFS不是本文重点,本文重点是探索EXT4文件系统的磁盘数据结构的实现
inode结构体
- 数据类型介绍
- __le16 字节小端序的2字节大小
- __le32 字节小端序的4字节大小
Ext2中的inode
- 首先介绍一下ext2中的inode结构体:ext2_inode
- 最重要的字段:
i_block[EXT2_N_BLOCKS]
,存放磁盘块号 - ext2_inode的大小为128字节,一个4KB的块可以存放32个inode
- 最重要的字段:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
i_block
字段是一个有EXT2_N_BLOCKS
个元素的数组,若EXT2_N_BLOCKS
的默认值为15
- [0,11]为直接寻址,即i_block[0,11]直接存放磁盘块号
- [12,13,14]均为简介寻址,[12]指向一个存放磁盘块号的块,[13]为二级指针,[14]为三级指针
- 若一个block的大小为4KB,直接寻址的范围为48KB,即只使用[0,11];若加上一次间接的[12],则为4.04MB;再加上二次间接的[13]则为4GB;再加上三次间接的[13]则约为4TB
Ext4中的inode
- ext4中就出现了extent,
ext4_inode
定义于/fs/ext4/ext4.h
,ext4_inode
的大小为256字节,一个4KB的块可以保存16个inode
- 字段
i_block[EXT4_N_BLOCKS]
- 字段
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 可见字段
i_block
的大小为15个字节,即EXT4_N_BLOCKS=15
- 前6个字节为extent头,为extent的基本信息
- 后24个字节可以保存4个extent节点,每个extent节点为6字节大小
- extent以树的形式组织,叶节点和非页节点的大小均6字节
- 叶节点即直接保存了文件逻辑块号、起始磁盘块号、块数
- 非叶节点同样具有文件逻辑块号,后面内容指向了一个磁盘块号,有两个字节未使用
- extent相关结构体定义于
/fs/ext4/ext4_extents.h
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
磁盘布局
- 这里简单的认识一下ext2的磁盘布局,ext4在ext2之上发展起来,基本概念相通
概念
- 引导块:磁盘的第一个块,ext2对其不管理,其余的块以块组的形式管理
- 磁盘布局如下
引导块 | 块组0 | … | 块组n |
---|---|---|---|
第1个块 | m个块 | … | m个块 |
-
每个块组有如下内容
- 超级块:1个块
- 组描述符:n个块
- 数据块bitmap:1个块
- 索引节点bitmap:1个块
- 索引节点表:n个块
- 数据块:n个块
-
其中超级块和组描述符,对于所有块组均相同,且总是缓存在内存中
- 其余则用于描述本块组管理的inode块和数据块
Ext4中的磁盘数据结构
- 超级块
superblock
实在太大,不值得搬上来,源码见此ext4_sb.h - 组描述符
ext4_group_desc
如下
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
寻找一个文件
- 那么接下来让我们从磁盘上寻找一个文件的内容,还记得文章开头部分新建的一个文件么,即
testfile
- 1
- 2
- 3
- 4
- 5
找到文件的inode号
cat
命令可以打印一个普通文件的内容,那么对于目录文件就是常见的ls
命令,使用ls -i
可以打印出相应的inode号
- 1
- 2
根据inode号打印出inode内容
- 首先确认一下这个文件所在的设备
df
命令列出挂载的设备,可知/home
对应设备/dev/sda3
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
istat
命令可以打印出某个设备上的某个inode信息
- Group:320表示这个inode存在于块组320上
- size:38表示文件的大小为38个字节
- Direct Blocks:10622354则是这个命令根据i_block解析出来的磁盘快号
- 那么我们不直接使用这个块号,我们自己来算一算它,这就需要直接查看inode所在的磁盘数据块才能看到完整的信息
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
查找inode所在的磁盘块
-
根据前一步可知这个inode位于块组320中,inode号为2629310
-
fsstat
列出文件系统的信息,找到320块组的信息- 该块组管理的inode的inode号范围为2621441 - 2629632,可知包含了2629310这个inode
- 该块组的inode所在的块的块号范围为10485792 - 10486303
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 那么我们可以来算一下:一个inode大小256B,一个块4KB
- 一个块可以容纳4KB/256B=16个inode
- inode号范围起始为2621441,2629310号inode为第2629310-2621441=7869个inode(从0计数)
- 7869/16=491余13,即这个inode位于该块组用来存放inode的磁盘块的第491个磁盘块(从0计数),且位于该块的16个inode中的第13+1个(从1计数)
- inode所在块的起始块号为10485792,所以该inode位于块号为10485792+491=10486283的磁盘块
读取磁盘块内容分析inode
blkcat
命令读出一个磁盘块的内容
- 1
- 使用十六进制编辑器
hexedit
查看内容数据,其中该inode是这个块的第14个inode
- 根据ext4_inode结构体的定义,以及小端字节序
- 字节4-7为文件大小: 0x00000026=38
- 字节40-51为extent头信息
- 字节40-41为魔数:0xF30A
- 字节42-43为extent数量:0x0001=1
- 字节44-45为extent最大数量:0x0004=4
- 字节52-63为第一个extent,也是唯一一个extent
- 字节52-55为该extent对应的第一个文件逻辑块号:0x00000000=0
- 字节56-57为该extent对应的块数量:0x0001=1
- 字节78-59为磁盘块号的高16位:0x0000
- 字节60-63为磁盘快号的低32位:0x00A21592
- 故该extent对应的磁盘块号为0x000000A21592=10622354,和之前
istat
告诉我们的Direct Blocks
一样~~~
读取文件数据所在的磁盘块
- 终于找到头了(虽然
istat
的Direct Blocks
已经告诉我们了)
- 1
- 2
拓展
- 有兴趣的可以再研究一下extent_tree,即使用extent如何实现间接的寻址
- 其实有了结构体的定义已经很清楚了