はじめに
Linuxにおいてのプロセスについて調べたことをまとめました。
内容的には今更感はありますが、まあ奥の深い分野ですので語り尽くす事は不可能に近いと思うので
出来る限り平易に書いてみます。
プロセスとは
ググったところだとLinux上で動作中のプログラムのこと。
Linuxサーバ上で動いているプログラムは全てプロセスとして管理され、PIDという一意の番号を振られます。
そのサーバのプロセス状況を見ればだいたい何をしているかわかります。
Linuxだと大体initd もしくは systemdというプログラムが必ず1番目のプロセス(PID:1)となります。
OSとして必要なものも含め、一般的に認識されているプロセスとはinitd/systemdの子プロセスとして実行されます。
という事でプロセスを学ぶ事はLinuxを学ぶ上で必要不可欠な重要な要素と言えます。
プロセスはどのように実行されるか
プロセスは実行されると、
- メモリ上に必要な領域を確保して読み込まれる
- 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でログインするだけでもかなりプロセスの動きがある事がわかりますね。