Linux システムコールを C 言語から利用するための基本的な知識
[履歴] [最終更新] (2018/05/28 00:36:50)

概要

C 言語から Linux OS の各システムコールを利用する場合を想定して、関連情報をまとめます。

システムコールについて

プログラムを実行するとき、CPU のレジスタの一つであるプログラムカウンタは、実行するプログラムが展開されたメモリ上のアドレスを指します。CPU が実行するプログラムには、OS のカーネルと、カーネルが管理するユーザプログラムがあります。CPU のプログラムカウンタは、これら複数のプログラムのメモリアドレスを次々と切り換えながら指すことで、見かけ上、複数のプログラムを同時に実行します。これをプリエンプティブ・マルチタスク (preemptive multitask) とよびます。プロセスとして動作するユーザプログラム間の切り換えはコンテキストスイッチという仕組みを利用します。

カーネルとユーザプログラムは異なる CPU モードで動作します。それぞれ、特権モード (スーパーバイザモード、カーネルモード) とユーザモードで動作しており、例えばユーザモードではハードウェアを操作する CPU 命令を出すことができません。ユーザプログラムからハードウェアを操作したい場合は、カーネルが提供するシステムコールというインタフェースを用いて間接的に操作します。このインタフェースを介することにより、ハードウェアを操作する CPU 命令を、不具合を含む可能性のあるユーザプログラムからでも「安全に」出すことができます。ハードウェアを操作するような CPU 命令を出すためには、ユーザプログラムの動作権限を一時的に特権モードに切り換える必要があり、このときもコンテキストスイッチの仕組みを利用します。

libc とシステムコールの関係

カーネルが提供するシステムコールは、アセンブラで記述して呼び出すこともできますが、例えば C 言語の場合、より簡単に利用できるライブラリ関数が各システムコールに対応するように libc 内で提供されています。libc を利用すると、OS の違いによって存在したりしなかったりするシステムコールの差異を吸収することもできます。

マニュアルの参照方法

CentOS の場合は man-pages をインストールしておきます。

sudo yum install man
sudo yum install man-pages

man コマンド実行時にはセクション番号を指定できます。例えば chmod はコマンドとシステムコールでそれぞれ別物であるため、システムコールのマニュアルを読みたい場合は man 2 chmod とします。

  • 1: ユーザコマンド (ls など)
  • 2: システムコール (fork など)
  • 3. システムコールを除く C ライブラリ関数 (printf など)

上述のとおり、OS によってはシステムコールが存在せず、互換性のためにシステムコールを間接的に利用するライブラリ関数として実装されていることにも注意します。その場合はセクション番号 3 を指定する必要があります。

man 2 fork
man 2 chmod

エラー処理について

システムコールの返り値で条件分岐して perror() でエラー内容を出力します。

main.c

#include <sys/stat.h> // open()
#include <fcntl.h> // open()
#include <stdio.h> // perror()
#include <errno.h> // errno
#include <string.h> // strerror()
#include <locale.h> // setlocale()

int main() {
    int fd_r;

    // 英語以外のエラー表示をしたい場合は `LC_ALL` を空にして $LANG 環境変数を見るようにします。
    setlocale(LC_ALL, "");

    // 基本的には、システムコールを内部的に利用する C ライブラリ関数の返り値を利用して
    // エラーが発生したかどうかを判別できます。0 以上であれば成功、失敗の場合は -1 です。

    // ただし、システムコールによっては戻り値なしでリターンするものや
    // `exit()` のようにそもそもリターンしないものがあります。

    if ((fd_r = open("/path/to/nofile", O_RDONLY)) < 0) {

        // `任意のメッセージ: strerror(errno)` という形式で出力できます。
        perror("/path/to/nofile");

        // システムコールの実行でエラーが発生した場合は、外部変数 `errno` にエラー番号が格納されます。
        // エラーが発生していない場合は以前の値が格納されたままになります。
        // 戻り値なしでリターンするシステムコールを利用する場合は、
        // errno に 0 を代入してから実行してエラー判定する必要があります。
        printf("errno %d: %s\n", errno, strerror(errno));

        return 1;
    }

    // fd_r を利用した処理
    // ...
    return 0;
}

実行例

gcc -Wall -O2 main.c
unset LC_ALL
unset LC_MESSAGES
unset LC_CTYPE

$ LANG=ja_JP.UTF-8 ./a.out
/path/to/nofile: そのようなファイルやディレクトリはありません
errno 2: そのようなファイルやディレクトリはありません

$ LANG=C ./a.out
/path/to/nofile: No such file or directory
errno 2: No such file or directory
関連ページ
    概要 以下のシステムコールに関する、C 言語のサンプルコードです。 fork execv exit wait 子プロセスを fork で作成して execve でプログラムを実行 以下のプログラムでは、シェルで echo コマンドを実行したときと同じように、シェルに相当する親プロセスで fork で子プロセスを生成して、子プロセス内で
    概要 unshare コマンドを用いると、プロセス内でのみ有効なマウントポイントを作成できます。内部的には unshare システムコールが発行されます。 unshare コマンド → man 1 unshare unshare システムコール → man 2 unshare 動作の理解 事前準備
    概要 メモリ操作に関するシステムコールを利用した C 言語のサンプルコードを記載します。 ページサイズの確認 (getpagesize) OS はメモリを複数のページに分割して管理しています。一つのページのサイズは以下のコマンドで確認できます。通常は 4kb です。 getconf PAGESIZE 4096
    概要 プロセスの情報を取得および設定するためのシステムコールに関する、C 言語のサンプルコードです。 各種 ID プロセスID の取得 (getpid/getppid) コマンドラインから直接実行したプログラムの親プロセスはシェルになるため getppid() で取得されるプロセスID は と同じ値になります。
    概要 FFI (Foreign Function Interface) の一つである ctypes を利用すると、C 言語のライブラリを Python から利用できます。サンプルコードを記載します。 適宜参照するための公式ドキュメント libm の sqrt を利用する例 main.py #!/usr/bin/python # -*- coding: utf-8 -*- from c
    概要 ファイル記述子 (File Descriptor) に関連するシステムコールを利用した C 言語のサンプルコードを記載します。 ファイルの読み書き open/close main.c #include <unistd.h> #include <fcntl.h> #include <stdio.h> int main() { int fd_r, fd_w; // 読
    概要 よく使う python ライブラリのサンプルコード集です。 JSON #!/usr/bin/python # -*- coding: utf-8 -*- import json arr = [1, 2, {'xxx': 3}] # オブジェクト ←→ JSON 文字列 jsonStr = json.dumps(arr) arr2 = json.loads(jsonStr) # オ
    コマンドのエイリアスを登録する (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