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


这个很容易理解,因为磁盘的物理空间是划分成 4k 的 block,这个是最小单位了,不能再分了,你无法切割一个最小的单位 。
值得注意的是,就算你没有 4k 对齐的发送调用,fallocate 也不会报错,这个请注意了 。

深度剖析 Linux cp 命令的秘密

文章插图
 
cp 的秘密
深度剖析 Linux cp 命令的秘密

文章插图
 
铺垫了这么久的基础知识,终于到我们的 cp 命令的解密了 。回到最开始的问题,cp 一个 100G 的文件 1 秒都不到,为什么这么快?
说到现在,这个问题就很清晰了,这个 100G 的文件是个稀疏文件,盲猜一手:cp 的时候只拷贝了有效数据,空洞是直接跳过的 。 往前看 stat 命令和 ls 命令显示的差距就知道了 。
接下来我们具体看一下 cp 的实现 。
cp 有一个参数 --sparse 很有意思,sparse 这个参数控制这 cp 命令对稀疏文件的行为,这个参数有三个值可选:
  1. --sparse=always :空间最省;
  2. --sparse=auto :默认值,速度最快;
  3. --sparse=never :吭呲吭呲 copy,最傻;
cp 默认的时候,sparse 是 auto 策略 。auto,always,never 分别是什么策略呢?
 
spare 三大策略auto 策略默认的情况下,cp 会检查源文件是否具有稀疏语义,对于不占物理空间的位置,目标文件不会写入数据,从而形成空洞 。
所以,对于我们的例子,真实的就只进行了 2M 的 IO ,预期的 100G 文件,只拷贝了 2M 的数据,自然飞快了,自然惊艳所有人 。
auto 是默认策略,使用该模式的时候,cp 内部实现是通过系统调用拿到文件的空洞位置情况,然后对这些位置目标文件会保持空洞 。
注意,不会对非空洞位置的文件内容做判断,如果用户数据占用了物理块,但是是全 0 数据,这种情况下,auto 模式不会识别,会以全零的数据写入到目标文件 。这个是跟 always 最大的区别 。
auto 策略下 cp 的文件的文件,size,物理 block 数量都和源文件一致 。
always这种方式是最激进的,追求空间的最小化 。在 auto 的基础之上,还多做了一步:对源文件内容做了判断 。
在读出源数据之后,就算这块数据位置在源文件不是空洞,也会自己在程序里做一次判断,判断是否是全 0 的数据,如果是,那么也会在目标文件里对应的位置创建空洞(不分配物理空间) 。
这种方式则会导致源文件的 size 和目标文件一样(三种策略下,文件size 都是不变的),但是 物理 blocks 占用却更小 。
never这种方式最保守,实现也最简单 。不管源文件是否是稀疏文件,cp 完全不感知,读出来的任何数据都直接写入目标文件 。也就是说,如果一个 100G 的文件,就算只占用了 4K 的物理空间,也会创建出一个 100G 的目标文件,物理空间就占用 100G 。
所以,如果你 cp 的时候带了这个参数,那么将会非常非常慢 。
 
深入剖析 cp --sparse 源码 
上面的都是结论,现在我们通过源码再深入理解下 cp 的原理,一起围观下 cp 的代码实现 。
cp 命令源码在 GNU 项目的 coreutils 项目中,为 Linux 提供外围的基础命令工具 。看似极简的 cp,其实代码实现还挺有趣的 。
cp 的入口代码在 cp.c 文件中(以下基于 coreutils 8.30 版本):
以一个 cp 文件的命令举例,我们一起走一下源码视角的旅途:
cp ./src.txt dest.txt首先,在 main 函数里初始化参数:
      switch (c)        {        case SPARSE_OPTION:          x.sparse_mode = XARGMATCH ("--sparse", optarg,                                     sparse_type_string, sparse_type);          break;这里会根据用户传入的参数,对应翻译成一个枚举值,该枚举值就是 SPARSE_NEVER,SPARSE_AUTO,SPARSE_ALWAYS 其中之一,默认用户没带这个参数的话,就会是 SPARSE_AUTO:


推荐阅读