コース内容#
プロセスとは#
- プロセスはプログラムのメモリ内のイメージであり、実行中のプログラムであり、プログラムのインスタンスであり、複雑な集合体である
- 開放されたメモリ空間、ユーザー情報、グループ情報、権限、使用中のリソース、実行中のコード、オープンファイルなどを含む
- それに対して
- ① プログラムとは何か
- プログラムはコンパイルされた実行可能なバイナリファイルであり、ディスク上に置かれている
- 普通のファイルであり、x 権限を持つ
- プログラムの集合はアプリケーションである
- プログラムはコンパイルされた実行可能なバイナリファイルであり、ディスク上に置かれている
- ② スレッドとは何か
- スレッドは一連の順序付けられた、CPU によって実行される命令を表す
- 一つのプロセスは一つ以上のスレッドで構成され、同時に命令を実行することができる
- [PS] プロセスは CPUリソース配分の基本単位であり、スレッドは CPUスケジューリングの基本単位である
- ① プログラムとは何か
fork#
子プロセスを作成する [プロセスインターフェース]
- man fork
- プロトタイプ + 説明
- 戻り値 [型:pid_t]:プロセス ID
- fork を呼び出したプロセス [親プロセス] をコピーして新しいプロセス [子プロセス] を作成し、親プロセスと子プロセスは相互独立のメモリ空間で実行される
- fork が完了すると、両者は同じ内容を持ち、その後のメモリの書き込みやファイルマッピングは互いに影響を与えない
- メモリが変更されるときにのみ、実際のコピーが発生する [書き込みコピーの概念]
- そうでなければ、同じメモリ空間を共有している
- 親子の主な違いは以下の通り:
- 子は自分だけのユニークな PID を持ち、既存の PID とは異なる
- 子が認識する親 PID [getppid] は実際の親 PID と同じである
- 子は親のメモリロックを継承しない
- 子のリソース使用量と CPU 使用時間は 0 にリセットされる
- 子は実行待ちの信号、セマフォ、記録ロック、タイマー、非同期 IO 操作を継承しない
- 戻り値
- 成功:親プロセスでは子の PID を返し、子プロセスでは 0 を返す
- 親は他の方法でその子の PID を取得できず、子は getppid を通じて親の PID を取得できる
- 失敗:-1 を返し、errno を設定する [子プロセスを作成できなかった]
wait#
プロセスの状態の変化を待つ
- man wait
- プロトタイプ
- wstatus [int *]:子プロセスの状態を返す
- 子プロセス内の return、exit の値など
- マクロを使用して解析する必要がある、例:WIFEXITED (wstatus)、詳細はコードデモ —wait— 二を参照
- 説明
- 待機対象:呼び出しプロセスの子
- 状態変化の状況:子が終了、信号で中断、信号で再開
- 終了した子がいる場合、
- wait コマンドはシステムに子に関連するリソースを解放させる
- そうでなければ [wait コマンドを実行しない]、終了した子プロセスはゾンビプロセスになる [👇]
- 死んだ子は親プロセスに認識されず、そのリソースは解放されない
- top を使用して確認できる
- ゾンビはゾンビプロセスである
- 一つの子が状態を変えた場合、wait コマンドはすぐに戻る
- そうでなければ、子が状態を変えるか信号で中断されるまでブロックされる
- 戻り値
- 終了した子の PID または - 1 を返す [エラーが発生し、errno を設定]
exec 族#
ファイルを実行する [すべてはファイル]
- man exec
- プロトタイプ
- 多くの兄弟がいる
- 説明
- 現在のプロセスイメージを全く新しいプロセスイメージで【置き換える】
- [子に全く新しい世界を持たせる]
- 最初の引数は実行するファイルの名前である
- path:完全なパス
- file:PATH 環境変数内のコマンドまたは完全なパスである
- 全体の族は次のように要約できる:"exec + l/v + p/e/pe"
- 引数名 arg は前の引数 path の引数を示す
- l-list、すべての引数を一つの文字列にまとめる [引数の渡し方]
- 通常、arg0 は実行するファイルの名前に関連しているべきである
- 必ず (char *) NULL で終わる必要がある
- v-vector、すべての引数を文字列配列にまとめる [引数の渡し方]
- 必ず null ポインタで終わる必要がある
- p-path、実行可能ファイルの検索範囲には PATH 環境変数が含まれる
- シェルのコマンド検索プロセスをコピーしたものである
- e-env、環境変数を指定することを許可する
- 変数 - 値のペア
- 戻り値
- エラーが発生した場合にのみ - 1 を返す
flock#
オープンファイルに対して操作提案ロックを行う
[本質的にはデータを保護するため]
- man 2 flock
- プロトタイプ + 説明
- ファイルディスクリプタ fd を通じて操作する
- 主に三つの操作
- LOCK_SH:共有ロック
- LOCK_EX:排他ロック
- 排他ロック:一人がアクセスしている場合、他の人はアクセスできない
- 例:多くの人が一つのトイレに入る
- LOCK_UN:ロック解除
- 戻り値
- 0、成功;-1、失敗
コードデモ#
fork#
一、バッファをコピーし、行バッファ
- 出力結果
- ❗ fork の後に出力関数がないのに、なぜ入力 suyelu を入力すると二つの suyelu が出力されるのか?
- 【事実】fork の後にコードが子プロセスにコピーされたが、子プロセスは fork の後のコードのみを実行する
- 【重要】バッファがコピーされており、その中に suyelu が残っている
- printf には改行文字がなく、標準 I/O は行バッファI/O であり、13 行が実行されてもバッファはフラッシュされない
- プログラムが終了するまで、バッファのフラッシュ条件はトリガーされない
- [PS] zsh では suyelu が一度しか出力されない可能性があるが、これは zsh の最適化か?bash では二つ出力される
二、親子プロセスは相互独立
- 出力結果
- ❗ 親プロセスは必ず先に実行されるのか?
- 必ずしもそうではない、親子プロセスは完全に独立しており、誰が先に実行されるかはカーネルのスケジューリングによって決まる
- しかし、親プロセスが非常に高い確率で先に実行される、なぜならカーネルスケジューリングの各プロセスには実行時間があり、親プロセスが子を生んだ後、その実行時間がまだ来ていないからである
- [PS]
- プロセス 1 <pid が 1 のプロセス> は init プロセスであり、他のプロセスはそれによって生み出される
- 人間の世界とは逆に、コンピュータの世界の最初のプロセスは常に生きており、子プロセスの死体を回収するのを待っている
三、10 個の子プロセスを作成し、自分の番号を印刷する
10 個の子プロセスは兄弟である
- 18 行の break を加えない場合
- 2^10 個のプロセスが生成される:1 -> 2 -> 4 -> 8 -> 16 -> 32 -> ... -> 2^10
- 実行中の親、子プロセスの数を統計する:ps -ef | grep -v grep | grep Ten | wc -l
- [Ten は実行可能プログラム名]
- sleep の長さは重複しない
- プロセスが sleep に遭遇すると、システムは他のプロセスにスケジュールを切り替え、最終的な待機時間は約 10 秒だけを反映する
- i 変数が子プロセスに持ち去られた後は独立し、親プロセス内の i 変数の変更によって変わることはない
wait#
一、ゾンビプロセスを作成する
- wait を使用せずに子プロセスの終了を感知しないと、ゾンビプロセスが発生する
- ユーザーはゾンビプロセスを確認するための多くの方法がある [プログラムをバックグラウンドで実行する:./a,out &]
- ps を基に、defunct または Z マークのプロセスを確認する
- top を基に
- pstree を使用してゾンビプロセスの血縁関係を確認できる
- [PS] ゾンビプロセスを殺すには、その親プロセスを殺す必要がある;その親プロセスの親プロセスは zsh であり、プログラムが終了すると、zsh はシステムに親子プロセスの死体を回収するように通知する
二、子プロセスの戻り状態を感知する
- プログラムが約 2 秒間実行された後、次のように出力される:
- ❗ なぜ子プロセスが return 1 し、親プロセスが wait で得た status が 256 なのか?
- 16 ビットの int 型変数の値は 256 👉 その二進数は第 8 ビットが 1 で、他のビットはすべて 0 である
- 次の図を参照 [Linux-UNIX システムプログラミングマニュアル (上巻)—26.1.3 節]、問題の答えが得られる
- 実際、man マニュアルでは状態をチェックするためにマクロを使用できると述べられている
- WEXITSTATUS (wstatus) は終了状態を解析することができる
- ソースコード内では、各マクロが以下のビット操作に対応している
- したがって、printf 状態を表示する際には、必要に応じてマクロを処理すればよい
exec 族#
【全く新しいプロセスに置き換える】
- 子プロセスは 17 行目で全く新しいプロセス [vim] に置き換えられ、その後のコードは永遠に実行されない
- fork の後に直接 exec する:fork 時に親プロセスのメモリ空間をコピーせず、exec 時にすぐに使用する [書き込みコピーの概念:メモリが変更されるときにのみ、実際のコピーが発生する]
- wait (NULL) は死体を回収する役割を果たす
- execlp の第二引数は任意であるが、第一引数との関連性がある方が意味がある
- 以下でその引数のある側面の意味を示すことができる
- exec コードを 17 行目に置き換えると、第二引数は自由に名前を付けることができる
- 実行可能ファイル Test のソースファイル test.c は以下の通り:
- argv [0] の値を出力する
- 上下の二つのコードを実行した結果は以下の通り:
- 第二引数が argv [0] 変数に反映されていることがわかる
追加知識#
- while (1){} を使用する際にループ内に sleep を加えると、CPU に優しい
- そうでなければ、CPU の使用率が急上昇し、アイドル状態になり、過熱する可能性がある
- pstree を使用すると、プロセスの継承関係を簡単に確認でき、-p を指定すると pid を表示できる
- ゾンビプロセスを確認するには、ps、ps -aux、ps -ef、top などを使用できる
- デッドロック:二つ以上の演算ユニットが互いに相手が停止するのを待ってシステムリソースを取得しようとするが、どちらも先に退出しない
- コンピュータにおける同期は、生活の中のそれとは異なる
- 同じ操作を行うのではなく
- 事象が発生する順序が決まっており、因果関係がある
考察ポイント#
ヒント#
- du [-h]:現在のディレクトリおよびすべてのサブディレクトリのサイズを確認する [human-readable]
- マルチプロセスの出力に対して、more を使用すると異なるプロセスの出力が独立して表示される
- おすすめの映画:《彼女》2013