zoukankan      html  css  js  c++  java
  • Ext4文件系统架构分析(三)

    ioctl源码分析之交换两个文件的物理extents

    1. 交换两个文件的extents

    Ext4 的EXT4_IOC_MOVE_EXT命令用于交换两个文件的extents,实际上是交换两个文件的对应逻辑长度的数据的物理存储空间(见下图),也是EXT4文件系统碎片整理的基础。

    用户可以通过ioctl函数使用Ext4文件系统的Ioctl命令EXT4_IOC_MOVE_EXT将用户指定交换的两个文件及交换的范围的相关信息struct move_extent的地址传给ioctl的第三个参数unsigned int arg:

     ioctl(fd, EXT4_IOC_GROUP_ADD, arg )

    至于操作是否成功,结果验证可以使用filefrag命令,分别在执行转移前后使用该命令获取文件的区段信息,就可以判断是否执行的转移(交换)操作。

    2. EXT4_IOC_MOVE_EXT交换文件extents的限制

    利用Ext4 的EXT4_IOC_MOVE_EXT交换两个文件的extents的操作受到以下限制(这里将进行转移的文件称为源文件,参与转移操作正确执行的另一文件称为donor文件):

    1. 文件自身的限制:

    1)源文件与donor文件应该是不同的文件;

    2)源文件与donor文件应该是常规文件;

    2. 交换区段的参数限制(即调用mext_check_arguments()函数检查struct move_extent的元素数值是否有效):

    1)donor文件都不能设置SUID或SGID;

    2)两个文件都不能支持swapfile,也就是两个文件都不能是swapfile;

    3)两个文件应当在同一个Ext4文件系统中;

    4)两个文件都要是基于extent的;

    5)两个文件都不能是0长度的;

    6)交换的(逻辑)起始偏移应该相等;

    7)两个文件的交换起始偏移块号、交换的数据块的个数以及转移的最后一个数据块的块号都不能超过一个文件的最大逻辑块(2^32 -1),这是因为Ext4文件系统支持的最大文件为16TB;

    8)当源文件长度大于donor文件长度时:

    a) 如果起始数据块大于等于donor文件的所有数据块个数,则退出;

    b) 如果起始数据块加上转移数据块的个数得到的逻辑数据块号大于donor文件的最后一个逻辑数据块号,则需要对转移数据块的个数进行修改,新的转移长度为donor文件的长度减去起始逻辑数据块号;

    9)当源文件长度小于等于donor文件长度时:

    a) 如果起始数据块大于等于源文件的所有数据块个数,则退出;

    b) 如果起始数据块加上转移数据块的个数得到的逻辑数据块号大于源文件的最后一个逻辑数据块号,则需要对转移数据块的个数进行修改,新的转移长度为源文件的长度减去起始逻辑数据块号;

    10)经过以上检查后还有确定转移的数据块的个数不能为0;

    3.  交换文件的extents的操作过程

    1. 首先打开的文件不是用于读或写,那么这个描述符无效,则不对文件进行extents交换;

    2. 将用户设置的转移信息struct move_extent拷贝到内核空间;

    3. 设置已转移(交换)的数据块的数据块的个数为0;

    4. 获取donor文件的描述符,然后确认donor文件以可写的方式打开;

    5. 获取对文件系统的写权限;

    6. 调用ext4_move_extents()函数,转移(交换)文件指定范围的数据的物理存储位置。该函数的操作流程如下:

    1)判断文件自身是否受限制(标准见上一小节文件自身的限制);

    2)获取源文件与donor文件的i_mutex锁,防止操作过程中文件被截断;

    3)按inode号顺序获取两个inode的 i_data_sem写锁,防止extent tree被delalloc破坏;

    4)调用mext_check_arguments()函数检查交换的两个文件及其指定的参数能否满足move extent的条件(步骤参见上一小节交换区段的参数限制);

    5)先获取源文件的最后一个数据块的逻辑块号,然后获取源文件的待转移的extents中的最后一个数据块的逻辑块号;如果文件的最后一个数据块逻辑号小于待转移的extent中的最后一个数据块的逻辑块号,则重新修正转移长度为起始偏移到文件末尾;

    6)为交换指定的起始逻辑块号block_start寻找一个extent path,也就是建立对这个新的extent的路径索引,主要通过调用get_ext_path()函数实现;

    7)为交换指定的起始逻辑块号block_start寻找一个extent path以检查文件空洞情况;

    8)获取源文件的extent树的深度;

    9)根据指定的转移起始块是否在文件的hole中的情况,修正转移起始数据块;

    10)判断以当前extent的起始块的extent是否有数据块位于待转移的数据块范围内(这种情况指定的范围可能是一个文件的空洞),不在则退出;

    11)进行extent的转移(交换):

    a)计算当前可转移(交换)的数据块的个数;

    b)以当前extent为起点,首先修正当前extent中可被转移的数据块个数;

    c)设置当前extent为前一个extent(ext_prev),然后确定下一个待转移的extent作为新的当前extent(ext_cur),计算下一个待转移的extent中包含的数据块的个数;

    d)然后判断当前extent是否可以与前一个extent合并,可以的话,跳到b)继续执行,否则进入下一步;

    e)判断当前extent的前一个extent是否未初始化(如果extent的长度大于一个块组的长度则被认为是未初始化的extent);

    f)转移(交换)前一个extent中的数据块,首先释放两个inode的i_data_sem写锁,然后反复调用move_extent_per_page()函数,每次转移一个page中的extent中的数据。move_extent_per_page()函数的主要操作是通过调用mext_replace_branches()函数,首先保存源inode的数据块,然后使用donor inode的相应extents 替换源inode的extents,最后,将保存的源inode的数据写到源inode的新的数据块中,并返回替换的数据块的个数;mext_replace_branches()函数一页一页地使用donor inode的extents替换源inode的extents;按以下三步实现这种替换:

    i  保存原始inode及donor inode的数据块信息到dummy extents中;

    ii  改变源inode的数据块信息指向donor文件的数据块;

    iii  改变donor inode的数据块信息指向保存在dummy extents中源inode的数据块的信息.

    g)获取两个inode的 i_data_sem写锁;

    h)获取下一个转移的extent;获取成功,则跳到b)继续执行;否则,表示需要转移的数据块都已转移完毕。

    12)资源释放:

    a)两次调用ext4_discard_preallocations()函数分别释放预分配给源inode和donor inode的数据块;

    b)释放两个extent路径orig_path与holecheck_path占用的空间;

    c)释放两个inode的i_data_sem写锁;

     

    13)释放两个inode上的i_mutex 锁,转移(交换)操作结束;

    7. 结束对文件系统的写操作;

    8. 判断转移(交换)的数据块的个数是否不为0(不为0表示确实发生了数据存储位置的转移),如果不为0,那么移除donor文件的SUID (Set User ID);

    9. 返回内核空间的struct move_extent信息到用户空间;

    10. 释放donor文件描述符,转移操作结束,返回。

    ioctl源码分析之强制立即分配延迟分配给文件的数据块

    Ext4文件系统支持强制立即分配延迟分配给文件的数据块,本质上是强制立即进行数据同步,也就是立即刷新文件的写缓冲。在Ext4文件系统中,写文件产生新数据时,文件系统并不立即分配数据块存储这些新数据(脏数据),而是延时等待到不得不写数据到磁盘为止,才会为新数据分配磁盘空间。Ext4文件系统的Ioctl命令EXT4_IOC_ALLOC_DA_BLKS用于为单个文件破解这种延迟分配的方式,也就是与该文件相关操作产生的新数据,文件系统立即为其分配空间,不再等待以延时分配。使用以下命令就可实现调用EXT4_IOC_ALLOC_DA_BLKS命令为文件立即分配数据块:

    ioctl(fd, EXT4_IOC_ALLOC_DA_BLKS, NULL)

          ioctl 命令成功执行后。其中与 fd 所引用的文件相关的新数据,系统立即为其分配存储空间,不延时分配(这一结果可以从 /dev/sys/fs/ext4//delayed_allocation_blocks中查看,如果该文件内容为0,表示没有延迟分配的数据块,否则表示延迟分配的数据块的个数,其中,表示Ext4文件系统所在的设备,如果Ext4/dev/sda5上,那么dev就表示sda5)。

        强制立即分配延迟分配给文件的数据块的操作流程

    (1) 判断执行强制立即分配操作的进程是否有相应的权限;

    (2) 获取对文件系统的写操作权限;

    (3) 调用ext4_alloc_da_blocks()函数,强制立即分配数据块;该函数首先调用函数trace_ext4_alloc_da_blocks(inode)获取延迟分配给文件的数据块的个数,然后判断文件是否为延迟分配预留有数据块或元数据块,如果都没有预留则,直接返回(文件系统认为没有给该文件开启延迟分配的特性).否者,调用filemap_flush(inode->i_mapping)函数,立即强制分配数据块。filemap_flush(inode->i_mapping)函数的工作仅仅是调用__filemap_fdatawrite(mapping,WB_SYNC_NONE)函数,其中WB_SYNC_NONE是一个同步标志,表示不进行任何等待。__filemap_fdatawrite(mapping,WB_SYNC_NONE)函数又调用了__filemap_fdatawrite_range(mapping, 0, LLONG_MAX, sync_mode)函数,该函数最终调用了do_writepages()函数,将文件的所有脏数据写到磁盘。

     

     

     

    (4) 释放对文件系统的写操作权限,操作结束

    ioctl源码分析之在线扩展Ext4文件系统

    Ext4文件系统在线扩展大小的本质是将文件系统的数据块个数扩展到用户期望的数据块的个数。从功能上来看是扩展文件系统最后一个块组(EXT4_IOC_GROUP_EXTEND)与增加块组扩展文件系统(EXT4_IOC_GROUP_ADD)两种方式的结合,但是处理上稍微有点不同。Ext4文件系统的Ioctl命令EXT4_IOC_RESIZE_FS用于在线扩展Ext4文件系统的大小。使用以下命令就可实现调用EXT4_IOC_RESIZE_FS命令扩展Ext4文件系统:

    ioctl(fd, EXT4_IOC_RESIZE_FS, arg )

    参数arg为用户希望扩展后最终的文件系统所具有的(文件系统)数据块的个数unsigned int arg

    1. EXT4_IOC_RESIZE_FS扩展文件系统的限制

    利用Ext4 EXT4_IOC_RESIZE_FS命令扩展文件系统,它对文件系统的扩展结果受到以下限制:

    (1)不支持缩小文件系统;

    (2)如果Ext4文件系统开启了bigalloc(大数据块)特性,则不支持在线扩展;

    (3)如果Ext4文件系统开启了元块组(meta_bg)特性,则不支持在线扩展;

    (4)如果Ext4当前使用32位系统且系统不兼容64位,同时调整后的数据块的个数超过2^32,那么不支持在线扩展;

    (5)如果新扩展的部分要使用新的块组描述符数据块,且Ext4中没有预留GDT数据块,则不进行在线扩展;

    (6)如果用户期望扩展的文件系统大小超过实际分区,则不进行扩展。

    2. 在线扩展Ext4文件系统的操作流程

     (1) 依次判断当期Ext4是否开启bigalloc特性与元块组特性,如果开启,则不进行扩展文件系统的操作;

    (2)如果当前Ext4文件系统使用32位数据块个数寻址且不支持64位兼容性,那么如果用户期望扩展文件系统使其超过2^32个,则不进行在线扩展;

     

    (3) 设置ext4_sb_info->s_resize_flags标志位,表示将进行在线调整大小;

    (4) 获取对文件系统的写权限;

    (5) 调用ext4_resize_fs(sb, n_blocks_count)函数扩展文件系统,其中sb为要扩展的文件系统的超级块,n_blocks_count为期望扩展到的最终数据块个数。该函数实际执行如下操作:

    a)  判断参数以及文件系统自身的配置是否满足扩展操作执行的条件。

                i.  获取当前文件系统的数据块个数;

                ii.   判断参数是否指向缩小文件系统,如果是,则推出。不支持缩小文件系统的在线扩展调整;

               iii.  如果新增的部分的块组的描述符要在一个新的块组描述符数据块中分配,判断文件系统中是否有预留GDT数据块,如果没有,那么不能进行扩展。

    b)  获取预留GDT数据块的Inode并进行有效性检查;

    c)  查看硬件物理空间是否足够扩展文件系统;

    d)  先将当前文件系统的最后一个块组扩充完整;

    e) 以每次新增一个flex块组的方式扩展文件系统,具体步骤如下:

             i.  调用alloc_flex_gd(flexbg_size)分配一个struct ext4_new_flex_group_data,用于增加一个flex块组,其中参数flexbg_size为每个flex块组中普通块组的个数;

                 ii.   反复调用ext4_setup_next_flex_gd(sb, flex_gd, n_blocks_count,flexbg_size)函数每次增加一个新的flex块组,直到所有新增数据块都增加到文件系统中。每次增加一个新的flex块组后,先调用ext4_alloc_group_tables(sb, flex_gd, flexbg_size)在创建的flex块组中分配块位图、Inode位图以及Inode表,然后调用ext4_flex_group_add(sb, resize_inode, flex_gd)函数将块组加到文件系统中。

    f) 释放分配的struct ext4_new_flex_group_data空间,将预留GDT数据块的Inode的引用次数减1,返回。

    (6) 建立日志事务锁(barrier机制),更新文件系统元数据;

    (7) 释放对文件系统的写权限;

    (8) 清除ext4_sb_info->s_resize_flags标志位,返回;扩展文件系统操作结束。

  • 相关阅读:
    python装饰器
    python名称空间和作用域
    python函数-函数对象
    python函数-参数
    python文件高级操作
    python基本文件操作
    python2和3的区别
    OSI-传输层
    Android屏幕density, dip等相关概念总结
    架构设计:系统间通信(20)——MQ:消息协议(下)
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8461598.html
Copyright © 2011-2022 走看看