こんにちは。
42Tokyo本科生のなかじです。
今回は、前回投稿したpipexという課題で用いることが出来るシステムコールの中でもプロセス制御関連についてまとめてみました。
そもそもシステムコールとは何か?
システムコールとは、OSカーネルの機能や関数を呼び出すために使用する機能です。
例えば、C言語においてはmalloc() という動的メモリ確保を行うライブラリ関数は、その関数内においてsrbk() というシステムコールを呼び出すことでヒープ拡張が行われています。
execve()
使用目的
- 現在のプロセスで新しいプログラムを実行
- プロセスのテキスト、データ、スタック、ヒープを新しいプログラムで置き換え
使い方
int execve(const char *pathname, char *const argv[], char *const envp[]);
- pathname: 実行するプログラムのパス
- argv: コマンドライン引数の配列
- envp: 環境変数の配列
使用例
pid_t pid = fork();
if (pid == 0) { // 子プロセス
char *const args[] = {"ls", "-l", NULL};
char *const env[] = {
"PATH=/bin:/usr/bin",
"USER=username",
NULL
};
if (execve("/bin/ls", args, env) == -1) {
perror("execve failed");
exit(1);
}
} else { // 親プロセス
wait(NULL); // 子プロセスの終了を待つ
}
戻り値
- 成功: 返らない(新しいプログラムが実行される)
- 失敗: -1 (errnoがセット)
特徴
- 保持される情報:
- プロセスID(PID)
- 親プロセスID(PPID)
- プロセスグループID
- オープンしているファイルディスクリプタ(O_CLOEXECフラグがないもの)
重要な点
- 呼び出しが成功すると元のプログラムには戻らない
- ファイルディスクリプタは継承される
- argvとenvpはNULLで終端する必要がある
- 実行権限が必要
exit()
使用目的
- プロセスを終了する
- 終了ステータスを親プロセスに返す
- リソースの解放を行う
使い方
void exit(int status);
- status: 終了ステータス(0-255)
- 0: 正常終了
- 非0: エラー終了
使用例
void cleanup(void) {
printf("Cleaning up...\n");
}
int main(void) {
atexit(cleanup); // 終了時に実行する関数を登録
if (some_error) {
exit(1); // エラー終了
}
// 処理成功
exit(0); // 正常終了
}
戻り値
- なし
特徴
- 終了処理の順序:
- atexit登録関数の実行(逆順)
- 標準入出力のバッファをフラッシュ
- 一時ファイルの削除
- _exit()システムコールの呼び出し
重要な点
- バッファリングされた出力は自動的にフラッシュされる
- 子プロセスは親プロセスにステータスを返す
- 親プロセスはwait()で終了ステータスを取得可能
- atexit()で登録した関数は必ず実行される
- リソースは自動的に解放される
fork()
使用目的
- 新しいプロセス(子プロセス)を作成
- 現在のプロセス(親プロセス)の複製を生成
使い方
pid_t fork(void);
使用例
pid_t pid = fork();
if (pid == -1) {
// fork失敗
perror("fork failed");
exit(1);
} else if (pid == 0) {
// 子プロセス
printf("Child: PID = %d, Parent PID = %d\n",
getpid(), getppid());
} else {
// 親プロセス
printf("Parent: PID = %d, Child PID = %d\n",
getpid(), pid);
wait(NULL); // 子プロセスの終了を待つ
}
戻り値
- 親プロセス: 子プロセスのPID
- 子プロセス: 0
- 失敗: -1 (errnoがセット)
特徴
- 複製される要素:
- メモリ空間
- ファイルディスクリプタ
- 各種属性とフラグ
重要な点
- 子プロセスは親プロセスの完全な複製
- fork後は親子で別々の実行パスを取る
- ファイルディスクリプタは共有される
- メモリは実際の書き込み時にコピー(Copy-on-Write)
wait()
使用目的
- 子プロセスの終了を待つ
- 子プロセスの終了ステータスを取得
- ゾンビプロセスの防止
使い方
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
使用例
pid_t pid = fork();
if (pid == 0) {
// 子プロセス
exit(5);// 終了ステータス5で終了
} else {
// 親プロセス
int status;
pid_t child_pid = wait(&status);
if (WIFEXITED(status)) {
// 正常終了の場合
printf("Child %d exited with status %d\n",
child_pid, WEXITSTATUS(status));
}
}
終了ステータスのマクロ
WIFEXITED(status)// 正常終了したか
WEXITSTATUS(status)// exit()の引数値
WIFSIGNALED(status)// シグナルで終了したか
WTERMSIG(status)// 終了シグナル番号
- status: 子プロセスの終了状態を格納する変数へのポインタ
戻り値
- 成功: 終了した子プロセスのPID
- 失敗: -1 (errnoがセット)
特徴
- 子プロセスがない場合はエラー
- 既に終了している子プロセスの情報も取得可能
- ゾンビプロセス防止に重要
- シグナルハンドラでの使用時は注意が必要
waitpid()
使用目的
- 特定の子プロセスの終了を待つ
- より柔軟な待ち合わせ制御
- 非ブロッキング待ち合わせの実現
使い方
pid_t waitpid(pid_t pid, int *status, int options);
- pid: 待つプロセスのPID
- pid > 0: 指定したPIDのプロセス
- pid == -1: 任意の子プロセス(waitと同じ)
- pid == 0: 同じプロセスグループの子プロセス
- pid < -1: プロセスグループID = |pid|の子プロセス
- status: 終了状態を格納する変数
- options: 動作オプション
- WNOHANG: ノンブロッキング
- WUNTRACED: 停止子プロセスの状態も報告
- WCONTINUED: 再開子プロセスの状態も報告
戻り値
- 成功: 終了/停止した子プロセスのPID
- 子プロセスがまだ終了していない(WNOHANG時): 0
- 失敗: -1 (errnoがセット)
特徴
- wait()より柔軟な制御が可能
- ノンブロッキング動作が可能
- 特定のプロセスやグループを指定可能
- 停止/再開の監視も可能
- 複数の子プロセスの管理に適している