OSの起動とサービス管理
起動の各段階とサービス依存を理解し、失敗や停止の記録から処理の順序を説明します。
説明
OSの起動では、ファームウェア、ブートローダー、カーネル、サービス管理プロセスへと処理が引き継がれます。アプリが動くには、その前に記憶装置や依存サービスが使える必要があります。
Linuxとsystemdを例に、起動過程をまとめた表とアプリのユニット定義を調べます。DBより後にアプリを起動する設定を確認し、DBが失敗した記録、逆順で停止する記録、終了期限を超えた記録を比較します。
1電源投入からサービス起動まで
boot.tsvは起動過程を整理した教材用の記録です。msは電源投入からの経過時間で、componentが処理主体、eventが完了した処理を表します。
cat記録を表示 boot.tsv起動過程の表ファームウェアによる初期化後、ローダーがカーネルとinitramfsをメモリへ読み込みます。カーネルはinitramfsを展開し、その初期環境が本来のルートファイルシステムを探してマウントします。145 ms以降は本来のルート上のsystemdがサービスを管理します。
2ルートの準備とアプリ起動の間
root_mountedからapp.serviceのstartedまでの時間を求めます。表の時刻から差を計算するので、処理を一つ追加するとどの区間に影響するかも分かります。
awk条件に合う行を計算 '$3=="root_mounted" {t=$1} $2=="app.service" {print $1-t,"ms"}'マウント完了からアプリ起動までの差 boot.tsv時刻の入力差は138 msです。この区間には管理プロセスへの移行、データベース起動、アプリ起動が含まれます。これは区間全体の経過時間であり、アプリだけがCPUを使った時間ではありません。
3必要なサービスと起動順序
app.serviceの設定を読みます。ユニットはsystemdが管理する単位で、このファイルはアプリの起動方法と他ユニットへの依存を定めています。
catユニット定義を表示 app.serviceアプリの設定Requires=db.serviceはアプリ起動時にDBも起動対象に含めます。After=db.serviceは両方の起動ジョブがある場合の順序を定めます。AfterだけではDBの起動は要求されません。Type=execは実行ファイルの起動成功を待ちますが、アプリ内の初期化完了まで保証する設定ではありません。
4依存順序を並べる
order.txtの各行は「先に起動するもの 後に起動するもの」です。ここではroot.mount→db.service→app.serviceの順序だけを抜き出しています。tsortはこの前後関係を満たす順に名前を出します。
cat order.txt順序の指定; tsort order.txt前後関係を満たす並びこの3個では順序が一意になります。実際の起動には無関係なサービスもあり、順序制約のないものは並行して起動できます。tsortの縦一列の出力を、OS全体が一つずつ処理するという意味には読み替えません。
5依存先の失敗と起動されなかったサービス
別の起動でDBが失敗した記録を読みます。result列にはユニットごとの結果をまとめています。
cat失敗した起動の記録 failure.tsvユニットごとの結果DBの終了コードは1です。RequiresとAfterの両方を持つアプリは、DBの起動失敗によりdependencyとなっています。アプリ自身の終了コードが1という意味ではありません。依存していないlog.serviceは起動できています。
6停止時の順序
同じ3ユニットをすべて停止する場合、起動時の順序関係は逆になります。起動順を一度保存し、末尾から表示します。
tsort order.txt起動順を求める | awk '{a[NR]=$0} END {for(i=NR;i>0;i--) print a[i]}'逆順で表示アプリ、DB、ルートの順です。アプリが終了処理でDBを利用する間はDBを残し、ファイルを使うサービスが終わってからアンマウントします。この関係は明示した順序制約によるもので、Requiresだけから逆順を導けるわけではありません。
7終了待ちが期限を超えた記録
shutdown.tsvには、停止処理のうちシグナルと停止完了を記録しています。アプリが5秒以内に終了しなかった例です。
cat停止記録を表示 shutdown.tsvシグナルと状態変化最初にSIGTERMと、停止中のプロセスも終了できるようにするSIGCONTが送られます。TimeoutStopSec=5sを過ぎても残ったためSIGKILLが送られ、アプリの停止が確認された後にDB停止へ進んでいます。KillMode=control-groupは同じユニットの残存プロセスも対象にします。
8期限と実際の停止完了時刻
SIGTERMからSIGKILLまでの差を抽出します。設定の秒とログのミリ秒を対応させます。
awkアプリのシグナル時刻を抽出 '$2=="app.service" && $3=="SIGTERM" {t=$1} $2=="app.service" && $3=="SIGKILL" {print ($1-t)/1000,"seconds"}'経過時間を秒へ換算 shutdown.tsv停止時刻の入力差は5秒で設定と一致します。停止完了は5002 msなので、期限到達と停止確認には2 msの差があります。電源断の順序を調べる際は、停止要求・強制終了・停止完了・アンマウントをそれぞれの記録で確かめます。
まとめ
Requiresは起動対象への依存、Afterはジョブ間の順序を定めます。
依存先の失敗とアプリ自身の失敗は、ユニットごとの結果から判断します。
両方を停止するときは起動の順序関係が逆になり、終了期限を超えたプロセスには設定に従って強制終了が行われます。
操作履歴と解説を振り返れます。
シェル / 起動・停止の記録
cat boot.tsvms component event 0 firmware memory_ready 18 loader kernel_initramfs_loaded 43 kernel initramfs_unpacked 110 initramfs root_mounted 145 systemd root_manager_started 220 db.service started 248 app.service started
awk '$3=="root_mounted" {t=$1} $2=="app.service" {print $1-t,"ms"}' boot.tsv138 ms
cat app.service[Unit] Description=Example application Requires=db.service After=db.service [Service] Type=exec ExecStart=/usr/local/bin/example-app TimeoutStopSec=5s KillMode=control-group
cat order.txt; tsort order.txtroot.mount db.service db.service app.service root.mount db.service app.service
cat failure.tsvms unit result detail 180 db.service exit-code status=1 181 app.service dependency required_job_failed 182 log.service success started
tsort order.txt | awk '{a[NR]=$0} END {for(i=NR;i>0;i--) print a[i]}'app.service db.service root.mount
cat shutdown.tsvms unit event 0 app.service SIGTERM 0 app.service SIGCONT 5000 app.service SIGKILL 5002 app.service stopped 5003 db.service SIGTERM 5003 db.service SIGCONT 5050 db.service stopped 5060 root.mount unmounted
awk '$2=="app.service" && $3=="SIGTERM" {t=$1} $2=="app.service" && $3=="SIGKILL" {print ($1-t)/1000,"seconds"}' shutdown.tsv5 seconds
演習完了