ext2文件系统

这几天比较忙,一直没更新,现在开始填之前挖的坑,分析一下ext2文件系统,这里主要对文件系统的格式进行分析不会深入到VFS以及代码逻辑,可能要下次重开一篇来对代码层进行分析。

分析文件系统之前先简单介绍一下文件的主要载体:硬盘。硬盘是由多个盘片组成的,盘片上面有磁性物质,当硬盘的磁头通电的时候就会改变磁性物质的方向产生磁极,这个就是磁盘存储的大致原理。为了方便,就将盘片分为很多同心圆,同心圆间存在的圆环就叫做磁道。由圆心向圆周画多条直线,夹在两条直线中的一段弧形就被称作扇区。可以看下面的图片。

扇区的大小一般是512字节,现在也有4k字节大小。文件系统就是基于这样的物理基础来对存储做一定的规划。我们可以想象一下,文件系统就是为了更有效率的寻找和保存数据,那么如果是自己写一个文件系统应该要如何来规划呢?首先应该是要确定固定的大小作为文件存储的单位,既不能太小,比如一个字节,这样会严重影响效率。也不能太大,比如10M字节,这样会造成存储空间的浪费。接下来就是应该有一个地方用来保存所存储的数据的信息,比如保存了一个文件,那么至少有个地方告诉使用者这个文件是保存的位置,以及文件大小,修改时间等信息。另外我们还得记录磁盘里面哪些位置已经用过了,整个磁盘用了多少还有多少可以用等信息。这些信息要保存在哪里?要用多大的空间来保存这些数据?上面这些问题都需要在文件系统中去解决,下面就看看ext2文件系统是如何来处理的。

可以先看上面这幅图,这个是ext2文件系统在磁盘中存储规划。首先有几点要说明,一个是它的存储单位是Block(块),一个Block的大小是1024字节。然后就是这些Block会分组,叫做一个个Block Group,那么一个Group里面有多少个Block,这个是可以设定的。接着就是可以看到每个Block Group里面又可以分为:Super Block、GDT、Block Bitmap、Inode Bitmap、Inode Table、Data Blocks几个部分,这几个部分具体代表的含义以及功能,后面分析。最后就是一个分区最开始都会有个Boot Block(启动块),这个是由PC标准规定的并不是ext2文件系统的规定。文件系统是从Block Group 0开始。

下面对图中一些模块的功能做一下说明:

  • Super Block(超级块)

描述整个分区的信息,比如Block的总数量、可用的Block数量、uuid、Block的大小、上次mount的时间、Inode大小等信息

  • GDT(块组描述符)

用来描述块组(Block Group)的信息,有多少块组就有多少个GDT。主要用来描述对应的块组中,剩余Block的总数量,剩余的Inode的总数量等信息

  • Block Bitmap(块位图)

用来描述哪些块可以用,哪些块已经被使用了,每个bit就代表这个块组中的一个块,如果这个bit为1就表示该块已经被使用,这个bit为0表示该块可用。

  • Inode Bitmap(Inode位图)

inode是用来存储文件的属性信息,比如文件类型、文件大小、修改时间等,也就是通过ls -l命令能够看到的那些信息。和上面的块位图类似,Inode Bitmap就是每个bit表示一个inode是否可用,bit为1表示这个inode已经被使用,bit为0表示该inode可以使用。

  • Inode Table(Inode表)

inode保存的地方,里面保存了所有的inode。

  • Data Block(数据块)

保存了实际的文件数据

为了比较直观的观察上面的这些信息,我们下面来动手做一些实际的操作

# 创建一个磁盘文件
dd if=/dev/zero of=disk.ext2 bs=1k count=2048

# 格式化为ext2
mkfs.ext2 disk.ext2

# 将disk.ext2拷贝一份,并重命名,用于mount和添加文件
cp -f disk.ext2 disk-add-file.ext2

# 创建一个文件hello.txt
touch hello.txt
echo "test file system" > hello.txt

# mount文件系统并将文件拷贝进去
mount disk-add-file.ext2 /mnt
cp -f hello.txt /mnt/
umount /mnt

 接下来,我们先使用dumpe2fs来查看和对比一下disk.ext2和disk-add-file.ext2这两个文件的信息。下面两个图中,上面这个是disk.ext2的信息,下面那个是disk-add-file.ext2的信息,这两个磁盘文件的区别就是后面这个是被mount过,而且里面放进去了一个文件。我们看下图中红框这些部分,主要就是列举出了Inode总数量、Block总数量、可用的Inode数量,可用的Block数量、Inode大小、Block大小、每个group包含的Block和Inode数量以及mount时间等信息。对比一下两个磁盘文件的信息,主要差别就在于一个是可用的Block:disk.ext2可用的Block是1990,disk-add-file.ext2可用的block是1989刚好少一个block,用来保存了hello.txt文件。另外一个差别就是可用的inode数量:disk.ext2可用的inode数量是245,disk-add-file.ext2可用的inode数量是244,同样是因为用来保存hello.txt文件信息了。还有一个比较明显的差别就是文件mount时间和mount次数,这个就不用详细解释了。

 

