【42】pipexで使うシステムコール（プロセス制御）

こんにちは。

42Tokyo本科生のなかじです。

今回は、前回投稿したpipexという課題で用いることが出来るシステムコールの中でもプロセス制御関連についてまとめてみました。

## そもそもシステムコールとは何か？
システムコールとは、OSカーネルの機能や関数を呼び出すために使用する機能です。

例えば、C言語においてはmalloc() という動的メモリ確保を行うライブラリ関数は、その関数内においてsrbk() というシステムコールを呼び出すことでヒープ拡張が行われています。

## execve()
### 使用目的
- 現在のプロセスで新しいプログラムを実行
- プロセスのテキスト、データ、スタック、ヒープを新しいプログラムで置き換え
### 使い方
```c
int execve(const char *pathname, char *const argv[], char *const envp[]);
```
- pathname: 実行するプログラムのパス
- argv: コマンドライン引数の配列
- envp: 環境変数の配列
### 使用例
```c
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がセット)
### 特徴
:::note info
1. 保持される情報：
- プロセスID（PID）
- 親プロセスID（PPID）
- プロセスグループID
- オープンしているファイルディスクリプタ（O_CLOEXECフラグがないもの）
:::
### 重要な点
:::note info
- 呼び出しが成功すると元のプログラムには戻らない
- ファイルディスクリプタは継承される
- argvとenvpはNULLで終端する必要がある
- 実行権限が必要
:::

## exit()
### 使用目的
- プロセスを終了する
- 終了ステータスを親プロセスに返す
- リソースの解放を行う
### 使い方
```c
void exit(int status);
```

- status: 終了ステータス（0-255）
    - 0: 正常終了
    - 非0: エラー終了
### 使用例
```C
void cleanup(void) {
    printf("Cleaning up...\n");
}

int main(void) {
    atexit(cleanup);  // 終了時に実行する関数を登録
    
    if (some_error) {
        exit(1);  // エラー終了
    }
    
    // 処理成功
    exit(0);  // 正常終了
}
```
### 戻り値
- なし
### 特徴
:::note info
1. 終了処理の順序：
- atexit登録関数の実行（逆順）
- 標準入出力のバッファをフラッシュ
- 一時ファイルの削除
- _exit()システムコールの呼び出し
:::
### 重要な点
:::note info
- バッファリングされた出力は自動的にフラッシュされる
- 子プロセスは親プロセスにステータスを返す
- 親プロセスはwait()で終了ステータスを取得可能
- atexit()で登録した関数は必ず実行される
- リソースは自動的に解放される
:::

## fork()
### 使用目的
- 新しいプロセス（子プロセス）を作成
- 現在のプロセス（親プロセス）の複製を生成
### 使い方
```C
pid_t fork(void);
```
### 使用例
```c
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がセット)
### 特徴
:::note info
1. 複製される要素：
- メモリ空間
- ファイルディスクリプタ
- 各種属性とフラグ
:::
### 重要な点
:::note info
- 子プロセスは親プロセスの完全な複製
- fork後は親子で別々の実行パスを取る
- ファイルディスクリプタは共有される
- メモリは実際の書き込み時にコピー（Copy-on-Write）
:::

## wait()
### 使用目的
- 子プロセスの終了を待つ
- 子プロセスの終了ステータスを取得
- ゾンビプロセスの防止
### 使い方
```c
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
```
### 使用例
```C
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));
    }
}
```
### 終了ステータスのマクロ
```C
WIFEXITED(status)// 正常終了したか
WEXITSTATUS(status)// exit()の引数値
WIFSIGNALED(status)// シグナルで終了したか
WTERMSIG(status)// 終了シグナル番号
```
- status: 子プロセスの終了状態を格納する変数へのポインタ
### 戻り値
- 成功: 終了した子プロセスのPID
- 失敗: -1 (errnoがセット)
### 特徴
:::note info
- 子プロセスがない場合はエラー
- 既に終了している子プロセスの情報も取得可能
- ゾンビプロセス防止に重要
- シグナルハンドラでの使用時は注意が必要
:::

## waitpid()
### 使用目的
- 特定の子プロセスの終了を待つ
- より柔軟な待ち合わせ制御
- 非ブロッキング待ち合わせの実現
### 使い方
```c
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がセット)
### 特徴
:::note info
1. wait()より柔軟な制御が可能
2. ノンブロッキング動作が可能
3. 特定のプロセスやグループを指定可能
4. 停止/再開の監視も可能
5. 複数の子プロセスの管理に適している
:::