[Linux] プロセスの基本とコマンドをまとめた

# はじめに
LPIC-1について勉強している。プロセスやプロセスに関連するコマンドをまとめた

# 動作環境
- Docker環境
- ubuntu:22.04

# プロセスとは
- 動作中のプログラムのことを指す。
## 親プロセスと子プロセス

- すべてのプロセスは、他のプロセスから作られる。
    - 親プロセス : 他のプロセスを作り出すプロセス
    - 子プロセス  : 親プロセスによって作られたプロセス

システム起動時にカーネルよって生成されるPID1(init, systemd)のプロセスには親がいない。


## プロセスの生成について

### プロセス生成の2つの目的
- 同じプログラムの並列処理
    - 同じ処理を複数のプロセスに分けて実行
<br>

- 別のプログラムの実行
    - 新しいプログラムを起動
    - 例 : bashからlsコマンドを実行

### プロセスの生成の基本

新しいプロセスが生まれるときは、基本的には以下の2つのステップを踏む。

1. fork()システムコール
    - プロセスのコピーを作成
    - コピー元のプロセス : 親プロセス
    - 生成されたプロセス : 子プロセス
2. exec()システムコール
    - コピーしたプロセスを別のプログラムに置き換える。

---

### fork()システムコール

fork()は、「現在実行中のプロセスのコピーして新しいプロセスを作る」システムコール

コピーされたプロセス（子プロセス）はメモリの状態を含む親プロセスの実行状態を完全にコピーする。ただし、実際はCopy-on-Writeという仕組みが使われており、fork()直後は、親子プロセスは物理メモリを共有し、どちらかが書き込みを行った時にその部分をコピーをする

プロセスIDはコピーされず新たに割り当てられる。

子プロセスの実行は、`fork`の呼び出し直後から開始される。

親プロセスとメモリの状態は同じなので、fork呼び出し前に定義された変数などはそのまま利用する。


以下のプログラムはforkの戻り値と親プロセス、子プロセスのPIDを出力するPythonプログラムである

```python

import os
import sys
ret = os.fork()
print("fork()の戻り値:", ret)
if ret == 0: 
    print(f"子プロセス : PID ={os.getpid()} , 親のプロセス : PID={os.getppid()}")
    exit()
elif ret > 0: 
    print(f"親プロセス : PID ={os.getpid()} , 子のプロセス : PID={ret}") 
else:
    print("fork()の実行に失敗しました")
    sys.exit(1)

```

>**出力** 

```python
fork()の戻り値: 27533
親プロセス : PID =27532 , 子のプロセス : PID=27533
fork()の戻り値: 0
子プロセス : PID =27533 , 親のプロセス : PID=27532
```

fork()は呼び出し後、親プロセスと子プロセスに異なる値を返す。

| プロセス | 戻り値 | 意味 |
| --- | --- | --- |
| 親プロセス | 子のPID | 作成した子プロセスのID |
| 子プロセス | 0 | 自分が子プロセスであることを示す |
| エラー時 | -1 | fork()の実行に失敗 |

出力を見てみると、fork()の戻り値が27533のときは親プロセスの実行結果である。この戻り値は子プロセスのPIDと一致していることがわかる。

fork()の戻り値が0のときは、子プロセスの実行結果である。このforkの戻り値によって、親プロセスであるか子プロセスであるかを判断できる。

### execシステムコール

多くの場合、fork()で作った子プロセスは、exec()を使って別のプログラムに置き換えられる。

> **実際の使用例**
> 

```python
import os
import sys

ret = os.fork()

if ret == 0:
    # 子プロセス: lsコマンドを実行
    print("子プロセス: PID =", os.getpid(), ", 親のプロセス: PID =", os.getppid())
    print("子プロセスでlsコマンドを実行します")
    os.execve("/bin/ls", ["ls", "-l"], os.environ)
    # execve()が成功すると、ここより下の行は実行されない
    print("hogehoge")
    
elif ret > 0:
    # 親プロセス: 子の終了を待つ
    print("親プロセス: PID =", os.getpid(), ", 子のプロセス: PID =", ret)
    print("親プロセス: 子プロセスの終了を待機中...")
    os.waitpid(ret, 0)
    print("子プロセスが終了しました")
```

**出力**

```python
親プロセス: PID = 30941 , 子のプロセス: PID = 30942
親プロセス: 子プロセスの終了を待機中...
子プロセス: PID = 30942 , 親のプロセス: PID = 30941
子プロセスでlsコマンドを実行します
total 48
-rw-r--r--  1 user  staff  520  6  9 11:32 fork.py
(以下省略)

子プロセスが終了しました
```

execve()を使って別のプログラムに書き換えている。exec()が成功すると、それ以降のコードは実行されない。出力を見ると`ls -l` が実行された後に、プログラムでは`print(”hogehoge”)` と書いてあるが、出力結果には、”hogehoge”は出力されていない

### プロセスの状態 

- プロセスは生存中に様々な状態を行き来する

| コード | 状態名 | 説明 | 具体例 |
| --- | --- | --- | --- |
| R | Running | 実行可能状態 (CPU使用中) | 処理中 |
| S | Sleeping | 割り込み可能な待機状態 | キーボード入力待ち |
| D | Uninterruptible Sleep | 割り込み不可能な待機 | ディスクI/O待ち |
| T | Stopped | 停止中 | Ctrl + Zで停止 |
| Z | Zombie | ゾンビプロセス | 終了したが親に回収されていない |
| I | Idle | アイドル状態のカーネルスレッド | システムの待機プロセス |

状態を確認するコマンドは以下の通り

```bash
ps aux
```

出力例

