ファイルディスクリプタ関連のシステムコールのサンプルコード (C 言語)
[最終更新] (2019/06/03 00:30:29)
最近の投稿
注目の記事

概要

ファイル記述子 (File Descriptor) に関連するシステムコールを利用した C 言語のサンプルコードを記載します。

ファイルの読み書き

open/close

main.c

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main() {
    int fd_r, fd_w;

    // 読み込み専用
    if((fd_r = open("./main.c", O_RDONLY)) < 0) {
        perror("xxx");
        return 1;
    }
    // 存在しなければ新規作成して書き込み、存在していれば既存の内容を削除して書き込み
    // 8進数666 - umask でファイル作成 (umask 022 ならば 644 で作成)
    if((fd_w = open("/tmp/sample.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) {
        // 追記する場合は O_WRONLY|O_APPEND
        perror("xxx");
        return 1;
    }
    if(close(fd_r) < 0) {
        perror("xxx");
        return 1;
    }
    if(close(fd_w) < 0) {
        perror("xxx");
        return 1;
    }
    return 0;
}

実行例 (関連: umask)

$ gcc -Wall -O2 main.c && ./a.out 
$ ls -l /tmp/sample.txt 
-rw-r--r-- 1 vagrant vagrant 0 Jul 31 15:31 /tmp/sample.txt
$ umask
0022

read/write

#include <unistd.h>
#include <stdio.h>

int main() {
    char buf[1024];
    ssize_t n;
    if((n = read(0, buf, sizeof buf)) < 0) {
        perror("xxx");
        return 1;
    }
    write(1, buf, n);
    return 0;
}

実行例

$ gcc -Wall -O2 main.c && echo 123 | ./a.out
123

lseek

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main() {
    int fd;
    off_t offset;
    ssize_t n;
    char buf[1024];

    if((fd = open("./main.c", O_RDONLY)) < 0) {
        perror("open failed");
        return 1;
    }

    // 400 バイト進める
    if((offset = lseek(fd, 400, SEEK_SET)) < 0) {
        perror("lseek failed");
        return 1;
    }

    if((n = read(fd, buf, sizeof buf)) < 0) {
        perror("read failed");
        return 1;
    }
    write(1, buf, n);
    return 0;
}

実行例

$ gcc -Wall -O2 main.c && ./a.out
izeof buf)) < 0) {
        perror("read failed");
        return 1;
    }
    write(1, buf, n);
    return 0;
}

truncate

#include <unistd.h>
#include <stdio.h>

int main() {
    if(truncate("sample.txt", 0) < 0) { // 0 バイトに切り詰める
        perror("truncate failed");
        return 1;
    }
    return 0;
}

ファイルの削除や権限変更

#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

int main() {

    // mkdir/rmdir
    if(mkdir("mydir", 0777) < 0) { // 777 - umask
        perror("mkdir failed");
        return 1;
    }
    if(rmdir("mydir") < 0) {
        perror("rmdir failed");
        return 1;
    }

    // rename/unlink
    if(rename("sample.txt", "sample2.txt") < 0) {
        perror("rename failed");
        return 1;
    }
    if(unlink("sample2.txt") < 0) {
        perror("unlink failed");
        return 1;
    }
    return 0;
}

chmod/chown

#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    if(chmod("sample.txt", 0777) < 0) { // umask は無関係
        perror("chmod failed");
        return 1;
    }
    if(chown("sample.txt", 123, 456) < 0) { // uid, gid
        perror("chown failed");
        return 1;
    }
    return 0;
}
#include <unistd.h>
#include <stdio.h>

int main() {
    ssize_t n;
    char buf[1024];

    if(symlink("./main.c", "mylink.c") < 0) { // mylink.c -> ./main.c
        perror("symlink failed");
        return 1;
    }

    if((n = readlink("mylink.c", buf, sizeof buf - 1)) < 0) {
        perror("readlink failed");
        return 1;
    }

    buf[n] = '\0';
    printf("%s\n", buf); // "./main.c"

    return 0;
}

ファイルの記述子の操作

pipe