接下来我们通过对linux内核代码以及磁盘文件二进制分析来更进一步来深入到ext2文件系统中。

ext2.h

struct ext2_super_block {
    __le32  s_inodes_count;     /* inode数目 */
    __le32  s_blocks_count;     /* 块数目 */
    __le32  s_r_blocks_count;   /* 已分配块的数目 */
    __le32  s_free_blocks_count;    /* 空闲块数目 */
    __le32  s_free_inodes_count;    /* 空闲inode数目 */
    __le32  s_first_data_block; /* 第一个数据块*/
    __le32  s_log_block_size;   /* 块长度 实际长度=1<<(s_log_block_size+10) */
    __le32  s_log_frag_size;    /* 碎片长度 */
    __le32  s_blocks_per_group; /* # 每个块组包含的块数 */
    __le32  s_frags_per_group;  /* # 每个块组包含的碎片 */
    __le32  s_inodes_per_group; /* # 每个块组包含的inode数目 */
    __le32  s_mtime;        /* Mount时间 */
    __le32  s_wtime;        /* 写入时间 */
    __le16  s_mnt_count;        /* Mount计数*/
    __le16  s_max_mnt_count;    /* 最大mount计数*/
    __le16  s_magic;        /* 魔数,标记文件系统类型 */
    __le16  s_state;        /* 文件系统状态 */
    __le16  s_errors;       /* 检测到错误时的状态 */
    __le16  s_minor_rev_level;  /* 副修订号*/
    __le32  s_lastcheck;        /* 上一次检查时间 */
    __le32  s_checkinterval;    /* 两次检查允许间隔的最长时间 */
    __le32  s_creator_os;       /* 创建文件系统的OS */
    __le32  s_rev_level;        /* 修订号 */
    __le16  s_def_resuid;       /* 能够使用保留块的默认UID */
    __le16  s_def_resgid;       /* 能够使用保留块的默认GID */
    /*
     * 这些字段只适用于EXT2_DYNAMIC_REV 超级块
     * 请注意:兼容特性集与不兼容特性集的差别在于;如果不兼容特性中某个置位的比特位内核
     * 不了解则应该拒绝装载该文件系统
     * 
     * e2fsck的要求更为严格。如果它不了解某个特性,不管是不是兼容特性
     * 它都必须放弃工作,而不是去尽力弄乱不了解的东西
     */
    __le32  s_first_ino;        /* 第一个非保留的inode */
    __le16   s_inode_size;      /* inode结构的长度 */
    __le16  s_block_group_nr;   /* 当前超级块所在的块组编好*/
    __le32  s_feature_compat;   /* compatible feature set 兼容特性集*/
    __le32  s_feature_incompat;     /* incompatible feature set 不兼容特性集 */
    __le32  s_feature_ro_compat;    /* readonly-compatible feature set 只读兼容特性集 */
    __u8    s_uuid[16];     /* 128-bit uuid for volume 卷的128位uuid */
    char    s_volume_name[16];  /* volume name 卷名*/
    char    s_last_mounted[64];     /* directory where last mounted 上一次装载目录 */
    __le32  s_algorithm_usage_bitmap; /* For compression  用于压缩*/
    /*
     * 性能提示。仅当设置了EXT2_COMPAT_PREALLOC flag标志,才能进行目录的已分配
     */
    __u8    s_prealloc_blocks;  /* Nr of blocks to try to preallocate 试图预分配的块数*/
    __u8    s_prealloc_dir_blocks;  /* Nr to preallocate for dirs 试图为目录预分配的块数 */
    __u16   s_padding1;
    /*
     * 如果设置了EXT3_FEATURE_COMPAT_HAS_JOURNAL,日志支持才是有效的
     */
    __u8    s_journal_uuid[16]; /* uuid of journal superblock */
    __u32   s_journal_inum;     /* inode number of journal file */
    __u32   s_journal_dev;      /* device number of journal file */
    __u32   s_last_orphan;      /* start of list of inodes to delete */
    __u32   s_hash_seed[4];     /* HTREE hash seed */
    __u8    s_def_hash_version; /* Default hash version to use */
    __u8    s_reserved_char_pad;
    __u16   s_reserved_word_pad;
    __le32  s_default_mount_opts;
    __le32  s_first_meta_bg;    /* First metablock block group */
    __u32   s_reserved[190];    /* Padding to the end of the block 填充字节,补齐到块结尾 */
};

 上面是Linux内核中对于super block的代码描述,对照我们的磁盘二进制数据来看。