```bash
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
user     1  0.0  0.0   5296  3484 pts/0    Ss+  00:58   0:00 /bin/bash
user    12  0.0  0.0   5420  3556 pts/1    Ss   00:58   0:00 bash
user    49  0.0  0.0   7588  2912 pts/1    R+   02:59   0:00 ps aux
```
![スクリーンショット 2025-06-09 10.29.06.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3750206/6247b910-6f87-45a9-bf73-0df24ce05a9b.jpeg)
引用 :[［試して理解］Linuxのしくみ―実験と図解で学ぶOS、仮想マシン、コンテナの基礎知識【増補改訂版】](https://gihyo.jp/book/2022/978-4-297-13148-7) (kindle版 : p70)

### プロセスの終了

プロセスを終了させるには、`exit_group()` というシステムコールを呼ぶ。子プロセスが終了した後は、親プロセスが`wait()`や`waitpid()`といったシステムコールを呼び出すことで次の情報が得られる。

- プロセスの戻り値 :
    - 0~255の間になる
- シグナルによって終了したか
- 終了までにどれだけCPU時間を使ったか

これらの情報を得ることで、プロセスの戻り値によって、正常に終了したか異常終了していたかも判別でき、その時にはエラーログを出力するなどの対処も可能。

一般的に親プロセスは、子プロセスでリソースを圧迫させないように、適宜、子プロセスの終了状態を受けて、リソースをカーネルに開放させる必要がある。しかし、何らかの原因で親プロセスがwait()システムコールができない状態のプロセスを**ゾンビプロセス**と呼ぶ

このゾンビプロセスは、メモリやCPUリソースを消費するわけではない。しかし、プロセステーブルのエントリを占有し続けるため、大量のゾンビプロセスが発生するとプロセステーブルが溢れ、新しいプロセスが生成できない場合がある。

# コマンドについて

## psコマンド

プロセスを表示するコマンド

```python
ps          # 現在の端末のプロセスのみ表示
ps aux      # 全ユーザーの全プロセスを詳細表示
ps -ef      # 全プロセスをフル形式で表示
ps -p PID   # 特定のPIDのプロセス情報
ps -u USER  # 特定ユーザーのプロセス
```

- **`ps -ef`**
    - 全てのプロセスを表示するが、親プロセスのプロセスID (PPID)も表示する

## pstreeコマンド

プロセスをツリー形式で表示する。

```python
pstree
```

## topコマンド

リアルタイムでプロセスの状態を監視するコマンド

```python
top
```

リアルタイムで表示するだけでなく、表示の仕方を変更したり、表示する秒数(デフォルト3秒)を変更できるなど、キーを入力して画面を更新できる。

- P (大文字): CPU使用率順にソート
- M (大文字): メモリ使用率順にソート
- k: プロセスを終了させる（PIDを入力してkillシグナルを送る）
- s : 表示間隔の秒数を変更する
- q: topを終了する

など

出力例

```bash
top - 01:38:21 up  1:22,  0 users,  load average: 0.08, 0.13, 0.09
Tasks:   3 total,   1 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   3919.4 total,   3209.8 free,    325.1 used,    384.5 buff/cache
MiB Swap:   1024.0 total,   1024.0 free,      0.0 used.   3459.6 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    1 develop+  20   0    5296   3400   3144 S   0.0   0.1   0:00.00 bash
   24 develop+  20   0    5296   3824   3312 S   0.0   0.1   0:00.14 bash
   49 develop+  20   0    7860   3116   2732 R   0.0   0.1   0:00.00 top

```

## lsofコマンド

各プロセスが開いているファイル全てを一覧表示する。

```bash
lsof 
```

```bash
developer@49d2930c39c3:~$ lsof
COMMAND PID      USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
bash      1 developer  cwd    DIR   0,64     4096 33680507 /home/developer
bash      1 developer  rtd    DIR   0,64     4096 33680543 /
(以下省略)
```

- COMMAND: 実行されているコマンド名
- PID: プロセスID
- USER: 実行ユーザー
- FD: ファイルディスクリプタ（cwd: カレントディレクトリ, txt: 実行ファイル, mem: メモリマップドファイル, 1u: 標準出力など）
- TYPE: タイプ（REG: 通常ファイル, DIR: ディレクトリ, IPv4/IPv6: ソケットなど）
- DEVICE: デバイス番号
- SIZE/OFF: ファイルサイズまたはオフセット
- NODE: inode番号, プロトコル
- NAME: ファイル名やソケット情報

- ファイルのパスを引数に渡すと、そのファイルを使っているプロセスを表示する

```bash
lsof /path/to/file
```

### **pgrepコマンド**

- 特定のプロセスを見つけるためのコマンド

```bash
pgrep [option] パターン
```

### killコマンド

プロセスに対してシグナルを送り、終了させたり、動作を制限したりするコマンド

```bash
kill [option] プロセスID
```

1. **シグナル名で指定する**
    - 例) kill -KILL <PID> または kill -s KILL <PID>
2. **シグナル番号で指定する**
    - 例) kill -9 <PID>

| シグナル番号 | シグナル名 | 制御内容 |
| --- | --- | --- |
| 1 | SIGHUP | 再起動 |
| 6 | SIGABRT | 中断 |
| 9 | SIGKILL | 強制終了 |
| 15 | SIGTERM | 終了 |
| 17 | SIGSTOP | 停止 |
| 18 | SIGCONT | 再開 |

シグナル名を指定する際は、先頭の”SIG”を省いて入力する。

プロセスIDではなくプロセス名を指定したい時は、`killall`コマンドを使う


# 参考

https://gihyo.jp/book/2022/978-4-297-13148-7