なぜlsとduで表示されるファイルのサイズに差があるのですか?


何度かlsとduでファイルのサイズを確認したところ、両者が表示されているサイズが一致していないことがわかりました.たとえば、次のようにします.
bl@d3:~/test/sparse_file$ ls -l fs.img
-rw-r--r-- 1 bl bl 1073741824 2012-02-17 05:09 fs.img
bl@d3:~/test/sparse_file$ du -sh fs.img
0       fs.img

ここでlsはfsを示す.imgのサイズは1073741824バイト(1 GB)であり、duはfsを示す.imgのサイズは0です.
もとはずっとこの問題を深く研究していなかったが,今日は特に補充しに来た.
この2つの異なる原因は主に2つあります.
  • 疎ファイル
  • lsとduが示すsizeには異なる意味がある
  • まずまばらな書類を見てみましょう.疎ファイルはファイルの中に「穴」(hole)があるファイルのみで、例えばCが「穴」があるファイルを書く:
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
        int fd = open("sparse.file", O_RDWR|O_CREAT);
        lseek(fd, 1024, SEEK_CUR);
        write(fd, "\0", 1);
    
        return 0;
    }
    このファイルから分かるように、「穴」があるファイルを作成するのは主にlseekでファイルポインタをファイルの末尾を上回ってwriteを移動し、それによって「穴」を形成する.Shellでも疎ファイルを作成することができる:
    $ dd if=/dev/zero of=sparse_file.img bs=1M seek=1024 count=0
    0+0 records in
    0+0 records out
    疎ファイルを使用する利点は以下の通りである(Wikipedia上の原文):The advantage of sparse files is that storage is only allocated when actually needed:disk space is saved,and large files can be created even if there is insufficient free space on the file system.つまり、疎ファイルの「穴」は、ストレージ領域を占有しないことができます.lsとduが出力するファイルサイズの意味(Wikipedia上の原文):The du command which prints the occupied space,while ls print the apparent size.すなわち、lsはファイルの「論理的」なsizeを表示し、duはファイルの「物理的」なsizeを表示する.すなわち、duが表示するsizeは、ファイルがハードディスク上でどれだけのblockを占めているかで計算される.例:
    bl@d3:~/test/sparse_file$ echo -n 1 > 1B.txt
    bl@d3:~/test/sparse_file$ ls -l 1B.txt
    -rw-r--r-- 1 bl bl 1 2012-02-19 05:17 1B.txt
    bl@dl3:~/test/sparse_file$ du -h 1B.txt
    4.0K    1B.txt
    ここではまずファイル1 Bを作成する.txt、サイズは1バイト、lsが示すsizeは1 Byte、1 Bである.txtこのファイルはハードディスク(HDD)にN個のblockを占有し、各blockのサイズに基づいて計算されます.ここでNを使ったのは、具体的な数字ではなく、裏に隠されている細部が多いからです.例えば、Fragment sizeです.後で議論します.もちろん、これらはlsとduのデフォルトの動作であり、lsとduはそれぞれ異なるパラメータを提供してこれらの動作を変更する.例えばlsの-sオプション(print the allocated size of each file,in blocks)とduの--apparent-sizeオプション(print apparent sizes,rather than disk usage;although the apparent size is usually smaller,it may be larger due to holes in(`sparse')files,internal fragmentation,indirect blocks,and the like).また、コピー疎ファイルの場合、cpはデフォルトでコピーの速度を速めるために最適化されます.例えば、
    $ strace cp fs.img fs.img.copy >log 2>&1
    stat("fs.img.copy", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
    stat("fs.img", {st_mode=S_IFREG|0644, st_size=1073741824, ...}) = 0
    stat("fs.img.copy", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
    open("fs.img", O_RDONLY)                = 3
    fstat(3, {st_mode=S_IFREG|0644, st_size=1073741824, ...}) = 0
    open("fs.img.copy", O_WRONLY|O_TRUNC)   = 4
    fstat(4, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
    mmap(NULL, 532480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f90df965000
    read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 524288) = 524288
    lseek(4, 524288, SEEK_CUR)              = 524288
    read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 524288) = 524288
    lseek(4, 524288, SEEK_CUR)              = 1048576
    read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 524288) = 524288
    lseek(4, 524288, SEEK_CUR)              = 1572864
    これはcpのsparseに関するオプションと関係がある、cpのmanpage:By default、sparse SOURCE files are detected by a crude heuristic and the corresponding DEST file is made sparse as wellを見る.  That is the behavior selected by --sparse=auto.  Specify --sparse=always to create a sparse DEST file whenever the SOURCE file contains a long enough sequence of  zero bytes.  Use --sparse=never to inhibit creation of sparse files. cpのソースコードを見てみると、readのたびに、cpは読んだ内容が0であるかどうかを判断し、lseekだけでwriteではないことがわかります.もちろんsparseファイルの処理は,ユーザに対して透過的である.