Blog:
Embedded Linuxの起動時間最適化
アプリケーションの中には、システム起動時間について一定の要件のあるものがあります。システムにおける全タスク実行の準備ができている必要はないことが多いのですが、特定のミッションクリティカルタスク(例えばイーサネットを介した命令の受信またはユーザーインターフェイスの表示)の準備はできている必要があります。この記事では、Toradexのシステムオンモジュールにおいて、起動時間を改善するために容易に実行できる方法をいくつかご紹介します。
注意:この記事で紹介する方法には、U-Bootやカーネルをリコンパイルしたり、ルートファイルシステムを最初からリビルドしたりする必要のあるものが含まれています。 それぞれ、デベロッパーウェブサイトにある記事を参照してください。
最適化作業を開始する前に、起動時間を測定する適切な方法が必要です。正確なエンド・トゥ・エンドの起動時間が必要な場合は、ハードウエア(例えばGPIOやオシロスコープ)を利用する必要のある場合もあります。大半の場合は、単純にホストシステムのシリアルポートを監視するだけでも十分正確です。シリアル出力のタイミングを監視するのに人気のあるユーティリティは、Tim Birdのgrabserialです。このユーティリティツールは、下に示すように、シリアルポートからキャプチャした各行にタイムスタンプを追加します。
$ ./grabserial -d /dev/ttyUSB1 -t [0.000002 0.000002] [0.000171 0.000169] [0.000216 0.000045] U-Boot 2015.04-00006-g6762920 (Oct 12 2015 - 15:35:50) [0.005177 0.004961] [0.005227 0.000050] CPU: Freescale Vybrid VF610 at 500 MHz [0.008938 0.003711] Reset cause: POWER ON RESET [0.011153 0.002215] DRAM: 256 MiB [0.063692 0.052539] NAND: 512 MiB [0.065568 0.001876] MMC: FSL_SDHC: 0
最初の数字がタイムスタンプ(最初の文字が受信されてから)を意味しており、一方で2番目の数字は現在の行と前の行のタイムスタンプの差を示しています。
この記事は全般的にToradexのすべてのモジュールに当てはまります。一方で、NXP®/Freescale Vybridベースのモジュール、Colibri VF61を使い、特殊な測定値や改善点なども紹介します。
Linuxシステム起動には、大まかに言って3つの段階があります。下に記載したこの3つの段階については、このブログの中で分析していきます。
- ブートローダ
- Linuxカーネル
- ユーザ空間(initシステム)
ブートローダ
実は、ブートローダが稼働可能となる以前に2つの段階があります。ハードウェアの初期化とROMの起動です。ハードウェアの初期化フェーズは、電源シーケンスとバスまたはSoCのリセットのタイミング要件を満たすために必要です。このフェーズは通常、10-200ミリ秒の範囲で固定されています。Arm SoCは、内部ROMに保存されたファームウェアから起動します。このファームウェアは、起動メディアからブートローダをロードします。ランタイムは通常かなり短く、ブートローダのサイズによって変化します。ブートローダのサイズをできる限り小さくする以外の最適化は困難で、事実上、最適化の余地と柔軟性があるのはブートローダ(U-Boot)内となります。
現在のリリースV2.5ベータ1では、最初の文字からカーネル開始までの時間は約1.85秒です。これには次の段階があります。
- U-Boot初期化(約110ミリ秒、最初の文字を受信してからの計測)
- 自動起動ディレイ(1秒)
- UBI初期化とUBIFSマウント(Fastmapという機能のおかげで約300ミリ秒。Fastmapなしではだいたい1.6秒かかる)
- カーネルのローディング(375ミリ秒)
- デバイスツリーのローディングおよびパッチング(約35ミリ秒)
- そして最後に、カーネルの開始アドレスにジャンプ
明らかな最適化の方法は、自動起動ディレイを短縮することです。これは、下のようにしてゼロに設定することができます。
setenv bootdelay 0
saveenv
これは、CONFIG_BOOTDELAY のconfigシンボルを使ってデフォルトとして設定することも可能です。けれども、現在のリリースでは、起動ディレイが0だとブートローダのコンソールに直接入ることができません。U-Bootには、CONFIG_ZERO_BOOTDELAY_CHECKというオプションがあり、これはbootdelayが0でも1文字を確認するものです。次のリリースでは、このオプションをデフォルト設定とすることにしました。
シリアル出力は同期して送信されます。つまり、シリアルラインでその文字が送信されるまで、CPUが待つ、ということです。このため、出力される各文字のせいでU-Bootの速度が落ちてしまいます。特に、UBIは多くの情報メッセージを出力するため、最適化の余地があります。これにはCONFIG_UBI_SILENCE_MSGという設定シンボルがあることがわかりました。
ハードウェアを最大限に効率的に利用するには、ハードウェアの性能やその時点で何が実装されているのかについてのインサイトが必要です。これまではなかった機能の1つに、レベル2のキャッシュがあります。(Colibri VF61上のみ。)レベル2キャッシュを実装したところ、起動時間が40ミリ秒以上改善しました。
一定の機能を取り除くと、再配置時間とそのような機能の初期化を短縮するのに役立ちます。ディスプレイサポート(DCU)、EXT3 および EXT4サポート、DFUや大容量記憶装置などのUSB周辺機器ドライバを除外することで、U-Bootのサイズを366kBに縮小し、更に10ミリ秒短縮することができました。
タイムスタンプによると、時間の大部分は、UBIを付け、UBIFSをマウントし、カーネルをロードするのに費やされています。(約380ミリ秒)もちろんカーネルのサイズとロード時間には線形の相関関係があり、このため、カーネルのサイズを最適化することで、ブート時間を更に改善できます。
カーネル
カーネルの起動時間のみを計測するには、grabserialの「マッチ」機能を使えば、ブートローダが出力した最後のメッセージのタイムスタンプをリセットできます。
./grabserial -d /dev/ttyUSB1 -t -m "^Starting kernel.*"
ルートファイルシステムがマウントされ、ユーザ空間の最初のプロセス稼働が始まった後も、カーネルはハードウェア初期化を続けるため、起動終了の時間を割り出すのは多少難しい面があります。「Freeing unused kernel memory」という文字列は、initプロセスが始まる前に出される最後のメッセージです。このため、カーネルの「リニア」なinit手順が終了した印となります。(init/main.cのkernel_initをご覧下さい。)このメッセージのタイムスタンプを使って起動時間を比較することにします。出荷されるカーネルはZip形式で4316kBのサイズがあり、起動時間は2.56秒です。
U-Bootと同様にLinuxカーネルもすべてのメッセージを同期してシリアルコンソールに出力します。実際の動作は、利用するシリアルコンソールによって異なりますが、LPUART(Vybridのコンソール用ドライバ)は、シリアルポートで文字が送信されるまで同期して待ちます。この利点は、カーネルがクラッシュした際、それまでのすべての文字が見えるということです。非同期でメッセージが送られる場合は、見えるメッセージの最期の部分はクラッシュの位置を示してくれません・・・。
カーネルには、表示されるカーネルメッセージの量を最小限にする引数があります。"quiet"です。しかし、それでは起動時間測定のための印である「Freeing unused kernel memory」も表示されなくなります。画面にこのメッセージを表示させる最も簡単な方法は、この出力命令文のログレベルを引き上げることです。これは「mm/page_alloc.c」にあります。「Freeing %s memory」を検索してください。ここではメッセージを「pr_alert 」に引き上げました。測定では、1.55秒の向上が見られました。これは、2倍以上改善したことになります!
更に改善をするための最も簡単な方法は機能を取り除くことです。Yoctoプロジェクトには、ksize.pyという便利なツールがありますが、これは、カーネルのビルドディレクトリ内から開始する必要があります。このツールは、カーネルのパーツ個々のサイズを示す表を出力します。最初の表は、ハイレベルの概要を示します。(正確な概要を見るには、ビルド前にmake cleanを使います。)
Linux Kernel total | text data bss ------------------------------------------------------------------- vmlinux 8305381 | 7882273 247732 175376 drivers/built-in.o 2010229 | 1881545 109796 18888 fs/built-in.o 1944926 | 1911100 19422 14404 net/built-in.o 1477404 | 1398316 44832 34256 kernel/built-in.o 628094 | 514935 17099 96060 sound/built-in.o 326322 | 316298 8248 1776 mm/built-in.o 288456 | 276492 8000 3964 lib/built-in.o 160209 | 157659 217 2333 block/built-in.o 137262 | 133614 2420 1228 crypto/built-in.o 104157 | 100063 4082 12 security/built-in.o 37391 | 36303 788 300 init/built-in.o 31064 | 16208 14772 84 ipc/built-in.o 29366 | 28640 722 4 usr/built-in.o 138 | 138 0 0 ------------------------------------------------------------------- sum 7175018 | 6771311 230398 173309 delta 1130363 | 1110962 17334 2067
どの機能を取り除いてもよいかは、もちろんアプリケーションによって様々です。それぞれのハイレベルディレクトリを確認すれば、簡単に最も効果が高いと思われる候補を取り除いていくことができます。この記事では、いくつかのファイルシステム(cifs、nfs、ext4、ntfs)、オーディオサブシステム、マルチメディアサポート、USBおよびワイヤレスネットワークアダプタのサポートを外しました。カーネルは3356kB程度になり、それ以前に比べて1MB程小さくなりました。また、これにより、ブートローダ内のカーネルロード時間が85ミリ秒程度短縮されました。
他の改善のためのアイディアとしては、異なるコンプレッションアルゴリズムを検討することも考えられます。ただし、現在の弊社のカーネル設定ではLZOがデフォルトアルゴリズムとなっており、これはすでにかなり高度なものです。
ユーザ空間
Linuxのユーザ空間では、initシステムが初期化を行います。Toradex BSPイメージは、SystemdであるÅngström標準initシステムを使います。昨今Linuxデスクトップにおける事実上の標準initシステムとなったSystemdは、非常に機能が豊富で、特に動的システムを念頭に設計されたものです。Systemdはまた、起動時間についても対策をしています。複数のデーモンが同時に起動されます(今日のマルチコアシステムを活かすため)。ソケットのアクティベートではサービスのロードを後に遅らせることができ、デバイスのアクティベートではサービスをオンデマンドで開始できます。さらに、統合されたロギングデーモンjournaldが、バイナリパックされたログファイルと複雑なログファイル管理により容量を節約します。
アプリケーションによっては、組み込みシステムはかなり静的である場合があります。このため、systemdの動的な機能は本来必要ありません。残念ながら、systemdはあまりモジュール化されていないか、個々のモジュールが交錯した依存性を持っています。このため、systemdを必要最小限にそぎ落とすのは困難です。このセクションは、二つの節に分割されています。初めの節では、systemd起動最適化のテクニックを示します。一方、2番目の節では、System Vやその他の代替方法について見ていきます。
どちらの節でも、「Freeing unused kernel memory」というメッセージを時間測定における基準の時間とします。
./grabserial -d /dev/ttyUSB1 -t -m "^\[ *[]0-9.]* Freeing unused kernel memory.*"
systemd
このブログ記事では、シリアルコンソールのログインシェルをクリティカルタスクとして定義します。ログインシェルを「Type=Idle」として定義します。つまり、定義からしてすべてのサービスが開始された後にのみ、開始されることになります。
ヘッドレスまたはフレームバッファベースのアプリケーションを開始するには、通常新しいサービスを作成します。Systemdでは、特定の要件をサービスに必要なものとして定義することが可能で(例えば「 Wants=network-online.target」のネットワーク)、そうした場合、要件が満たされると自動的にすぐにサービスが開始されます。しかし、サービスは平行して開始されるため、サービス全体でCPUのリソースを共有することになります。それでも、アプリケーションはシリアルコンソールが使えるようになるまでには起動し稼働中の可能性が高いため、次の数字は高めに見えるかもしれません。
カーネル引数のquiet引数は、systemdでも使われます。この変更はすでにsystemdの起動時間にも大きな影響を与え、処理が1.6秒ほど短縮します。
systemdには、systemd-analyzeというユーティリティもあり、サービスの一覧および「blame」引数で起動した場合はサービスの開始時間を出力します。これにより、起動時間に悪影響を与えるものをかなり簡単に見つけることができます。ただし、時間が実時間で計測されるため、この値は誤りを招く可能性もあります。表示されるサービスはスリープ状態にあるだけで、CPUが他の仕事を処理していることがあるためです。このため、最も悪影響を及ぼすのが一覧の一番上のサービスではない可能性もあります。シングルコアシステムの場合は特にそうです。
サービスはdisableコマンドにより無効にすることができます。サービスの中には(特にsystemd自身が提供するサービス)無効にするのにmaskコマンドが必要なものもあります。システム稼働のために必要なサービスもあります。このため、サービスの無効化は慎重に、1つずつ行わなければなりません。この記事では、次のサービスを無効化しました。
systemctl disable usbg systemctl disable connman.service # replaced with networkd systemctl mask alsa-restore.service
Systemdには、 journaldというシステムロギングデーモンがあります。これは完全な無効化をしてはいけないコンポーネントです。起動中、ロギングデーモンは、ディスク上の古いログファイルを管理、削除し、さらに新しいログエントリーをディスクに書き出す必要があります。ディスクへのロギングを無効化するだけでも、起動時間を改善できます。ただし、もちろんログファイルが保存されないことになります。/etc/systemd/journald.conf内でStorage=noneを使うとログ保存機能を無効化できます。
System V initとその他の手法
SysVもまた、長い間Linuxの標準のinitシステムとなっています。initシステムがスクリプトベースなため、非常にモジュラー的で、最低限の形へとそぎ落とすのが比較的簡単です。特に、systemdのデバイスアクティベートまたはソケットアクティベートの必要がない、比較的静的なシステムでは、SysVはよい代替手法となります。
Yoctoプロジェクトのリファレンスディストリビューション「poky」ではSysVをデフォルトで使っています。「poky」については、前回の記事[The Yocto Project's Reference Distribution “Poky” on Toradex Hardware]で書きました。「minimal-console-image」および静的IPアドレス設定を使った場合、Colibri VF61におけるユーザ空間の起動時間は最大2.3秒と測定されました。
meta-yoctoレイヤも「poky-tiny」を提供しており、これはinitシステムとして単純にシェルスクリプトを使います。ディストリビューションを「poky-tiny」と入れ替え、「console-image-minimal」などの普通のYoctoイメージをビルドします。このディストリビューションは、initramfsとしての使用を目的としているのですが、MACHINE_ESSENTIAL_EXTRA_RDEPENDS、IMAGE_FSTYPESおよびPREFERRED_PROVIDER_virtual/kernelをconf/distro/poky-tiny.confファイルから消すと、動作するUBIFSイメージをビルドできます。ディストリビューションをフラッシュ可能なルートファイルシステム用に適切に「再設定」するには、新規のディストリビューションレイヤを作成し、ディストリビューションの設定ファイルをコピーします。シェルへの「起動時間」はもちろん非常に速く(220ミリ秒)、起動時間全体はたった2秒未満で簡単なコマンド実行が可能です。ただし、機能はほとんどなくなり、ルートファイルシステムのマウント、基本的な仮想ファイルシステムのサポートおよびシェル以外の機能はありません。とはいえ、プロジェクトに必要な機能の量によっては、出発点として適したものかもしれません。
関連するリソース:
http://free-electrons.com/doc/training/boot-time/boot-time-slides.pdf
Jordon Wu - 6 years 7 months | Reply
Where to get the Boot Time Optimization patch or config? thanks
Toradex - 6 years 6 months | Reply
Hi Jordon, there is a OpenEmbedded Layer which contains the changes as patch files: https://github.com/toradex/meta-toradex-extra/tree/toradex-fast-boot
sukesh - 7 years 3 months | Reply
this artical is nice you can also remove this 3 steps
[0.005227 0.000050] CPU: Freescale Vybrid VF610 at 500 MHz
[0.011153 0.002215] DRAM: 256 MiB
[0.063692 0.052539] NAND: 512 MiB
at production level there is no needed.
Stefan Agner - 7 years 3 months | Reply
Thank you for your feedback Sukesh. Since all output is sent synchronously over the serial output, every omitted character saves time... with the cost of having less debug information. I agree the mentioned lines are not very important in a product and hence good candidate to omit. You can also use the CONFIG_SILENT_CONSOLE along with the environment variable "silent" to get rid of all console output.
Sergey - 6 years 2 months | Reply
Hallo Stefan, ich kann nicht das "toradex-tiny" mit neuem branch "krogoth" bauen.
bitbake:
ERROR: Unbuildable tasks were found.
These are usually caused by circular dependencies ...
Würdest Du mir weiter helfen.
Stefan Agner - 6 years 2 months | Reply
Hi Sergey, So far we did not port the demo to krogoth, but I don't see any practical issue why it should not work. Note that I did not use a special image, I just used the standard image "console-image-minimal" along with the poky-tiny distribution. If you still experience problems, use our official Support channels https://www.toradex.com/support (preferable Community) and include detailed information of what you exactly did. Thanks, Stefan.