上面就是对disk-add-file.ext2这个磁盘文件二进制数据的整理,可以看到从0x400也就是1k开始,就是ext2的数据,首先是super block(超级块),那么对照上面Linux内核对于ext2结构体的定义,可以一项一项对应起来。特别要注意的就是因为x86是小端结构,所以二进制数据需要倒过来。

按照前面ext2总体结构,super block接下来就是GDT,先看下linux内核中对于GDT的定义

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];
};

从上面的定义可以看出,一个块组描述符是32byte,同样对照磁盘二进制数据

图上可以看到,只列出了一个块组描述符,这是因为整个磁盘文件是2M,包含了2048个block,而一个block group最多能够容纳8192个block,也就是说整个磁盘只有一个block group,那按照前面说的有多少个block group就有多少个GDT,所以这里就只有一个GDT了。接下来应该就是block bitmap和inode bitmap了,那么它们是从哪个地址开始呢?是不是就是接着GDT从0x820?这里就要看GDT里面的描述了,可以看到block bitmap=0x0a=10,也就是block 10的位置就是保存着block bitmap。inode bitmap=0x0b=11,也就是block 11的位置保存着inode bitmap。inode table=0x0c,也就是block 12的位置保存了inode table。我们知道block 0保存了启动块(boot block),block 1保存着超级块(super block)那么接下来就定位到相关的block中来具体看看。

下面先看下block 10位置的block bitmap,前面我们讲过这里的每个bit就代表一个block,bit为0表示该block可用,为1则表示已经被占用了。先确认一下block 10的偏移地址:10*1024=0x2800

 

可以看到前面57个bit都是1,就表示前57个block都被占用了。接下来看看inode bitmap。它的偏移地址是:11*1024=0x2c00

 

可以看到前面12个bit都是1,就表示前12个inode都已经被使用了。

接下来就要看inode table了,这个比较关键是因为里面都是保存了inode,而inode里面就是文件和文件夹的各种信息,包括保存的位置,大小,时间等等信息。而从super block(超级块)的信息中,我们已经知道一个group glock中有256个inode,也就意味着这个inode table中就保存了256个inode。

先看下Linux内核中对于inode结构的定义:

struct ext2_inode {
         __le16  i_mode;         /* File mode 文件模式*/
         __le16  i_uid;          /* Low 16 bits of Owner Uid 所有者UID的低16为 */
         __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 组ID的低16位*/
         __le16  i_links_count;  /* Links count 链接计数 */
         __le32  i_blocks;       /* Blocks count 块数目*/
         __le32  i_flags;        /* File flags 文件标志*/
         union {
                 struct {
                         __le32  l_i_reserved1;
                 } linux1;
                 struct {
                         __le32  h_i_translator;
                 } hurd1;
                 struct {
                         __le32  m_i_reserved1;
                 } masix1;
         } osd1;                         /* OS dependent 1 */
         __le32  i_block[EXT2_N_BLOCKS];/* Pointers to blocks块指针(块号) */
         __le32  i_generation;   /* File version (for NFS) 文件版本 */
         __le32  i_file_acl;     /* File ACL 文件acl*/
         __le32  i_dir_acl;      /* Directory ACL 目录acl*/
         __le32  i_faddr;        /* Fragment address 碎片地址 */
         union {
                 struct {
                         __u8    l_i_frag;       /* Fragment number 碎片编号 */
                         __u8    l_i_fsize;      /* Fragment size 碎片长度*/
                         __u16   i_pad1;
                         __le16  l_i_uid_high;   /* these 2 fields    */
                         __le16  l_i_gid_high;   /* were reserved2[0] 此前定义为reserved2[0] */
                         __u32   l_i_reserved2;
                 } linux2;
                 struct {
                         __u8    h_i_frag;       /* Fragment number */
                         __u8    h_i_fsize;      /* Fragment size */
                         __le16  h_i_mode_high;
                         __le16  h_i_uid_high;
                         __le16  h_i_gid_high;
                         __le32  h_i_author;
                 } hurd2;
                 struct {
                         __u8    m_i_frag;       /* Fragment number */
                         __u8    m_i_fsize;      /* Fragment size */
                         __u16   m_pad1;
                         __u32   m_i_reserved2[2];
                 } masix2;
         } osd2;                         /* OS dependent 2 */
 };

 在磁盘二进制文件中inode table的偏移地址为:12*1024=0x3000,每个inode的大小是128字节,那么256个inode的总大小就是:256x128=32k字节。通过前面的inode bitmap我们知道总共前面12个inode被使用了,那么就主要看这几个inode的内容。

上面这个就是inode 1的内容,结合上面Linux代码的注释,前面两个表示文件模式,这里的数据是0,意味着什么都不是。所以这个inode没有保存任何文件或者文件夹,是作为一个保留项。接下来看看inode 2的内容,在ext2文件系统中,这个是作为根目录。

 

