C 言語でシステムコールを利用してファイル操作をしてみる.

    はじめに

    CIFS プロトコルを勉強している.

    CIFS プロトコルは, ファイル読み書きのシステムコールを そのままネットワーク上に流したような仕様になっている.

    エクスプローラから見れば, ファイルシステムがリモートにあろうが ローカルにあろうが, 意識することなくファイルにアクセスできる. それは, ファイルアクセスをすると, Windows のカーネル・モードにあるリダイレクタが呼び出されて, 適切なデバイスに対してコマンドを発行してくれるから.

    つまり, ファイルアクセスの仕組みを理解すれば, CIFS のプロトコルも理解が深まると言うことだ!

    ということで, C 言語で システムコールを利用して ファイルアクセスをしてみた.

    つごうにより, 環境は ArchLinux だけど…

    ファイル作成

    やること

    /home/tsu-nera/tmp 配下に test というファイルを新規作成する.

    touch で作成する

    Unix のコマンド touch で作成する.

    [sourcecode language=”bash” title=”” ]
    touch /home/tsu-nera/tmp/test
    [/sourcecode]

    touch のソースを眺める

    GNU coreutils のソースを眺める.

    そして,fd_reopen という関数の中で open/close が利用されている. https://github.com/goj/coreutils/blob/master/lib/fd-reopen.c

    strace でみてみる

    プログラムが使用するシステムコールおよび受け取るシグナルを監視するツール.

    [sourcecode language=”text” title=”” ]
    strace touch /home/tsu-nera/tmp/test
    [/sourcecode]

    こうなった.

    [sourcecode language=”text” title=”” ]
    execve (“/usr/bin/touch”, [“touch”, “/home/tsu-nera/tmp/test”], [/* 50 vars */]) = 0
    brk (0) = 0x6ce000
    access (“/etc/ld.so.preload”, R_OK) = -1 ENOENT (No such file or directory)
    open (“/usr/local/lib/tls/x86_64/libc.so.6”, O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
    stat (“/usr/local/lib/tls/x86_64”, 0x7ffffbbac6d0) = -1 ENOENT (No such file or directory)
    open (“/usr/local/lib/tls/libc.so.6”, O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
    stat (“/usr/local/lib/tls”, 0x7ffffbbac6d0) = -1 ENOENT (No such file or directory)
    open (“/usr/local/lib/x86_64/libc.so.6”, O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
    stat (“/usr/local/lib/x86_64”, 0x7ffffbbac6d0) = -1 ENOENT (No such file or directory)
    open (“/usr/local/lib/libc.so.6”, O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
    stat (“/usr/local/lib”, {st_mode=S_IFDIR|0755, st_size=4096, …}) = 0
    open (“/etc/ld.so.cache”, O_RDONLY|O_CLOEXEC) = 4
    fstat (4, {st_mode=S_IFREG|0644, st_size=166490, …}) = 0
    mmap (NULL, 166490, PROT_READ, MAP_PRIVATE, 4, 0) = 0x7f042abdd000
    close (4) = 0
    open (“/usr/lib/libc.so.6”, O_RDONLY|O_CLOEXEC) = 4
    read (4, “\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\1\2\0\0\0\0\0″…, 832) = 832
    fstat (4, {st_mode=S_IFREG|0755, st_size=1984416, …}) = 0
    mmap (NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f042abdc000
    mmap (NULL, 3813200, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 4, 0) = 0x7f042a642000
    mprotect (0x7f042a7db000, 2097152, PROT_NONE) = 0
    mmap (0x7f042a9db000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 4, 0x199000) = 0x7f042a9db000
    mmap (0x7f042a9e1000, 16208, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f042a9e1000
    close (4) = 0
    mmap (NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f042abdb000
    mmap (NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f042abda000
    arch_prctl (ARCH_SET_FS, 0x7f042abdb700) = 0
    mprotect (0x7f042a9db000, 16384, PROT_READ) = 0
    mprotect (0x60d000, 4096, PROT_READ) = 0
    mprotect (0x7f042ac06000, 4096, PROT_READ) = 0
    munmap (0x7f042abdd000, 166490) = 0
    brk (0) = 0x6ce000
    brk (0x6ef000) = 0x6ef000
    open (“/usr/lib/locale/locale-archive”, O_RDONLY|O_CLOEXEC) = 4
    fstat (4, {st_mode=S_IFREG|0644, st_size=2581856, …}) = 0
    mmap (NULL, 2581856, PROT_READ, MAP_PRIVATE, 4, 0) = 0x7f042a3cb000
    close (4) = 0
    open (“/home/tsu-nera/tmp/test”, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, 0666) = 4
    dup2 (4, 0) = 0
    close (4) = 0
    utimensat (0, NULL, NULL, 0) = 0
    close (0) = 0
    close (1) = 0
    close (2) = 0
    exit_group (0) = ?
    +++ exited with 0 +++
    [/sourcecode]

    見にくいので, 統計情報を出力する.

    あるディレクトリ配下にファイルを作成すだけでも, 結構な数のシステムコールが呼ばれていることが分かる.

    [sourcecode language=”text” title=”” ]
    [tsu-nera]% strace -c touch /home/tsu-nera/tmp/test
    % time seconds usecs/call calls errors syscall
    —— ———– ———– ——— ——— —————-
    0.00 0.000000 0 1 read
    0.00 0.000000 0 8 4 open
    0.00 0.000000 0 7 close
    0.00 0.000000 0 4 3 stat
    0.00 0.000000 0 3 fstat
    0.00 0.000000 0 8 mmap
    0.00 0.000000 0 4 mprotect
    0.00 0.000000 0 1 munmap
    0.00 0.000000 0 3 brk
    0.00 0.000000 0 1 1 access
    0.00 0.000000 0 1 dup2
    0.00 0.000000 0 1 execve
    0.00 0.000000 0 1 arch_prctl
    0.00 0.000000 0 1 utimensat
    —— ———– ———– ——— ——— —————-
    100.00 0.000000 44 8 total
    [/sourcecode]

    C 言語 ライブラリで実装してみる

    C 言語で ファイル操作を行うために, fopen, fclose を利用する.

    [sourcecode language=”c” title=”” ]
    #include
    int main (void)
    {
    FILE *fp;
    fp = fopen (“/home/tsu-nera/tmp/test”, “w”);
    fclose (fp);
    return 0;
    }
    [/sourcecode]

    C 言語 システムコールで実装してみる

    では, 本題. opne/close を利用する.

    fopen と open の違いはここがわかりやすかった.

    [sourcecode language=”c” title=”” ]
    #include
    #include
    #include
    #include

    int main (void)
    {
    int fd;
    fd = open (“/home/tsu-nera/tmp/test”, O_RDWR|O_CREAT, S_IREAD | S_IWRITE);
    close (fd);
    return 0;
    }
    [/sourcecode]

    ファイル書き込み

    ファイル新規作成を応用して, ファイルにデータを書き込んでみる.

    やること

    /home/tsu-nera/tmp 配下に test という 1MB の ファイルを新規作成する.

    dd でやってみる

    Unix コマンドの dd を利用して, 1MB のファイルを作成する.

    [sourcecode language=”bash” title=”” ]
    dd if=/dev/urandom of=/home/tsu-nera/tmp/test count=1024 bs=1024
    [/sourcecode]

    dd コマンドのソースをながめる

    GNU coreutils のソースを眺める.

    たとえば以下のように write 関数が利用されている.

    [sourcecode language=”c” title=”” ]
    while (total_written < size) { ssize_t nwritten; process_signals (); nwritten = write (fd, buf + total_written, size - total_written); if (nwritten < 0) { if (errno != EINTR) break; } else if (nwritten == 0) { /* Some buggy drivers return 0 when one tries to write beyond a device's end. (Example: Linux kernel 1.2.13 on /dev/fd0.) Set errno to ENOSPC so they get a sensible diagnostic. */ errno = ENOSPC; break; } else total_written += nwritten; } [/sourcecode]

    C 言語 ライブラリで実装してみる

    fwrite 関数で書き込む.

    [sourcecode language=”c” title=”” ]
    #include
    #include

    #define WRITE_SIZE 1024
    #define WRITE_COUNT 1024

    int main (void)
    {
    int i;
    char r[WRITE_SIZE];
    FILE *fp;

    // generate random value
    for (i = 0; i < WRITE_SIZE; i++) { r[i] = rand (); } // open fp = fopen ("/home/tsu-nera/tmp/test", "w"); // write for (i = 0; i < WRITE_COUNT; i++) { fwrite (r, sizeof (char), WRITE_SIZE, fp); } // close fclose (fp); return 0; } [/sourcecode]

    C 言語 システムコールで実装してみる

    write 関数で書き込む.

    [sourcecode language=”c” title=”” ]
    #include
    #include
    #include
    #include
    #include

    #define WRITE_SIZE 1024
    #define WRITE_COUNT 1024

    int main (void)
    {
    int i;
    int fd;
    char r[WRITE_SIZE];

    // generate random value
    for (i = 0; i < WRITE_SIZE; i++) { r[i] = rand (); } // open fd = open ("/home/tsu-nera/tmp/test", O_RDWR|O_CREAT, S_IREAD|S_IWRITE); // write for (i = 0; i < WRITE_COUNT; i++) { write (fd, r, WRITE_SIZE); } // close close (fd); return 0; } [/sourcecode]