シェルのパイプでも利用されているシステムコールです。例えば fork して作成した子プロセスからの書き込みを親プロセスで受け取ることができます。プロセス間通信 IPC (inter process communication) の実装で利用できます。

#include <unistd.h>
#include <stdio.h>

int main() {

    // 親プロセスと子プロセスで利用する、
    // 接続されたファイル記述子を格納します。
    int pipe_fd[2];

    // 必須ではありませんがここでは子プロセスを生成します。
    pid_t child_pid;

    // 親プロセスでファイル記述子からデータを読み出すために利用します。
    ssize_t n;
    char buf[4096];

    // ファイル記述子を二つ作成して接続します。
    if(pipe(pipe_fd) < 0) {
        perror("pipe failed");
        return 1;
    }
    printf("%d => %d\n", pipe_fd[1], pipe_fd[0]);

    // 子プロセスを作成します。
    if((child_pid = fork()) < 0) {
        perror("fork failed");
        return 1;
    }
    else if(child_pid == 0) {
        // 子プロセスの場合の分岐
        close(pipe_fd[0]); // 使用しないため閉じます。
        write(pipe_fd[1], "IPC from child\n", 15); // IPC (inter process communication) プロセス間通信
        _exit(0);
    }

    // 親プロセスの場合の分岐
    close(pipe_fd[1]); // 使用しないため閉じます。
    if((n = read(pipe_fd[0], buf, sizeof buf)) < 0) {
        perror("read failed");
        return 1;
    }
    write(1, buf, n);
    return 0;
}

実行例

$ gcc -Wall -O2 main.c && ./a.out
4 => 3
IPC from child

poll

#include <stdio.h>
#include <poll.h>

int main() {
    int n;
    // 簡単のため、ファイル記述子0 (標準入力) だけを監視してみます。
    struct pollfd fds[1];
    char buf[256];

    fds[0].fd = 0;
    fds[0].events = POLLIN;

    while(1) {
        n = poll(fds, 1, 5000);
        if(n < 0) {
            perror("poll");
            return 1;
        }
        else if(n == 0) {
            printf("no input\n");
        }
        else {
            if(fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
                fprintf(stderr, "error\n");
                return 1;
            }
            if(fds[0].revents & POLLIN) {
                printf("input from fd0\n");
                fgets(buf, 256, stdin); // 標準入力から一行読み込んで空にする。
            }
        }
    }
    return 0;
}

実行例

$ gcc -Wall -O2 main.c && ./a.out
a  ←エンター
input from fd0
aaa
input from fd0
no input  ←5秒経過

select

poll と異なり select では監視できるファイル記述子数に制限があります。

#include <sys/select.h>
#include <stdio.h>

int main() {

    int n;
    fd_set readfds;
    struct timeval tv;

    // ファイル記述子 0 と 3 を監視
    FD_ZERO(&readfds);
    FD_SET(0, &readfds);
    FD_SET(3, &readfds);

    // 5 秒でタイムアウトするように設定
    tv.tv_sec = 5;
    tv.tv_usec = 0;

    // select では監視できるファイル記述子数に制限があります。
    printf("SETSIZE = %d\n", FD_SETSIZE);

    while(1) {

        // ファイル記述子 0 から 4-1 までを監視
        n = select(4, &readfds, NULL, NULL, &tv);

        if(n < 0) {
            perror("select failed");
            return 1;
        }
        else if(n == 0) {
            printf("no input\n");
        }
        else {
            if(FD_ISSET(0, &readfds)) {
                printf("fd = 0\n");
            }
            if(FD_ISSET(3, &readfds)) {
                printf("fd = 3\n");
            }
        }
    }
    return 0;
}

実行例

gcc -Wall -O2 main.c && ./a.out 3<&0

dup2/fcntl

以下のサンプルではファイル記述子1 (標準出力) の複製であるファイル記述子3 を生成して利用しています。

#include <unistd.h>
#include <stdio.h>

int main() {

    if(dup2(1, 3) < 0) {
        perror("dup2 failed");
        return 1;
    }

    write(3, "Hello\n", 6);

    return 0;
}

