分类: LINUX
2014-02-19 22:47:09
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命令,分别在执行转移前后使用该命令获取文件的区段信息,就可以判断是否执行的转移(交换)操作。
利用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;
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文件描述符,转移操作结束,返回。