Linuxのプロセスについて

## はじめに
Linuxにおいてのプロセスについて調べたことをまとめました。
内容的には今更感はありますが、まあ奥の深い分野ですので語り尽くす事は不可能に近いと思うので
出来る限り平易に書いてみます。

## プロセスとは
ググったところだとLinux上で動作中のプログラムのこと。

Linuxサーバ上で動いているプログラムは全てプロセスとして管理され、PIDという一意の番号を振られます。
そのサーバのプロセス状況を見ればだいたい何をしているかわかります。

Linuxだと大体initd もしくは systemdというプログラムが必ず1番目のプロセス(PID:1)となります。
OSとして必要なものも含め、一般的に認識されているプロセスとはinitd/systemdの子プロセスとして実行されます。

という事でプロセスを学ぶ事はLinuxを学ぶ上で必要不可欠な重要な要素と言えます。

## プロセスはどのように実行されるか
プロセスは実行されると、

1. メモリ上に必要な領域を確保して読み込まれる
2. CPUによってプログラムの上から順に処理される

という流れで実行されます。

メモリも仮想記憶やらページングやら色々ありますが、当記事ではプロセスを展開する領域程度の扱いとします。

ここではCPUを深堀りします。

CPUは1個あたり1つのプロセスしか同時に実行できません。

CPUで処理される時間は厳密に決められており(タイムスライスという)、この時間を使い切るとキューで待ちに並んでいる別のプロセスを処理します(コンテキストスイッチという)。

これを高速で行う事により、あたかも複数のプロセスが実行されているように見えるという話は誰もが聞いた事がある、有名な話かと思います。

で、ここで言うCPU1個というのは物理的なCPU(ソケット)の数ではなく、論理CPU(コア)の事を指します。
言い方を変えると、サーバ上ではコアの数だけプロセスが本当の意味での同時実行がされている、という事になります。

なのでコアの数はサーバの処理能力として、とても重要な要素となります。

## サーバのコア数の見方
そのサーバにコアがいくつあるのかは下記の方法で確認できます。

結論から言うと`/proc/cpuinfo`に書いてあります。

### 物理CPU数の見方
```
# grep physical.id /proc/cpuinfo | sort -u | wc -l
2
```
### 物理CPU毎のコア数の見方
```
# grep cpu.cores /proc/cpuinfo | sort -u
cpu cores       : 4
```

### 論理CPU数の見方
```
# grep processor /proc/cpuinfo | wc -l
16
```

はい、計算してみると数が合いませんね。

2(物理CPU数) × 4(物理CPUあたりのコア数) = 8(全コア数)となりそうですが、
ここの計算がずれるのはハイパースレッディングが有効になっているためですね。

なので↑の場合はコア数は8、
ハイパースレッディングが有効になって疑似的に倍の16と認識されている、
という事です。

ただし、ハイパースレッディングは1つのコアの隙間を有効利用する技術ですので、
単純倍率でさばける訳ではなく、数は2倍でも良くて1.2-1.3倍程度の向上と言われています。

当然、HT込みで16コアより純粋にコア数で16の方が遥かに処理が早いです。

場合によっては効率が落ちるとも言われているので
必ずしも有効にするべきという訳ではないようですね。

なので指標の軸としてはとりあえず純粋なコア数で見るべきかなーと思います。

という事で、プロセスの処理効率に密接に関わる論理CPU(コア)についてわかったかと思います。
CPUの処理指標としてはスループット、レイテンシという考え方がありますが、これは別記事にしようと思います。

## プロセス処理に関する技術

### プロセスを実行する時の動き
プログラムを実行するとプロセスが生成される。
プロセスの生成はfork と execveというシステムコールが使われる。

fork(内部的にはclone)はforkを呼び出したプロセスの複製を作成して
複製元が親プロセス、複製先が子プロセスの関係になる。
forkはコアの処理的に重めなので多発し過ぎるとオーバーヘッドになると言われている。

execveはまったく別のプロセスを生成する場合に使われる。
execveを実行するとプロセス数が増える訳ではなく、実行したプロセスを別のバイナリ実行用プロセスに置き換えるという形になる。

