ExtX系列文件系统的结构学习

一、概述:

现如今在Linux上主要流行的ExtX系列文件系统主要是Ext2、Ext3、Ext4,如它们名字中的数字排列一样:

The Second Extended File System(Ext2)文件系统是早期Linux系统中的标准文件系统,是通过对Minix的文件系统进行扩展而得到的Ext3一种日志文件系统,是对Ext2系统的扩展它兼容Ext2,而Ext4又是Ext3的扩展,但它存储数据的结构发生了很大变化,直接导致了支持 1EB(1,048,576TB, 1EB=1024PB, 1PB=1024TB)的文件系统,以及 16TB 的文件。08年底发布的 2.6.28 Linux kernel开始正式支持新的文件系统 Ext4

二、组织结构:

1、总体布局:

Ext2Ext3中,存储数据的结构是按“块”来组织的,而不是“扇区”。块的大小随文件系统的大小而有所不同(一般是512字节、1024字节等。而一定数量的块又组成一个块组,每个块组的起始部分有多种多样的描述该块组各种属性的元数据结构(磁盘布局结构):

wps_clip_image-15668

每一个分区的前两个扇区存放系统启动用的信息等,所以真正的文件系统是从第三个扇区开始算的。其实主要的结构包括超级块(并不是每一个组都有一个超级块,因为太浪费,系统一般用的也就是第一个而已,不过会在其他组做备份(稀疏超级块机制)),块组描述符(如其名字描述整个块组信息的,包括节点位图、节点表的位置等),块位图(记录空闲块和使用块等信息),节点位图(记录节点使用和空闲情况),节点表(一个节点标识一个文件,记录文件所有信息)和数据区(目录的数据区的内容是各个目录项,文件的数据区就是文件内容,所以目录、文件的存储方式都一样,在linux上一切皆文件

2、文件存放原理:

 ExtX系列文件系统中,文件由inode(节点,包含有文件的所有信息)进行唯一标识。一个文件可能对应多个文件名,只有在所有文件名都被删除后,该文件才会被删除。Ext2Ext3文件系统采用三级间接块来存储数据块指针,并以块为单位分配空间。(等在下面看到inode的数据结构体里面的各项成员就更加清楚了)。

 如下图所示DirectBlocks里存的就是一个块号,那个块里就是数据区了,Indirect blocks就是间接指针的意思 ,doubleindirect就是二级间接,triple  indirect就是三级间接了···(这样设计也是想多存点数据,但跟Ext4比起来还是不够合理)

wps_clip_image-25012

3、主要数据结构(一般定义在/usr/include/linux/ext2_fs.h文件中Ext2和Ext3结构基本一样)):

(1)超级块:

超级块的起始位置始终为其所在分区的第1024个字节(偏移两个扇区)。
struct ext2_super_block {
__le32 s_inodes_count;// 文件系统中inode的总数
__le32 s_blocks_count;// 文件系统中块的总数
·············
__le32 s_log_block_size;// 用以计算块的大小(1024算术左移该值即为块大小)
__le32 s_blocks_per_group;// 每个块组中块的总数
__le32 s_inodes_per_group;// 每个块组中inode的总数
···············

__le16 s_magic;// 文件系统标志,ext2中为0xEF53
···············
};

(2)块组描述符(每条描述符占32个字节):
struct ext2_group_desc
{
__le32 bg_block_bitmap;// 块位图所在的第一个块的块ID
__le32 bg_inode_bitmap;// inode位图所在的第一个块的块ID
__le32 bg_inode_table;// inode表所在的第一个块的块ID
__le16 bg_free_blocks_count;// 块组中未使用的块数
__le16 bg_free_inodes_count;// 块组中未使用的inode数

__le16 bg_pad;
__le32 bg_reserved[3];

};
3inode表
inode表用于跟踪定位每个文件,包括位置、大小等(但不包括文件名),一个块组只有一个inode表。每一个inode都对应一个文件,一个inode的结构如下:
struct ext2_inode {
__le16 i_mode; // 文件格式和访问权限
__le16 i_uid;// 文件所有者ID的低16位
__le32 i_size;// 文件字节数
__le32 i_atime;// 文件上次被访问的时间
__le32 i_ctime;// 文件创建时间
__le32 i_mtime;// 文件被修改的时间
__le32 i_dtime;// 文件被删除的时间(如果存在则为0)
__le16 i_gid;// 文件所有组ID的低16位
__le16 i_links_count;// 此inode被连接的次数
__le32 i_blocks;// 文件已使用和保留的总块数(以512B为单位)
__le32 i_flags;// 此inode访问数据时ext2的实现方式
union {
struct {
__le32 l_i_reserved1;// 保留
} linux1;
struct {
__le32 h_i_translator;// “翻译者”标签
} hurd1;
struct {
__le32 m_i_reserved1;// 保留
} masix1;
} osd1;// 操作系统相关数据
__le32 i_block[EXT2_N_BLOCKS];// 定位存储文件的块的数组,前12个为块号,第13个为一级间接块号,第14个为二级间接块号,第15个为三级间接块号
__le32 i_generation;// 用于NFS的文件版本
__le32 i_file_acl;// 包含扩展属性的块号,老版本中为0
__le32 i_dir_acl;// 表示文件的“High Size”,老版本中为0
__le32 i_faddr;// 文件最后一个段的地址
union {
struct {
__u8 l_i_frag;// 段号
__u8 l_i_fsize;// 段大小
__u16 i_pad1;
__le16 l_i_uid_high;// 文件所有者ID的高16位
__le16 l_i_gid_high;// 文件所有组ID的高16位
__u32 l_i_reserved2;
} linux2;
struct {
__u8 h_i_frag;// 段号
__u8 h_i_fsize;// 段大小
__le16 h_i_mode_high;
__le16 h_i_uid_high;// 文件所有者ID的高16位
__le16 h_i_gid_high;// 文件所有组ID的高16位
__le32 h_i_author;
} hurd2;
struct {
__u8 m_i_frag;// 段号
__u8 m_i_fsize;// 段大小
__u16 m_pad1;
__u32 m_i_reserved2[2];
} masix2;
} osd2; // 操作系统相关数据
};

(5)数据区的目录项结构:

ExtX中根目录总是在inode表的第二项(在编程或者操作时可以充分利用这一特定信息)。

struct ext2_dir_entry_2 {
__le32 inode; // 文件入口的inode号,0表示该项未使用
__le16 rec_len;// 目录项长度
__u8 name_len;// 文件名包含的字符数
__u8 file_type; // 文件类

//以上四项共占8个字节(定长)
char name[255];// 文件名(这是一个数组,根据name_len这一项确定长度,不定长
};

(6)Ext4:

Ext4和Ext2、3的数据存储结构很不一样,它采用了一种叫extent的方式。所谓extent就是描述保存文件数据使用的连续物理块的一段范围。分为头结构、索引结构、叶子结构:

//叶子

struct ext4_extent {

__le32  ee_block;      

__le16  ee_len;        

__le16  ee_start_hi;   

__le32  ee_start;      

};

//索引

struct ext4_extent_idx {

__le32  ei_block;     

__le32  ei_leaf;      

__le16  ei_leaf_hi;   

__u16   ei_unused;

};

  //头结构

struct ext4_extent_header {

__le16  eh_magic;      

__le16  eh_entries;   

__le16  eh_max;        

__le16  eh_depth;      

__le32  eh_generation; 

};

每个 extent 都是一个 ext4_extent 类型的结构,大小为 12 字节。每个 ext4_extent 结构可以表示该文件从ee_block 开始的 ee_len 个数据块,它们在磁盘上的位置是从 ee_start_hi<<32 + ee_start开始,到 ee_start_hi<<32 + ee_start + ee_len – 1 结束,全部都是连续的。

Ext4 文件系统的设计者们采用了一棵extent树结构,其布局如下图所示:

wps_clip_image-19356

在extent树中,节点一共有两类:叶子节点和索引节点。保存文件数据的磁盘块信息全部记录在叶子节点中;而索引节点中则存储了叶子节点的位置和相对顺序。不管是叶子节点还是索引节点,最开始的 12 个字节总是一个 ext4_extent_header 结构,用来标识该数据块中有效项(ext4_extent或 ext4_extent_idx 结构)的个数(eh_entries 域的值),其中 eh_depth 域用来表示它在 extent树中的位置:对于叶子节点来说,该值为 0,之上每层索引节点依次加 1。extent 树的根节点保存在索引节点结构中的 i_block域中,我们知道它是一个大小为 60 字节的数组,最多可以保存一个 ext4_extent_header 结构以及 4 个ext4_extent结构。对于小文件来说,只需要一次寻址就可以获得保存文件数据块的位置;而超出此限制的文件(例如很大的文件、碎片非常多的文件以及稀疏文件)只能通过遍历 extent 树来获得数据块的位置。

为了支持更大的文件系统,Ext4 决定采用48位的块号取代 Ext23原来的32位块号,并采用extent映射来取代ext2、3所采用的间接数据块映射的方法。其实Ext4的节点表、块位图、目录项等原理、结构基本一样,主要包括超级块、组描述符和节点的结构的区别如下:

Ext4超级块:

struct ext4_super_block {

__le32  s_inodes_count; 

__le32  s_blocks_count;   // 文件系统中块的总数 (低32位) 

__le32  s_r_blocks_count;   //保留块的总数(低32位)

__le32  s_free_blocks_count;   //未使用块的总数(低32位) 

__le32  s_free_inodes_count;   

__le32  s_first_data_block;   

__le32  s_log_block_size;    

·······

/* 64bit support valid if EXT4_FEATURE_COMPAT_64BIT */

  __le32  s_blocks_count_hi;     // 文件系统中块的总数 (高32位)  

__le32  s_r_blocks_count_hi;  //保留块的总数(高32位)

__le32  s_free_blocks_count_hi; //未使用块的总数(高32位) 

};

在 ext4_super_block 结构中,增加了3个与此相关的字段:s_blocks_count_hi,s_r_blocks_count_hi,s_free_blocks_count_hi,表示s_blocks_count、s_r_blocks_count、s_free_blocks_count 高 32 位的值,将它们扩充到 64 位。

Ext4块组描述符:

struct ext4_group_desc{

 __le32  bg_block_bitmap;    //块位图(低32位)

__le32  bg_inode_bitmap;     //节点位图(低32位)    

__le32  bg_inode_table;    //节点表(低32位)    

 ····················

__le32  bg_block_bitmap_hi; //块位图(高32位)  

__le32  bg_inode_bitmap_hi;//节点位图(高32位)

__le32  bg_inode_table_hi;//节点表(高32位)

};

Ext4节点:

与Ext2、3不同之处在于最后添加了 5 个与时间有关的字段,这是为了提高时间戳的精度。在 Ext2/Ext3 文件系统中,时间戳的精度只能达到秒级。随着硬件性能的提升,这种精度已经无法区分在同一秒中创建的文件的时间戳差异,这对于对精度要求很高的程序来说是无法接受的。在 Ext4 文件系统中,通过扩充索引节点结构解决了这个问题,可以实现纳秒级的精度。最后两个新增字段 i_crtime 和 i_crtime_extra 用来表示文件的创建时间,这可以用来满足某些应用程序的需求。

struct ext4_inode {

__le16  i_mode;       

__le16  i_uid;         

__le32  i_size;      

__le32  i_atime;      

__le32  i_ctime;      

__le32  i_mtime;     

__le32  i_dtime;     

__le16  i_gid;         

__le16  i_links_count; 

__le32  i_blocks;     

__le32  i_flags;      

__le32  i_block[EXT4_N_BLOCKS];//还是那60个字节

__le32  i_generation;  

__le32  i_file_acl;    

__le32  i_dir_acl;     

__le32  i_faddr;      

__le16  i_extra_isize;

__le16  i_pad1;

 __le32  i_ctime_extra; 

__le32  i_mtime_extra;

__le32  i_atime_extra; 

__le32  i_crtime;     

__le32  i_crtime_extra;

  };

三、注意事项:

(1)在对像/dev/sdaX进行读取操作时应该注意偏移量问题,lseek中那个offset是off_t型,当它是32位时,你的偏移量很大的时候就会放不下,所以应该加 #define _FILE_OFFSET_BITS 64

(2)在对Ext4文件系统进行操作时应该注意它的(/)要用struct ext4_inode取,但是像根下的usr、var、home这些子目录下的内容就要用extent的头、叶子、索引三个结构去取;

主要参考资料:

(1)本文中涉及的相关数据结构体均摘抄于Redhat下/usr/include下头文件

(2)数据重现文件系统原理精解与数据恢复最佳实践.(马林)

(3)ext4 存储原理及特性-如何恢复 Linux 上删除的文件_中文DY豆_cn_dydou_cn.htm(冯锐


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值