Ext2文件系统简单剖析(一)

1.ext2文件系统结构

我们都知道,磁盘时存储文件用的,但是磁盘必须先格式化为某种格式的文件系统,才能存储文件。文件系统的目的就是组织和管理磁盘中的文件。下图为ext2文件系统的存储布局。

ext2文件系统结构概貌.jpeg

磁盘中最小存储单元是扇区(1扇区=512Bytes),而文件系统的最小存储单元就是Block(一般地,1Block = 4KB = 8 扇区)。扇区大小在磁盘出厂就已经定了,而Block大小是在格式化时决定的。例如, mke2fs -b 4096 /dev/sda6 在格式化 /dev/sda6时指定了Block大小为4096Bytes。

在上图中,MBR是主引导记录,它的作用是检查分区表是否正确以及确定哪个分区为引导分区,并在程序结束时把该分区的启动程序调入内存加以执行。MBR后面的分区可以是NTFS,也可以是ext2的。这里我们主要分析ext2文件系统,ext2文件系统由以下几部分组成:

  1. Boot Sector:上图启动块(Boot Sector),是用来存储磁盘分区信息和启动信息,任何文件系统给都不能缺少启动块。启动块大小并不是我们前面所说的4KB,而是1KB,是由PC标准定义的。

  2. Block Group:启动块之后才是ext2文件系统的开始。ext2文件系统将整个分区划分为大小相等的块组(Block Group),每个块组由以下部分组成:

  3. Super Block:超级块主要有两个功能:1)超级块结构给出了文件系统的全局信息。例如块大小,文件系统的版本等等。2)超级块结构包含一些函数指针,例如super_operation的成员函数read_inode提供了读取inode信息的功能。每个具体的文件系统一般都要提供这个函数来实现对inode信息的读取,例如ext2文件系统提供的具体函数是ext2_read_inode。

    struct super_block {
        unsigned long s_blocksize;//指定了文件系统的块大小
        unsigned char s_blocksize_bits;
        ……/*省略超级块的链表、设备号等代码*/
        unsigned long long s_maxbytes; /* 指定文件系统中最大文件的尺寸 */
    
        struct file_system_type *s_type;/*s_type是指向file_system_type结构的指针。*/
        struct super_operations *s_op;
    
        unsigned long s_magic;
        struct dentry *s_root;/*s_root是指向文件系统根dentry的指针。*/
    
        struct list_head s_inodes; /* all inodes */
        struct list_head  s_dirty; /* dirty inodes */
    
        struct block_device *s_bdev;
        void  *s_fs_info; /* Filesystem private info */
     };
    
  4. GDT(Group Descriptor Table),组描述符表。由很多组描述符组成,整个分区分成多少个组就对应有多少个组描述符。每个组描述符(Group Descriptor)存储一个组的描述信息,例如在这个组中从哪里开始是inode表,从哪里开始是数据块,空闲的inode和数据块还有多少个等等。

    从上图可以看出,超级块和组描述符被复制到每个块组中,因为一旦超级块意外损坏就会丢失整个分区的数据,一旦块组描述符意外损坏就会丢失整个块组的数据,因此它们都有多份拷贝。只有块组0中包含的超级块和组描述符才被内核使用,当执行e2fsck检查文件系统一致性时,第0个块组中的超级块和块组描述符表就会拷贝到其它块组,这样当第0个块组的开头意外损坏时就可以用其它拷贝来恢复,从而减少损失。

  5. Block Bitmap,块位图。块位图就是用来描述整个块组中哪些块已用哪些块空闲的,它本身占一个块,其中的每个bit代表本块组中的一个块,这个bit为1表示该块已用,这个bit为0表示该块空闲可用。这里引出两个问题:

    1.为什么用df命令比du命令统计整个磁盘的已用空间非常快呢?
    因为df命令只需要查看每个块组的块位图即可,而不需要搜遍整个分区。相反,用du命令查看一个较大目录的已用空间就非常慢,因为不可避免地要搜遍整个目录的所有文件。

    Linux df命令用于显示目前在Linux系统上的文件系统的磁盘使用情况统计。
    Linux du命令用于显示目录或文件的大小。
    详情可参照:http://www.runoob.com/linux/linux-comm-df.html
    http://www.runoob.com/linux/linux-comm-du.html

    2. 在格式化一个分区时究竟会划出多少个块组呢?
    这主要取决于分区大小和块的大小。我们考虑一个32GB的ext2分区,块大小为4KB,Block Bitmap占一个块,也就是4KB,可以标注4K*8=32K个块,即128MB。因此,一个32GB的ext2分区需要32GB/128MB=256个块组。

  6. inode Bitmap,inode位图,和块位图类似,本身占一个块,其中每个bit表示一个inode是否空闲可用。

  7. inode Table,inode表。一个文件除了数据需要存储之外,一些描述信息也需要存储,例如文件类型(常规、目录、符号链接等),权限,文件大小,创建/修改/访问时间等,也就是ls -l命令看到的那些信息,这些信息存在inode中而不是数据块中。每个文件都有一个inode,一个块组中的所有inode组成了inode表。每个inode占128B。

    inode表占多少个块在格式化时就要决定并写入块组描述符中,mke2fs格式化工具的默认策略是一个块组有多少个8KB就分配多少个inode。

  8. Data Block:数据块。
    对于常规文件的内容存储在数据块中。
    对于目录,使用一个目录项的数据结构存储在数据块中。每个目录项存储了文件的文件名,inode号,文件类型,记录长度。这里引出三个问题:
    1. 要存一个hello的文件,具体步骤是?
    1)内核加载块组0中的GDT,从GDT中找出inode bitmap,从inode bitmap中找出inode table中空闲的inode。
    2)申请一个inode。inode主要包含两部分内容:文件属性(68Bytes),数据块指针(60Bytes)。数据块指针指向存储hello文件目录项和文件内容的Data Block。
    3)将文件内容和文件的目录信息分别存在对应的Data Block中。
    4)修改对应的inode Bitmap 和 Block Bitmap。

    2. 给定文件路径“/home/hello”,操作系统时如何找到该文件的位置?
    1)查找根目录的目录项。Linux有规定,根目录的目录项必须存放在2号inode中。
    2)根目录的目录项中存着根目录下的子目录目录项和文件的数据块信息。通过根目录的目录项可以找到home对应的inode。
    3)根据home对应的inode找到home的目录项。
    4)在home目录项中找到hello文件的inode。
    5)根据hello文件的inode中的数据块指针找到存储有hello文件内容的数据块。

    3. 如何删除hello文件?
    1)找到hello文件位置。
    2)将Block Bitmap中对应bit置为0
    3)将inode Bitmap中对应bit置为0

2.数据块寻址

在第一节里面我们提到,操作系统在查找文件时,会根据inode中数据块指针找到对应的Data Block。本节我们介绍数据块寻址。下图为寻址过程。


上文我们说到一个inode中数据块指针占了60Bytes,其中每一个指针占4字节,一共有60/4=15个指针。从上图可以看出,索引项Blocks[12-13]分别指向一级,二级,三级的间接寻址块。因此对于一个inode来说,最多可存储(12+256+2562+2563)*4KB的数据。

这种寻址方式对于访问不超过12个数据块的小文件是非常快的,访问文件中的任意数据只需要两次读盘操作,一次读inode(也就是读索引项)一次读数据块。而访问大文件中的数据则需要最多五次读盘操作:inode、一级间接寻址块、二级间接寻址块、三级间接寻址块、数据块。实际上,磁盘中的inode和数据块往往已经被内核缓存了,读大文件的效率也不会太低。

推荐阅读更多精彩内容