同様のことは汎用的なファイル記述子操作用のシステムコール fcntl に F_DUPFD を指定しても実現できます。

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main() {

    if(fcntl(1, F_DUPFD, 3) < 0) {
        perror("fcntl failed");
        return 1;
    }

    write(3, "Hello\n", 6);

    return 0;
}

実行例

$ gcc -Wall -O2 main.c && ./a.out
Hello

デバイスファイルにリクエストを発行

デバイスファイルには、実際に接続されているハードウェアのデバイスドライバへのインタフェースとして機能するファイルや、擬似デバイスの /dev/null 等を含めて以下のようなものがあります。

/dev/sda1 (ハードディスク)
/dev/null
/dev/zero
/dev/stdin -> fd/0
/dev/stdout -> fd/1
/dev/stderr -> fd/2
/dev/tty (接続している端末デバイス teletypewriter TTY)
/dev/pts/{番号} (擬似端末)
/dev/port (I/O ポートアクセス)

TTY/PTS について

疑似端末 (pseudo TTY) は SSH 等でリモートログインすると生成されます。以下では SSH クライアントが 4 つ接続している状態です。

vagrant@stretch:~$ w
 14:15:03 up  2:36,  4 users,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
vagrant  pts/0    10.0.2.2         11:39   51:25   0.20s  0.20s -bash
vagrant  pts/1    10.0.2.2         13:34   38:46   0.07s  0.02s pager
vagrant  pts/2    10.0.2.2         13:37    2:04   0.12s  0.00s less
vagrant  pts/3    10.0.2.2         14:08    0.00s  0.03s  0.00s w

vagrant@stretch:~$ ls -l /dev/pts/
total 0
crw--w---- 1 vagrant tty  136, 0 Aug 12 13:23 0
crw--w---- 1 vagrant tty  136, 1 Aug 12 13:36 1
crw--w---- 1 vagrant tty  136, 2 Aug 12 14:13 2
crw--w---- 1 vagrant tty  136, 3 Aug 12 14:15 3
c--------- 1 root    root   5, 2 Aug 12 11:38 ptmx

自分自身の番号は tty コマンドで確認できます。

vagrant@stretch:~$ tty
/dev/pts/3

以下の三つは同じ挙動を示します。

echo 123
echo 123 > /dev/tty
echo 123 > /dev/pts/3

ps コマンドの TTY 列で各プロセスの制御端末の番号を確認できます。

vagrant@stretch:~$ ps
  PID TTY          TIME CMD
23929 pts/3    00:00:00 bash
23971 pts/3    00:00:00 ps

デバイスファイルの種類について

この続きが気になる方は

ファイルディスクリプタ関連のシステムコールのサンプルコード (C 言語)

残り文字数は全体の約 18 %
tybot
100 円
関連ページ
    概要 メモリ操作に関するシステムコールを利用した C 言語のサンプルコードを記載します。 ページサイズの確認 (getpagesize) OS はメモリを複数のページに分割して管理しています。一つのページのサイズは以下のコマンドで確認できます。通常は 4kb です。 getconf PAGESIZE 4096
    概要 FFI (Foreign Function Interface) の一つである ctypes を利用すると、C 言語のライブラリを Python から利用できます。サンプルコードを記載します。 適宜参照するための公式ドキュメント libm の sqrt を利用する例 main.py #!/usr/bin/python # -*- coding: utf-8 -*- from c
    概要 ZeroMQ を Python から利用する場合のサンプルコードを記載します。 Fixing the World To fix the world, we needed to do two things. One, to solve the general problem of "how to connect any code to any code, anywhere". Two,
    概要 Python で数学的なことを試すときに利用される Matplotlib/SciPy/pandas/NumPy についてサンプルコードを記載します。 Matplotlib SciPy pandas NumPy チュートリアル Installing packages Quickstart tutorial
    コマンドのエイリアスを登録する (update-alternatives) mybin という名前のコマンドを登録 sudo update-alternatives --install /usr/local/bin/mybin mybin /usr/bin/echo 10 sudo update-alternatives --install /usr/local/bin/mybin mybin