要約すると、
execve: PID は変えずにプロセスのみを変える
fork: 異なる PID で同一のプロセスを作成する
という違いがある。

forkは子プロセスが作成されているケースでよく見るけど、
execveは見た記憶がないなぁと思ったけど、切り替わるタイミングなんて恐らく視認できないだろうから無理ないと思われ。

例えばサーバにログインする場合、 実はloginプロセスというものが実行されているが、
正常にログインされた際にはbashにexecveされているようなので視認できるケースは少ないと思います。

### タイムスライス
1つのコア上で処理されるプロセスの処理持ち時間。
クォンタム(Quantum)とも言う。

この時間を経過した場合、待ちプロセスの処理に切り替わる。
※実はどのプロセスに切り替わるかはちょっと複雑であり、キューに入った順番といった単純なものではない

このプロセスの切り替わりをコンテキストスイッチと言う。

待ちプロセスがない場合は継続して処理される。

また、タイムスライス時間を使い切らなくても優先度の高い処理が出てくると強制的に切り替わりが発生する。
これはいわゆる割り込み処理と言われている(そのまんまですな)。

タイムスライスの時間はシステム負荷に応じて動的に調整される。
ざっくり言うとコア数が増える程長く、プロセスが増える程短くなる。

これはコア数が増えれば一度に実行出来るプロセス数が増えるので、1つのコアでの切り替え頻度を増やす必要性が減るため。

### コンテキストスイッチ
コア上で動作するプロセスが切り替わる事。
コンテキストスイッチはプロセスがいかなるコードを実行中であろうとも容赦なく発生する。

コンテキストスイッチはマルチタスクを実現する上でとても重要だが、オーバヘッドの要因とも見られやすい。

これは処理途中のプロセスで使用しているコアの状態を退避したり、もしくは次に処理するプロセスで使用するコア状態を読み込んだりするため。
※ちなみにこのコア状態の事をコンテキストと言います。
※これをスイッチするのでコンテキストスイッチ

とは言え、頻度が少なすぎると他プロセスがユーザから止まって見えたりするし、
頻度が高いとオーバヘッドの要因となる。

バランスが大事という事ですね(投げやり)

## プロセスの状態について
プロセスの状態は大きく4つに分類されます。

| 表記 | 状態 | 説明 |
| --- | --- | --- |
| R | 処理、処理待ち | 処理されているもしくはCPU処理待ち |
| S | スリープ(割込み可な待ち) | 主な待ち理由は時間、ユーザ操作、パケット受信 |
| D | スリープ(割込み不可な待ち) | 主な待ち理由はディスクI/O |
| Z | ゾンビ | プロセス終了後に親プロセスが終了状態を受け取るのを待っている |

試しにサーバのプロセス状態を見てみるとわかりますが、
表示されるプロセスのほとんどがスリープ状態である事がわかります。

これは実行されるプロセス数はコア数に依るというのと、イベント待ち等で
ほとんどのプロセスは処理される契機を待っているスリープ状態である、という事になります。

この辺りがわかっていると、psコマンドでプロセス状態をぱーっと見た時にDが多かったりするとディスクIOでボトルネックになってるのかなーとか、Zが多いとなんらかの理由で終了するべきプロセスが解放しきれず、無駄なリソース(メモリとか)を消費しているのかなー、といった異常が判別できます。

ちなみにLoadAverageというものがありますが、
これは実行待ちのプロセス と ディスクI/O待ちのプロセスの数が換算されて表示されます。
※実行中は換算されないっぽい

なので、ステータスで言うと、R 及び Dのプロセスが対象となります。
今まさに処理したいんだけど待たされているプロセス、という事ですね。
この処理待ち状態のプロセスの数の1, 5, 15分間の平均値がLoadAverageとして算出されます。

Linuxカーネルは処理待ちプロセスの数をシステムの負荷として定義しているという事ですね。

SとかZが増えてもLoadAverageは増えないみたいですねー

