深度剖析 Linux cp 命令的秘密( 十 )

以上对于 copy_reg 的代码我做了极大的简化,把关键流程梳理了出来 。
小结:

  1. copy_reg 函数才是真正 cp 一个普通文件的逻辑所在,源文件的打开,目标文件的创建和数据的写入都在这里;
  2. 拷贝之前,会先用 is_probably_sparse 函数来判断源文件是否属于稀疏文件;
  3. 如果是 sparse always 模式,那么无论源文件是否是稀疏文件,那么都会尝试生成稀疏的目标文件(这种模式下,源文件如果是非稀疏文件,会判断是否是全 0 数据,如果是的话,还是会在目标文件中打洞);
  4. 如果是 sparse auto 模式,源文件是稀疏文件,那么生成的目标文件也会是稀疏文件;
  5. 源文件为稀疏文件的时候,会尝试使用效率更高的 extent_copy 函数来拷贝数据;
  6. 如果是 never 模式,那么是调用 sparse_copy 函数来拷贝数据,并且里面不会尝试 punch hole,拷贝过程会非常慢,会生成一个实打实的目标文件,物理空间占用完全和文件size一致;
上面的小结,提到几个有意思的点,我们一起探秘下几个问题 。
 
问题一:is_probably_sparse 函数是怎么来判断源文件的?
看了源码你会发现,非常简单,其实就是 stat 一下源文件,拿到文件大小 size,还有物理块的占用个数(假设物理块 512 字节),比一下就知道了 。
static boolis_probably_sparse (struct stat const *sb){  return (HAVE_STRUCT_STAT_ST_BLOCKS          && S_ISREG (sb->st_mode)          && ST_NBLOCKS (*sb) < sb->st_size / ST_NBLOCKSIZE);}举个例子,文件大小 size 为 100G,物理占用块 8 个,那么 100G/512字节 > 8,所以就是稀疏文件 。
文件大小 size 为 4K,物理占用块 8 个,那么 4K/512字节 == 8,所以就不是稀疏文件 。
 
问题二:extent_copy 为什么更有效率?
关键在于里面的一个子函数 extent_scan_read 的实现,extent_scan_read 位于 extent-scan.c 文件中 。extent_scan_read 位于 extent_copy 开头,用来获取到源文件的空洞位置信息 。这个就是 extent_copy 高效率的根本原因 。extent_scan_read 通过这个函数能够拿到文件的空洞的详细位置,那么拷贝数据的时候,就能针对性的跳过这些空洞,只拷贝有效的位置即可 。
那么,不禁又要问, extent_scan_read 又是怎么实现的呢?
答案是:ioctl 系统调用,搭配 FS_IOC_FIEMAP 参数,也就是 fiemap 的调用 。
/* Call ioctl(2) with FS_IOC_FIEMAP (available in linux 2.6.27) to obtain a map of file extents excluding holes. */
fiemap 这个是一个非常关键的特性,ioctl 搭配 FS_IOC_FIEMAP 这个函数能够拿到文件的物理空间分配关系,能够让用户知道长达 100G 的文件中,哪些位置才是真正有物理块存储数据的,哪些位置是空洞 。
这个特性则由文件系统提供,也就是说,只有文件系统提供了这个对外接口,我们才能拿得到,比如 ext4,就支持这个接口,ext2 就没有 。
 
问题二:sparse_copy 为什么慢,里面哟是做了啥?
这个函数是标准的 copy 函数,对比 extent_copy 来说,没有 fiemap 的加持,那么这个函数就自己判断是否是空洞,怎么判断?
sparse_copy 认为,只要大块连续的全 0 数据,那么就认为是空洞,目标文件就不用写入,直接打洞即可 。
判断是否全 0 的函数是is_nul,位于 system.h 头文件中,实现非常简单,就是看整个内存块是否全部为 0。
举个例子,现在 sparse_copy 从源文件里读出 4k 的数据,发现全都是 0,那么目标文件对应的位置就不会写入,而是直接 punch hole 打洞,节省空间 。
但是注意了,这种行为只有在激进的 sparse always 策略才是这样的 。如果是其他策略,sparse_copy 不会做这样做,而是老老实实的拷贝数据,哪怕是全 0 的数据,也要如实的写入到目标文件 。
所以,always 模式下,目标文件所占物理空间比源文件小的根本原因就在于 sparse_copy 这个函数的实现 。
 
cp 快速的原因 
梳理到这里,cp 的秘密已经彻底揭开了,cp 一个 100G 的文件为什么那么快?
因为源文件是稀疏文件啊,文件看似 100G,实际只占用了 2M 的物理空间 。文件系统将文件大小和物理空间占用这两个概念解耦,使得有更灵活的使用姿势,更有效的使用物理空间 。


推荐阅读