C 言語から Linux OS の各システムコールを利用する場合を想定して、関連情報をまとめます。
プログラムを実行するとき、CPU のレジスタの一つであるプログラムカウンタは、実行するプログラムが展開されたメモリ上のアドレスを指します。CPU が実行するプログラムには、OS のカーネルと、カーネルが管理するユーザプログラムがあります。CPU のプログラムカウンタは、これら複数のプログラムのメモリアドレスを次々と切り換えながら指すことで、見かけ上、複数のプログラムを同時に実行します。これをプリエンプティブ・マルチタスク (preemptive multitask) とよびます。プロセスとして動作するユーザプログラム間の切り換えはコンテキストスイッチという仕組みを利用します。
カーネルとユーザプログラムは異なる CPU モードで動作します。それぞれ、特権モード (スーパーバイザモード、カーネルモード) とユーザモードで動作しており、例えばユーザモードではハードウェアを操作する CPU 命令を出すことができません。ユーザプログラムからハードウェアを操作したい場合は、カーネルが提供するシステムコールというインタフェースを用いて間接的に操作します。このインタフェースを介することにより、ハードウェアを操作する CPU 命令を、不具合を含む可能性のあるユーザプログラムからでも「安全に」出すことができます。ハードウェアを操作するような CPU 命令を出すためには、ユーザプログラムの動作権限を一時的に特権モードに切り換える必要があり、このときもコンテキストスイッチの仕組みを利用します。
カーネルが提供するシステムコールは、アセンブラで記述して呼び出すこともできますが、例えば C 言語の場合、より簡単に利用できるライブラリ関数が各システムコールに対応するように libc 内で提供されています。libc を利用すると、OS の違いによって存在したりしなかったりするシステムコールの差異を吸収することもできます。
CentOS の場合は man-pages をインストールしておきます。
sudo yum install man
sudo yum install man-pages
man コマンド実行時にはセクション番号を指定できます。例えば chmod はコマンドとシステムコールでそれぞれ別物であるため、システムコールのマニュアルを読みたい場合は man 2 chmod
とします。
上述のとおり、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