下面看几个值得关注几个字段,首先是前面2个字节表示文件模式,它的值是:0x41ed,表示什么意思,可以看下面说明:

 

那么0x41ed就表示这是个目录,并且权限是本用户可读、可写、可执行,本组可读、可执行,其它用户可读、可执行。

另外一个值得关注的字段是偏移地址为0x309c的字段,它表示的是这个目录占用的block数量,这里可以看到它的值是2,就是表示占用了2个block也就是2k字节

 

最后一个要关注的字段是偏移地址为0x30a8的字段,它表示的是这个目录从哪个block开始,这里可以看到它的值为0x2c=44,也就是从block 44开始存这个目录的数据

 

接下来我们去block 44去看下这里存储的具体数据。block44的偏移地址是:44*1024=0xb000。上面的inode2里面信息表示,block44存储的是目录,那么目录的格式在Linux中是这样定义的:

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 */
};

 下面对照磁盘文件的二进制数据

红框内就表示存储的一组内容,对照上面Linux的定义,可以看到这组内容是对应了inode 2,然后长度是0x0c,文件名称长度是1,文件类型是2,那么2表示什么,可以看下面定义:

 

可以看到2表示这是个目录,最后看它的文件名是2e,经过ASCII转换就是".",这个在Linux中就表示本目录。接下来看下一组

 

可以看到这组内容依然对应着inode 2,长度也是0x0c,文件名称长度是2,文件类型也是2表示是目录,最后文件名是2e2e,经过ASCII转换就是"..",这个在Linux中表示上一级目录。接下来继续看下一组

 

可以看到这组就不一样了,内容对应的inode为inode 11,长度是0x14,文件名长度是0x0a也就是10,文件类型是2表示为目录,最后看下文件名是:0x6c 0x6f 0x73 0x74 0x2b 0x66 0x6f 0x75 0x6e 0x64,共10个字节,经过ASCII转换就是:lost+found。继续往下看:

 

可以看到这组内容对应的inode为inode12,长度为0x03d4,文件名长度为9,类型为1表示这是个普通文件,接下来文件名是:0x68 0x65 0x6c 0x6c 0x6f 0x2e 0x74 0x78 0x74,经过ASCII转换就是:hello.txt。我们可以看一下磁盘文件内的目录结构是不是和上面分析的一样

先将disk-add-file.ext2挂载到/mnt目录下

mount disk-add-file.ext2 /mnt

 接下来看下mnt目录中的内容,确实和上面分析的是一样的

最后再来看下hello.txt的存储情况,从上面知道它对应的inode为inode12,所以先找到inode12对应的偏移地址,然后从inode的信息中得到hello.txt保存的位置。从前面知道一个inode大小是128字节,inode1的偏移地址是0x3000,那么inode12的偏移地址就是:0x3580。 

这里主要看下文件内容保存的位置,它的偏移地址是0x35a8,值是0x201也就是block513,那么block513的偏移地址就是:513x1024=0x80400

下面看这个偏移地址的数据:

 

将上面的数据ASCII转换就是:test file system。这正是hello.txt文件里面的内容。

到这里ext2文件系统基本就分析完了,之所以使用它来分析主要原因一个是相对于ext3,ext4会简单一些,另外就是ext3和ext4大体框架和ext2也差不多。经过对文件系统格式的分析后,阅读Linux内核代码就比较好理解它的内在逻辑。同时也可以看到在Linux中所有文件都对应着inode,所以即使我们使用rm命令将文件删除了,只要inode还在而且文件存储的位置没有被其它内容占用,同样还是可以恢复的。这也算是磁盘文件恢复的一个原理吧。

最后总结一下,ext2文件系统的最小组织单位是block,大小默认是1024字节,当然也可以设置为其它值。多个block组成就组成了block group,每个group里面又分为Super Block、GDT、Block Bitmap、Inode Bitmap、Inode Table和Data Blocks几个部分。其中Super Block是一个总览,记录了Inode的总数量,Block的总数量,Inode可用的数量,Block可用的数量以及Inode大小,Block大小,mount时间,mount次数等信息。接下来的GDT主要描述各个block group的信息,比如这个block group中block bitmap的位置,inode bitmap的位置,inode table的位置,以及可用的block数量,可用的inode数量等信息。再下面的block bitmap和inode bitmap则描述block的具体使用情况和inode的具体使用情况。接下来的inode table则是由一个个inode组成,里面保存着文件或者目录的详细信息。最后Data Blocks则是真正保存文件或者目录内容的地方。

 下面看下找时间写ext3、ext4有哪些改进的地方,还有就是Linux下对文件系统的具体操作。预计是个比较大的工程。

文章里面使用到的参考资料以及制作的磁盘文件可以在后台回复:"文件系统",获取。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值