ここで紹介したプロセスの状態はあくまで基本的なものなので実際に見ると、
SsだかSslだとかI<とか結構種類があります。
細かいところはググりましょう。

## プロセスの状態の見方
psコマンドでのプロセスの状態の見方について整理します。

### psで見れる情報の性質

psやtopは性質として実行した瞬間の状況を表示する動きをします。
その時点での詳細は見れますが、過去の状況を知る情報は少ないです。
という事は念頭に置いておきましょう。

余談ですが対比として過去の状況を確認するコマンドとしてはsarやvmstat等があります。
こちらは(実行されていれば)その時点毎の状況を確認する事はできますが、
ある程度丸められているため概況を知るに留まるかと思います。

障害発生時の概況を見て怪しい箇所のあたりを付け、改めて瞬間の詳細を見る、
というのがよくある流れかと思います。

### 見てみます
では実行してみます。
※その辺のAmazon Linux2で実行しています。

```
# ps
  PID TTY          TIME CMD
18449 pts/0    00:00:00 sudo
18450 pts/0    00:00:00 su
18452 pts/0    00:00:00 bash
25216 pts/0    00:00:00 ps
```

単純な実行のみだとほとんど情報が見えません。

まあ、細かいところはググってもらうとして、適度にオプションを付けて実行します。

```
# # ps auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         2  0.0  0.0      0     0 ?        S    May08   0:00 [kthreadd]
root         4  0.0  0.0      0     0 ?        I<   May08   0:00  \_ [kworker/0:0H]
root         6  0.0  0.0      0     0 ?        I<   May08   0:00  \_ [mm_percpu_wq]
root         7  0.0  0.0      0     0 ?        S    May08   0:01  \_ [ksoftirqd/0]
root         8  0.0  0.0      0     0 ?        I    May08   0:07  \_ [rcu_sched]
root         9  0.0  0.0      0     0 ?        I    May08   0:00  \_ [rcu_bh]
root        10  0.0  0.0      0     0 ?        S    May08   0:00  \_ [migration/0]
root        11  0.0  0.0      0     0 ?        S    May08   0:01  \_ [watchdog/0]
root        12  0.0  0.0      0     0 ?        S    May08   0:00  \_ [cpuhp/0]
root        13  0.0  0.0      0     0 ?        S    May08   0:00  \_ [kdevtmpfs]
root        14  0.0  0.0      0     0 ?        I<   May08   0:00  \_ [netns]
root        20  0.0  0.0      0     0 ?        S    May08   0:00  \_ [xenbus]
root        21  0.0  0.0      0     0 ?        S    May08   0:00  \_ [xenwatch]
root       168  0.0  0.0      0     0 ?        S    May08   0:00  \_ [khungtaskd]
root       169  0.0  0.0      0     0 ?        S    May08   0:00  \_ [oom_reaper]
root       170  0.0  0.0      0     0 ?        I<   May08   0:00  \_ [writeback]
root       172  0.0  0.0      0     0 ?        S    May08   0:00  \_ [kcompactd0]
・
・
＜中略＞
・
・
root      3216  0.0  1.8 544424 18400 ?        Ssl  May08   3:34 /usr/bin/amazon-ssm-agent
root      3313  0.0  0.1  20596  1196 ?        Ss   May08   0:00 unit: main [unitd]
nobody    3315  0.0  0.0  20596   252 ?        S    May08   0:00  \_ unit: controller
nobody    3316  0.0  0.0  20596   256 ?        S    May08   0:00  \_ unit: router
root      3341  0.0  0.2 123772  2184 ?        Ss   May08   0:00 nginx: master process /usr/sbin/nginx
nginx     3343  0.0  0.4 124228  5000 ?        S    May08   0:00  \_ nginx: worker process

```

親プロセスの情報とかを見る場合は`l`を付けてみたりします。

