Linux:sparse fileのファイルサイズと実際に使っている領域の大きさはどう調べるのか。

今日の原書で学ぶ64bitアセンブラ入門(6)でsparse fileの話題が出て気になったので調べてみた。何を調べたかったかというと、sparse file全体の大きさ、実際に使っているサイズはどう管理されているのかというところ。

ちなみに、Sparse FileについてはSparse Fileとは?とか、Arch Linuxのwikiが作り方、サイズの調べ方など充実います。

まず、sparse fileを作ってその時点でのサイズを見てみます。 ddでinputとして/dev/zeroを、outputにfile.imgを指定します。そして、seekで10MiB目に移動してますがデータそのものはcount=0を指定しているので書き込みしません。 そうすると読み込みバイト数0、書き込みバイト数0という感じで処理が終わります。

masami@saga:~/sparse_file_test$ dd if=/dev/zero of=file.img bs=1 count=0 seek=10M
0+0 records in
0+0 records out
0 bytes (0 B) copied, 0.000267005 s, 0.0 kB/s

そして、lsで見てみるとサイズが10MiBのファイルができています。これはddのseek=10Mの結果ですね。

masami@saga:~/sparse_file_test$ ls -lah file.img
-rw-r--r-- 1 masami masami 10M Jun 15 22:56 file.img

次にduを見てみるとdiskの使用量は0になってます。これはデータを何一つ書いていないので当然と言えば当然ですね。

masami@saga:~/sparse_file_test$ du -h file.img
0       file.img

次にduでバイト数単位の使用量を見てみると10MiBでddで作った通りのサイズになっています。

masami@saga:~/sparse_file_test$ du -bh file.img
10M     file.img

ここまでで分かるのはファイル全体のサイズは10MiBだけど、実際のディスク使用量は0ということ。これはsparse fileなのでまあ当然の結果ですが、これらのサイズはどこで見れるんだろう?というのが今回調べたところ。

ファイルのサイズなのでstat構造体は使うだろうというのは想像できるんだけどst_sizeはどっちの値になるんだ?というのはありますよね。 調べた結果、st_sizeはファイル全体のサイズが設定されているのでfile.imgの場合は10485760になります。では実際に使用量は?というとこれはstat構造体にあるst_blocksを使います。 この変数はmanによるとファイルに割り当てられたブロック数(サイズは512B)とあります。 そこで、適当な文字列(foo)をfile.imgの先頭3byteに書き込んで実際にディスクを割り当てられた状態にしてからduして確認をしてみます。

duの結果4.0kとなり1block分使われたことがわかります(ファイルがあるのは普通に作ったext4環境なので1ブロックサイズは4KiBです)。

masami@saga:~/sparse_file_test$ du -h file.img
4.0K    file.img

duの-bオプションの場合は当然数値は変わりません。

masami@saga:~/sparse_file_test$ du -bh file.img
10M     file.img

stat(1)を実行すると以下のようになり8ブロック使用していることがわかります。ただし、manにも書かれていたとおりここの1ブロックのサイズは512Bです。なので4KiBが使用している領域の大きさになります。IO Blockはファイルシステムの1ブロックのサイズです。

masami@saga:~/sparse_file_test$ stat file.img
  File: ‘file.img’
  Size: 10485760        Blocks: 8          IO Block: 4096   regular file
Device: 813h/2067d      Inode: 6291832     Links: 1

これでdu -hの場合はst_blocks * 512、du -bやlsの場合はst_sizeを表示しているということがわかりました。

↓はテストに使ったコードです。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>

#define TEST_FILE_NAME "./file.img"

static void do_lstat(struct stat *st)
{
    if (lstat(TEST_FILE_NAME, st)) {
        perror("lstat");
        exit(-1);
    }
}

static void print_file_size(void)
{
    struct stat st;

    do_lstat(&st);
    printf("Total file size is %ld\n", st.st_size);
    printf("Block size is %ld\n", st.st_blksize);
    printf("Number of allocated blocks is %ld: total bytes is %ld\n", st.st_blocks, st.st_blocks * 512);
}

static void write_data(void)
{
    int fd =0;
    char *mapped = NULL;
    struct stat st;

    do_lstat(&st);

    fd = open(TEST_FILE_NAME, O_RDWR);
    if (fd < 0) {
        perror("open");
        exit(-1);
    }

    mapped = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mapped == (char *) MAP_FAILED) {
        perror("mapped");
        exit(-1);
    }

    mapped[0] = 'f';
    mapped[1] = 'o';
    mapped[2] = 'o';

    munmap(mapped, st.st_size);
    close(fd);

    printf("wrote \"foo\" to first three bytes\n");
    print_file_size();
}

static void usage(char *name)
{
    printf("usage %s [sw]\n", name);
    printf("\ts: print file size\n");
    printf("\tw: write data to test file\n");
    exit(0);
}

int main(int argc, char **argv)
{
    int opt;

    if (argc == 1)
        usage(argv[0]);

    while ((opt = getopt(argc, argv, "sw")) != -1) {
        switch (opt) {
            case 's':
                print_file_size();
                break;
            case 'w':
                write_data();
                break;
            default:
                usage(argv[0]);
        }
    }

    return 0;
}

プロのための Linuxシステム・10年効く技術 (Software Design plus)

プロのための Linuxシステム・10年効く技術 (Software Design plus)