一覧OS-04-1 · 起動と実行権限の境界

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はジョブ間の順序を定めます。

依存先の失敗とアプリ自身の失敗は、ユニットごとの結果から判断します。

両方を停止するときは起動の順序関係が逆になり、終了期限を超えたプロセスには設定に従って強制終了が行われます。

操作履歴と解説を振り返れます。

シェル / 起動・停止の記録

1
cat boot.tsv
ms	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
2
awk '$3=="root_mounted" {t=$1} $2=="app.service" {print $1-t,"ms"}' boot.tsv
138 ms
3
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
4
cat order.txt; tsort order.txt
root.mount db.service
db.service app.service
root.mount
db.service
app.service
5
cat failure.tsv
ms	unit	result	detail
180	db.service	exit-code	status=1
181	app.service	dependency	required_job_failed
182	log.service	success	started
6
tsort order.txt | awk '{a[NR]=$0} END {for(i=NR;i>0;i--) print a[i]}'
app.service
db.service
root.mount
7
cat shutdown.tsv
ms	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
8
awk '$2=="app.service" && $3=="SIGTERM" {t=$1} $2=="app.service" && $3=="SIGKILL" {print ($1-t)/1000,"seconds"}' shutdown.tsv
5 seconds

演習完了