```
# ps alx
F   UID   PID  PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
4     0     1     0  20   0 117344  5480 SyS_ep Ss   ?          0:12 /usr/lib/systemd/systemd --switched-root --system --deserialize 21
1     0     2     0  20   0      0     0 -      S    ?          0:00 [kthreadd]
1     0     4     2   0 -20      0     0 -      I<   ?          0:00 [kworker/0:0H]
1     0     6     2   0 -20      0     0 -      I<   ?          0:00 [mm_percpu_wq]
1     0     7     2  20   0      0     0 -      S    ?          0:01 [ksoftirqd/0]
1     0     8     2  20   0      0     0 rcu_gp I    ?          0:07 [rcu_sched]
1     0     9     2  20   0      0     0 -      I    ?          0:00 [rcu_bh]
1     0    10     2 -100  -      0     0 -      S    ?          0:00 [migration/0]
5     0    11     2 -100  -      0     0 -      S    ?          0:01 [watchdog/0]
1     0    12     2  20   0      0     0 -      S    ?          0:00 [cpuhp/0]
5     0    13     2  20   0      0     0 -      S    ?          0:00 [kdevtmpfs]
1     0    14     2   0 -20      0     0 -      I<   ?          0:00 [netns]
1     0    20     2  20   0      0     0 -      S    ?          0:00 [xenbus]
1     0    21     2  20   0      0     0 -      S    ?          0:00 [xenwatch]
1     0   168     2  20   0      0     0 watchd S    ?          0:00 [khungtaskd]
1     0   169     2  20   0      0     0 -      S    ?          0:00 [oom_reaper]
1     0   170     2   0 -20      0     0 -      I<   ?          0:00 [writeback]
1     0   172     2  20   0      0     0 -      S    ?          0:00 [kcompactd0]
1     0   173     2  25   5      0     0 -      SN   ?          0:00 [ksmd]
1     0   174     2  39  19      0     0 -      SN   ?          0:00 [khugepaged]
1     0   175     2   0 -20      0     0 -      I<   ?          0:00 [crypto]
1     0   176     2   0 -20      0     0 -      I<   ?          0:00 [kintegrityd]
1     0   178     2   0 -20      0     0 -      I<   ?          0:00 [kblockd]
1     0   531     2   0 -20      0     0 -      I<   ?          0:00 [md]
1     0   534     2   0 -20      0     0 -      I<   ?          0:00 [edac-poller]
・
・
＜中略＞
・
・
5     0  3341     1  20   0 123772  2184 -      Ss   ?          0:00 nginx: master process /usr/sbin/nginx
5   997  3343  3341  20   0 124228  5000 SyS_ep S    ?          0:00 nginx: worker process
4     0 18407  3165  20   0 141896  8256 SyS_po Ss   ?          0:00 sshd: ec2-user [priv]
5  1000 18419 18407  20   0 141896  4548 core_s S    ?          0:00 sshd: ec2-user@pts/0
0  1000 18420 18419  20   0 124792  4012 -      Ss   pts/0      0:00 -bash
4     0 18449 18420  20   0 196200  4804 SyS_po S    pts/0      0:00 sudo su -
4     0 18450 18449  20   0 190524  4076 -      S    pts/0      0:00 su -
4     0 18452 18450  20   0 125048  4396 -      S    pts/0      0:00 -bash
4    89 21816  3131  20   0  90568  6764 SyS_ep S    ?          0:00 pickup -l -t unix -u
1     0 22724     2  20   0      0     0 -      I    ?          0:00 [kworker/u30:0]
1     0 24599     2  20   0      0     0 -      I    ?          0:00 [kworker/0:2]
1     0 25158     2  20   0      0     0 -      I    ?          0:00 [kworker/0:0]
1     0 25487     2  20   0      0     0 -      I    ?          0:00 [kworker/0:1]
4     0 25557 18452  20   0 156052  2168 -      R+   pts/0      0:00 ps alx
1     0 31709     2  20   0      0     0 -      I    ?          0:00 [kworker/u30:1]
```

眺めてみると、大体PPID(親プロセス)が1か2なので、
systemdが起源である事がわかるかと思います。

で、あれPID2ってなんだろうと思って調べたら`kthreadd`というプロセスで、
カーネルスレッド・デーモンとの事でした。

