ファイル記述子 (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;
}
ファイルの削除や権限変更
mkdir/rmdir/unlink/rename
#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;
}
symlink/readlink
#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
デバイスファイルの種類について
デバイスファイルはデバイスドライバの API として機能しており open/close/read/write/ioctl を提供します。その際、一般にランダムアクセス等が可能なブロックデバイスと、そうではないキャラクタデバイスがあります。
vagrant@stretch:~$ ls -l /dev/sda1
brw-rw---- 1 root disk 8, 1 Aug 12 11:38 /dev/sda1  <-- 'b'lock
vagrant@stretch:~$ ls -l /dev/zero
crw-rw-rw- 1 root root 1, 5 Aug 12 11:38 /dev/zero  <-- 'c'haracter
ioctl
デバイスドライバによっては read/write に加えて利用できる特別なコマンドが用意されています。ioctl を利用してデバイスドライバに対してそれらコマンドを指定してリクエストを出すことができます。
指定可能なリクエスト一覧
man 2 ioctl_list
特に制御端末に関するリクエストについて
man 4 tty_ioctl
制御端末のサイズを取得する例です。TIOCGWINSZ リクエストを発行しています。
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
int main() {
    int fd;
    struct winsize win;
    if((fd = open("/dev/tty", O_RDONLY)) < 0) {
        perror("open failed");
        return 1;
    }
    if(ioctl(fd, TIOCGWINSZ, &win) < 0) {
        perror("ioctl failed");
        return 1;
    }
    printf("row: %d, col: %d\n", win.ws_row, win.ws_col);
    return 0;
}
実行例
$ gcc -Wall -O2 main.c && ./a.out
row: 52, col: 218
その他
getdtablesize
システムリソースのうち open 可能なファイルディスクリプタ数の上限を調査できます。
#include <unistd.h>
#include <stdio.h>
int main() {
    printf("%d\n", getdtablesize());
    return 0;
}
実行例
$ gcc -Wall -O2 main.c && ./a.out
256
$ ulimit -S -n
256
$ ulimit -H -n
unlimited
sync
#include <unistd.h>
int main() {
    sync();
    return 0;
}
Python から利用する場合
システムコールの多くは Python の os モジュールから利用できます。
#!/usr/bin/python
# -*- coding: utf-8 -*-
from os import open as _open, read, O_RDONLY
def Main():
    fd = _open("/dev/stdin", O_RDONLY)
    print read(fd, 5)
if __name__ == '__main__':
    Main()
実行例
$ echo "Hello" | python sample.py
Hello
記事の執筆者にステッカーを贈る
有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。
さらに詳しく →Feedbacks
ログインするとコメントを投稿できます。
関連記事
- cmake で ccache を有効化するための設定YOCTO Linux で開発している場合など、ビルド速度が開発効率にそのまま影響する際は ccache (compiler cache) で C/C++ ビルドを高速化することを考えます。cmakeと併用する場合の設定およびコマンド例を記載します。 インストール sudo apt install ccache 以下のバイナリファイルに加えて $ w
- Python から C ライブラリを利用 (ctypes)FFI (Foreign Function Interface) の一つである ctypes を利用すると、C 言語のライブラリを Python から利用できます。サンプルコードを記載します。 適宜参照するための公式ドキュメント libm の sqrt を利用する例 main.py ``
- 低レイヤーネットワークプログラミングに関する雑多な知識TCP/IP モデルのうちトランスポート層ではなく、インターネット層およびネットワークインターフェイス層のパケット (正確には PDU) を扱う低レイヤープログラミングの雑多なテクニックをまとめます。『ルーター自作でわかるパケットの流れ』などを参考にしています。バックアップ目的で書籍のサンプルコードをホスティングしました。 検証環境
- C言語の資産を利用 (C++をもう一度)サンプルコード メルセンヌ・ツイスタなど、C言語で記述されたライブラリをC++から利用するためには extern "C" を利用します。その際、組み込みマクロ __cplusplus を利用するとC言語からもC++からも利用できるヘッダファイルを作成できます。 sub.h #ifndef SUB_H_ #define SUB_H_ #ifdef __cplu
- C言語コード読解:size_t型とはサイズを表現するための符号なし整数型です。読み手にとっても、何らかのサイズを格納するための変数であることが分かりやすくなります。真偽値をbool型変数で扱う場合も同様です。 sample.cpp #include <iostream> using namespace std; int main() { size_t size = sizeof(int); cout &l...











