子孫の終了状態を回収する
孤児になった子孫の親になります。ほかのプロセスとは、シグナルの配送と終了に関する規則も異なります。
カーネルが最初に起動するプロセスと、その仕事
Linuxカーネルは、ユーザー空間で最初に動くプロセスへPID 1を割り当てます。PID 1だけに適用される規則と、systemdがカーネルの機能を組み合わせてサービスを管理する仕組みを順に追います。
プログラムを起動すると、Linuxカーネルは、実行に使うメモリや開いているファイルなどをまとめて管理します。この実行中のプログラムと実行環境をプロセスプロセス実行中のプログラムに、仮想アドレス空間やファイルディスクリプタ表などの実行環境を持たせた管理単位。と呼びます。
Linuxカーネルは各プロセスへprocess IDという番号を割り当てます。略称がPIDです。ユーザー空間で最初に動くプロセスには1が割り当てられるため、このプロセスをPID 1PID 1process IDが1で、孤児になったプロセスの終了状態を回収するプロセス。と呼びます。
親を失った子プロセスは、最終的にPID 1へ引き取られます。PID 1はその終了状態を回収できます。Linuxの起動時に開始したPID 1が終了するとカーネルはpanicするため、システムが動いている間はPID 1も動き続けます。これらは、カーネルがPID 1に適用する規則です。
Linuxの起動後には、通信の待ち受け、ログの記録、時刻の同期などを担うプログラムが動きます。利用者が画面から直接起動しなくても、常時または要求に応じて動くこうした処理を、この章ではサービスサービス利用者やほかのプログラムへ提供される処理や機能を、この教材でまとめて指す言葉。と呼びます。サービスを起動し、状態を監視し、必要に応じて停止するソフトウェアがサービスマネージャーです。
現在の主要なLinuxディストリビューションでは、systemdsystemdPID 1として起動処理とサービス管理を担う、ユーザー空間のソフトウェア群。がPID 1として動き、サービスマネージャーの役割を担います。systemdはユーザー空間のソフトウェアです。SysV init、OpenRC、runitなど、別のinitを使う構成もあります。
孤児になった子孫の親になります。ほかのプロセスとは、シグナルの配送と終了に関する規則も異なります。
必要なサービスを起動し、動作中のプロセスを追跡します。サービスごとにCPUとメモリの配分や、使える権限も設定します。
電源投入直後は、マザーボードのファームウェアが起動するデバイスを選びます。続いて、OSを読み込むプログラムであるブートローダーがLinuxカーネルをRAMへ配置します。カーネルはハードウェアを初期化し、ユーザー空間で最初に動くプログラムを探します。
Linuxのディレクトリツリーは、一番上にあるディレクトリを起点にします。このディレクトリをrootと呼び、パスでは/と表します。rootを含むファイルシステムがrootファイルシステムです。initramfsinitramfs起動中に一時的なrootファイルシステムとして使い、ストレージを読み出す準備をする小さなユーザー空間。は、起動中に一時的なrootファイルシステムとして使われます。ストレージを読み出す準備が終わると、PID 1は/usrや/etcを含むrootファイルシステムへ切り替えます。
起動するデバイスを選び、ブートローダーへ処理を渡します。
終了した子プロセスの情報を誰も受け取らないと、カーネルのプロセス表に終了状態が残り続けます。数が増えれば、新しいプロセスを作る妨げになります。
子プロセスが終了すると、カーネルは終了コードやCPU使用時間などを保持し、親へSIGCHLDを送ります。親がwait系のシステムコールで情報を読むまで残る状態をゾンビプロセスゾンビプロセス実行は終わっているが、親が終了状態をまだ受け取っていないプロセス。と呼びます。親が先に終了していれば、最も近いsubreapersubreaper孤児になった子孫プロセスを、PID 1より手前で引き取るよう指定されたプロセス。かPID 1が子を引き取ります。親がSIGCHLDをSIG_IGNに設定するか、SA_NOCLDWAITを使う場合は、カーネルが終了した子を自動で回収します。
子が終了すると、親はSIGCHLDを受け取り、wait系のシステムコールで終了状態を回収します。
同じLinux上でコンテナを動かす場合、コンテナ内のプロセスには、ホスト上の全プロセスを含まない独立したプロセス一覧を見せます。その一覧の中でも、終了した子を回収するPID 1が必要です。
プロセスから見えるPIDの範囲を分ける機能をPID namespaceと呼びます。PID namespaceは、第07章で説明したnamespacenamespacePID、マウント、ネットワーク、ホスト名などの参照範囲を、プロセス群ごとに分ける仕組み。の一種です。Linuxの起動時に作られる範囲とは別に、コンテナを起動するソフトウェアはコンテナ用のPID namespaceを作れます。その中で最初に動くプロセスにはPID 1が割り当てられますが、ホスト側から見ると同じプロセスに別のPIDがあります。
このPID 1が終了すると、カーネルは処理を続けられないためpanicします。
このPID 1が終了すると、カーネルは同じPID namespaceに残るプロセスへSIGKILLを送ります。その後、このPID namespaceには新しいプロセスを作れません。
同じPID namespaceのプロセスは、PID 1が処理を登録したシグナルだけを送れます。親側のPID namespaceからは、SIGKILLとSIGSTOPを送れる例外があります。
主要なLinuxディストリビューションでは、PID 1として動くsystemdが、起動時に多数のサービスを開始し、動作中も停止や再起動を管理します。ここでは、Linuxカーネルが実行するものと、systemdが管理するものを先に分けます。
通信の待ち受けやログの記録など、利用者やほかのプログラムが利用する処理や機能を指します。
システムを監視したり、ほかのプロセスへ機能を提供したりするプロセスです。サービスと一対一になるとは限りません。
サービスは「何を提供するか」を表します。daemonは、その処理を担うプロセスの役割を表します。
スレッドは同じプロセスのメモリやファイルを共有します。Linuxカーネルは各スレッドをタスクとして管理し、CPUへ割り当てます。
start、stop、reloadなど、unitの状態を変える要求です。処理が終わればjobも完了します。
起動するプログラム、依存関係、再起動条件などを保持し、起動したプロセスを監視します。
systemdは、起動、停止、監視する対象と、その依存関係をunitunitsystemdが起動、停止、監視、依存関係の管理を行う対象。として保持します。プロセスを管理するservice unitのほか、待ち受けsocket、mount point、timer、複数のunitをまとめるtargetなども同じ依存関係のグラフへ置けます。
unitを起動する要求が届くと、systemdは依存関係をたどり、同時に必要となるunitの操作をtransactionへまとめます。unitごとの開始や停止の要求がsystemd jobsystemd jobunitをstart、stop、reloadする要求を、systemdが実行順序とともに管理する一時的な単位。です。jobは実行順序を待つキューへ入り、順序を指定していない処理は並行して進められます。
どちらも、ほかのunitを起動対象へ加えます。Wantsでは相手の起動に失敗しても自分を起動できます。Requiresでは、相手が明示的に停止・再起動されると自分も停止・再起動されます。Afterも指定した相手の起動に失敗すると、自分を起動しません。開始順序はWantsやRequiresだけでは決まりません。
従来のdaemondaemonデーモンシステムを監視したり、ほかのプロセスへ機能を提供したりするプロセス。は、起動元のプロセスから独立するためにforkを重ね、主プロセスのPIDをファイルへ記録することがありました。補助プロセスが増えると、一つのPIDだけでは、停止すべきプロセスやサービス全体の使用量を判断できません。
第07章で説明したcgroupは、複数のプロセスを階層的なグループとして管理するカーネルの仕組みです。systemdはservice unitごとにcgroupを作ります。forkした子も同じcgroupへ残るため、サービス全体を停止し、CPUやメモリの使用量をまとめて制御できます。
PID 600(終了)PID 604PID 611/run/web.pidmain daemonのPIDを記録します。補助プロセスを含むサービス全体は、この番号だけでは表せません。
従来のdaemonは、起動したプロセスからforkして親子関係を切り離すことがありました。管理側はPIDファイルなどで主プロセスを特定します。
systemdが起動したプロセスは、そのservice unit用のcgroupへ置かれます。カーネルが保持する所属情報を使うため、PIDファイルだけに依存せず管理できます。
プロセスの親子関係やmain PIDが変わっても、明示的に移動しない限りcgroupの所属は引き継がれます。
シグナル、CPUの配分、メモリ使用量などを、cgroupに属するプロセス群へまとめて適用できます。
systemdが作るcgroup階層では、system.sliceなどのsliceが複数のunitをまとめます。serviceとscopeは、その下でプロセスを保持します。この形は、cgroup v2で原則として内部ノードへプロセスを置かない規則に対応しています。
cgroup階層は、複数の管理プログラムが同じ場所を書き換えると整合を保てません。systemdは階層全体を管理し、コンテナを起動するソフトウェアなどへ一部を任せる場合は、Delegate=で操作できる部分木を明示します。
第07章のweb.serviceとbatch.serviceの階層を見直す
通常のネットワークサービスは、起動後にsocket()、bind()、listen()を呼んで接続を待ちます。この順序では、サービスの起動が終わるまで接続を受けられません。使われる可能性が低いサービスも、待ち受けを始めるためだけに常時起動しておくことになります。
socket activationsocket activationサービスより先に待ち受けsocketを用意し、接続を契機にサービスを起動する方法。では、systemdが待ち受けsocketを先に用意します。接続はカーネルのlisten queueで待ち、systemdは対応するserviceを起動して、そのsocketのファイルディスクリプタを渡します。サービスの起動中も接続を保持でき、実際に接続された時点でプロセスを起動する構成も選べます。
systemdがsocket()、bind()、listen()を呼び、待ち受けを始めます。
ネットワークサービスが侵害された場合でも、書き換えられるファイル、使えるシステムコール、取得できる権限をあらかじめ狭めておけば、同じコンピューター内のほかの処理へ被害が広がりにくくなります。
systemdはservice unitから起動条件を読み取り、子プロセスをcgroupへ配置します。続いて、必要なnamespaceを作り、capabilityを減らし、seccompのシステムコールフィルターなどを設定してから、サービスのプログラムをexecve()します。プログラムのコードが動き始める時点で、カーネルが強制する境界ができています。
起動するプログラム、依存関係、権限、利用できる資源の範囲を保持します。
依存関係を解決し、プログラムを起動するための設定を子プロセスへ渡します。
サービスのプログラムを実行する前に、指定された実行環境を用意します。
execve()の後サービスのプログラム境界が設定された実行環境で、プログラムのコードが動き始めます。
namespaceとcgroupの仕組みを見る capabilityとseccompの仕組みを見る
systemdという名前は、PID 1として動くサービスマネージャーだけでなく、Linuxシステムの基本機能を担うソフトウェア群も指します。デバイスの処理、ログの記録、ログインの管理、名前解決は、PID 1の内部で動くのではなく、それぞれ独立したプロセスとして起動されます。
unitの依存関係と状態を管理する
検出されたデバイスへ名前、権限、追加処理を割り当てます。
複数のログ入力を受け取り、journalへ記録します。
ログイン中の利用者とセッションを追跡し、関連するプロセスを管理対象へ加えます。
DNS、LLMNR、mDNSによる名前解決と結果のキャッシュを扱います。