これは後で調べよう。。。

とりあえず、親プロセスなし(PPID：0)は
カーネルが起動する時に読み込まれるsystemdとkthreaddの2つので、
それ以外のプロセスは全てsystemd or kthreaddから派生したプロセスであるという事ですね。

## sshアクセス時のプロセスの動きを見てみる
大体話は終わったのですが、最後に試しにsshでサーバアクセスした時の
プロセスの動きを見てみようと思います。

まずは現状の確認

```
# ps auxf
root      3165  0.0  0.7 106288  7580 ?        Ss   May08   0:04 /usr/sbin/sshd -D
```
本来は状態確認でsshアクセスしていますが、抜粋しています。

大元のsshdのプロセスIDが3165なのでこのプロセスに対してstraceしてみます。

```
# strace -p 3165
strace: Process 3165 attached
select(10, [3 4 6], NULL, NULL, NULL)   = 1 (in [6])
```

とりあえずこの状態で止まっている。
ではsshでアクセスしてみます。

```
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=26733, si_uid=0, si_status=255, si_utime=0, si_stime=0} ---
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 255}], WNOHANG, NULL) = 26733
wait4(-1, 0x7ffe29150484, WNOHANG, NULL) = 0
rt_sigaction(SIGCHLD, NULL, {0x55814ff8bfa0, [], SA_RESTORER, 0x7f2a66f78720}, 8) = 0
rt_sigreturn({mask=[]})                 = -1 EINTR (Interrupted system call)
select(10, [3 4], NULL, NULL, NULL)     = 1 (in [3])
accept(3, {sa_family=AF_INET, sin_port=htons(50150), sin_addr=inet_addr("xxx.xxx.xxx.xxx")}, [16]) = 5
fcntl(5, F_GETFL)                       = 0x2 (flags O_RDWR)
pipe([6, 7])                            = 0
socketpair(AF_LOCAL, SOCK_STREAM, 0, [8, 9]) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f2a69bd8010) = 27331
close(7)                                = 0
write(8, "\0\0\2\275\0", 5)             = 5
write(8, "\0\0\2\270\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nHostKey"..., 700) = 700
close(8)                                = 0
close(9)                                = 0
close(5)                                = 0
getpid()                                = 3165
getpid()                                = 3165
getpid()                                = 3165
select(10, [3 4 6], NULL, NULL, NULL
```

するとバーッと色々表示される訳ですが、
途中、`clone(・・中略・・ = 27331`

みたいな表示で子プロセスを生成しています。

別コンソールからプロセス状況を見てみると

```
# ps auxf
root      3165  0.0  0.7 106288  7580 ?        Ss   May08   0:04 /usr/sbin/sshd -D
root     27331  0.0  0.6 106288  6992 ?        Ss   14:47   0:00  \_ sshd: [accepted]
sshd     27028  0.0  0.5 110624  5256 ?        S    14:47   0:00      \_ sshd: [net]
```

PID3165の子プロセスとして27331のプロセスが生成されていますね。
そしてaccepted状態になってますね。

```
# ps auxf
root      3165  0.0  0.7 106288  7580 ?        Ss   May08   0:04 /usr/sbin/sshd -D
root     27331  0.0  0.8 141896  8264 ?        Ss   14:52   0:00  \_ sshd: ec2-user [priv]
ec2-user 27366  0.0  0.4 141896  4676 ?        S    14:53   0:00      \_ sshd: ec2-user@pts/2
ec2-user 27368  0.0  0.3 124792  3924 pts/2    Ss   14:53   0:00          \_ -bash
ec2-user 27441  0.0  0.3 160400  3952 pts/2    R+   14:54   0:00              \_ ps auxf
```

最後までログインしてみると、pts /2を割り当てたのちに
loginプロセスをfork→bashにexecveという流れでいわゆるsshアクセスした状態となりました。
※視認できませんが
※PID 27368をstraceできるとexecveしているのが記録できるかもですね

sshでログインするだけでもかなりプロセスの動きがある事がわかりますね。