組み込みLinuxの起動高速化
組み込み機器の電源投入から操作できるようになるまでの時間は、使い勝手に直結する要素です。この記事では、Linuxを搭載した機器の起動時間をsystemd-analyzeで測定し、効果を期待できる区間から短縮する手順を解説します。対象は、Linuxで広く使われる起動管理の仕組みであるsystemdを用いた構成です。測定値の読み方の注意点と、サービスを無効にしてよいかの判断基準もあわせて整理します。
- 起動時間は3つの区間に分けて、測定してから短縮する
- ユニットごとの初期化時間は待ち時間の有無に注意して読む
- ユーザ空間は起動するユニットを削減すると効果が出やすい
- カーネル側は遅い初期化を特定して、ログ出力を絞る
- 無効にしてよいかは機器で使う機能かどうかで決める
起動時間は3つの区間に分けて、測定してから短縮する
起動時間の短縮は、どこで時間を使っているかを測定してから進めるのが基本です。systemd-analyzeを実行すると、カーネル、初期RAMファイルシステム(initrd/initramfs)、通常のユーザ空間に費やした時間を確認できます。対応する環境では、ファームウェアおよびブートローダの時間も表示されます。initrdを使わない構成では、その区間は表示されません。
カーネル区間はブートローダから初期RAMファイルシステム(またはユーザ空間)に制御が渡るまで、initrd区間はinitrdから通常のユーザ空間へ切り替わるまでの時間です。ユーザ空間の区間は、通常のシステムユーザ空間が起動処理を進め、サービスが起動されるまでの目安となる時間です。各サービスやアプリケーションの内部初期化が完了した時刻を必ずしも示すものではありません。まず一番時間がかかっている区間を見極めて手を付けると、かけた手間に見合う効果を得やすくなります。
ただし、systemd-analyzeの出力は、起動のボトルネックを把握する目安です。依存関係による待ちで数値が大きく見えることがあります。また、数字の上では起動が済んでいても、機器の機能を利用できる状態になっているとは限りません。変更前後は同じ条件で測定して数値を比較し、実際に操作できるようになる時刻(アプリケーションの起動完了など)も別途確認します。
ユニットごとの初期化時間は待ち時間の有無に注意して読む
区間の内訳を把握したら、次はユニット単位の調査です。ユニットとは、「サービスやマウントなど、systemdが起動時に扱う処理の単位」を指します。systemd-analyze blameを実行すると、初期化に時間がかかったユニットが降順で表示され、時間を使っている候補を一覧で確認できます。
systemd-analyze blameの出力時間は、ユニットがactivating状態にあった時間だけを示し、実行キューで待った時間は含まれません。また、Type=simpleのサービスはプロセス起動直後にactiveとみなされるため、プロセス内部の初期化時間がblameの数値に反映されないことがあります。一方、ExecStartPre=などによりサービス自身の起動処理中に待ちが発生している場合はその時間が含まれるため、上位のユニット自体の処理に問題があるとは限りません。数字は見なおし対象を探す材料とし、依存関係とあわせて判断するのが安全です。
依存関係のどこで時間を使ったかを調べるには、systemd-analyze critical-chainを使います。起動時間に大きく影響したユニットの連鎖をツリー形式で確認できる出力です。@の後にユニットが動き出した時刻が、+の後に初期化に要した時間が表示されます。critical-chainの表示は、要求時起動への依存と並列実行によって変わるため、参考情報として読み、実際の効果は変更前後の測定で裏付けます。
ユーザ空間は起動するユニットを削減すると効果が出やすい
起動の各区間のうち、ユーザ空間は起動するユニットを削減することで短縮の効果が出やすい部分です。systemctl list-unit-files --type=service --state=enabledを実行すると、自動起動が有効なサービスユニットを一覧表示できます。
表示された一覧を機器の仕様と突き合わせ、使わないと判断したサービスを無効にします。systemctl disable <unit名>は、通常の自動起動用リンクを削除する操作です。すでに起動しているサービスは停止しないため、必要に応じてsystemctl stop <unit名>を実行します。
また、依存関係やソケットユニット、タイマユニットから起動される構成では、disable後も起動する場合があるため、関連ユニットも確認します。名前だけでは役割がわからないサービスを憶測で無効にするのは危険です。無効にする前にsystemctl catでユニット定義の内容を確かめ、(manページが利用可能な場合)systemctl helpで付属の説明文書を参照し、そのサービスが担う役割を把握してから判断します。
使わない処理は要求時に起動する形を選ぶ
サービスは、単純に無効にする方法のほかに、必要になった時点で起動する形へ移す方法も選択肢です。ソケットユニットを用意しておくと、接続が来た時点で対応するサービスを起動できます。複数のサービスを並列に起動する用途にも利用できる仕組みです。要求が来るまでサービスを起動しない方式のため、起動直後に動かす処理を減らせます。
この方式は、対象のサービス側が、systemdから渡されるソケットを受け取る仕様に対応していることが前提です。また、「接続ごとにインスタンスを起動する形」と「単一のサービスが常駐して接続を受け続ける形」の2通りがあります。どちらの形で動かすかも設計の分かれ目です。
ネットワークやマウントの待ちを見なおして短くする
ネットワーク接続の確立を待つサービスを有効にしていると、接続が確立するまで起動処理が完了しません。その分だけ、起動時間が大幅に延びることがあります。起動直後にネットワークを使わない機器であれば、接続待ちそのものが見なおしの候補です。電源投入の直後にどの機能が必要かを、機器の仕様に立ち返って整理します。
マウントの待ちにも注意が必要です。ソケットユニットがファイルシステムのパスを参照している場合、そのパスへ到達するためのマウントに対する依存が自動で加わります。意図しない待ち合わせが挟まっていないかどうかも、依存関係とあわせて確認する対象です。
カーネル側は遅い初期化を特定して、ログ出力を絞る
カーネルの区間が長い場合は、どの初期化処理に時間がかかっているかの特定から始めます。起動パラメータ(カーネルコマンドラインパラメータ)にinitcall_debugを付けると、起動時に実行される初期化処理が順に記録されます。記録を追うことで初期化処理の実行順を確認でき、起動の途中で処理が止まる場合には、どこで止まっているかを調べる用途にも利用できます。
initcall_debugは初期化処理の調査時だけ一時的に使用し、通常運用では無効に戻します。ログ出力の増加によって測定結果に影響する可能性があるため、起動時間の最終評価はinitcall_debugをはずした条件でも実施します。
コンソールへ出すログの量も、起動パラメータで設定できる項目です。loglevelはコンソールに表示するメッセージの優先度を調整し、quietは大半のログ出力を抑えます。起動が速くなるかどうかは切り分けて考え、設定を変えた前後で起動時間を測定しなおして、数値の差で効果を判断します。測定して確かめる進め方は、カーネルの区間だけでなく、この記事で扱った変更のすべてに共通する原則です。
無効にしてよいかは機器で使う機能かどうかで決める
サービスの要否は、速くなるかではなく、機器で使う機能かどうかで決めます。機器の仕様に照らして不要と判断できる場合に限って無効にするのが安全な進め方です。無効にしてよいかの目安はディストリビューションや構成で異なるため、サービスの有無と依存関係は個別に確認します。
無効にした場合に失う機能も、あらかじめ押さえておきたい点です。たとえば、auditd.serviceを無効にすると、ユーザログインやサービス開始などの記録を後から確認できません。rsyslog.serviceを無効にするかどうかは、永続ログの要否だけでは判断しません。systemd-journaldの永続化設定に加え、従来形式のログファイル、外部サーバへのログ転送、ログ監視ツールとの連携、アプリケーションの依存有無を確認した上で検討します。動作の記録や障害の解析が要件に入っている機器では、起動時間と引き換えにできない機能です。
また、CPU構成によって判断が変わるサービスがある点にも注意が必要です。irqbalance.serviceの要否は、論理CPU数、割り込み親和性の設定、リアルタイム性、デバイス構成、および性能評価の結果を踏まえて判断します。単一論理CPUの構成では負荷分散の効果を得にくい一方、複数論理CPUの構成でも、割り込み配置を固定する設計では無効にする場合があります。一律に無効にするのではなく、対象の役割と構成を確認した上で、サービスごとに要否を判断することが欠かせません。起動時間の短縮は要件から逆算して進め、機器の記録や保守の機能まで無効にしていないかを最後に見なおします。


