Linuxの起動時間を高速にする

※ 2016/03/06に(あとで書く)を置き換えたり追記したり。
※ 今後さらに追記するかもしれません。

● はじめに
検索エンジンでこのページに来たあなたは間違っています。この手のは「Linux bootup speed」などで検索するとよい方法がたくさん見つかります。英語を使えない時点であなたは私と同程度です・・・うん。

ちなみに私が参考にしたところ、
http://processors.wiki.ti.com/index.php/Optimize_Linux_Boot_Time
(PDF注意) http://free-electrons.com/doc/training/boot-time/boot-time-slides.pdf (PDF注意)

● 起動時間への取り組み
Linuxに限らずですが、測る、遅いとこを見つける、ボトルネック改善する、のスパイラルが基本。このために、正確に測ること、再計測までの作業をできるだけ早くかつ自動化すること、が大事。あとどこを取り組むかも大事、100usecのところを半分にしても50usecの改善にしかならないが、100msecのところを1割早くすると10msecの改善になる。

● initcall_debug
cmdlineに「initcall_debug」を入れると、Initcall Debugができる。後述のinitcallの関数にかかった時間がKERN_DEBUGでログに出るので、あとでdmesgとかで見ればよい。出力レベルを下げると大量のログが出てその出力に時間がかかるようになるので避けた方がよい。CONFIG_KALLSYMS=yじゃないとアドレスからシンボルに変換する手間が増えてめんどいけど、その代わりkernelが太るので注意。

● initcallの仕組み
このへんに詳しい Linux関係メモ@宇治屋電子: [ARM] kernel先頭から。その2 ページがなくなっちゃうことも考えてメモしておくと、initcallはマクロ展開されて関数ポインタの配列として特殊なセクションにレベル順に配置することで起動時に配列をインクリメントしながらcallする、とかいう感じになってる。init/main.c include/linux/init.h include/asm-generic/vmlinux.lds.h あたり読め。

● Linuxの起動完了
おおざっぱに、初期の初期化、initcall関数呼び出し、rootmount、の順。初期の部分は手を出しにくい(したいていは時間かかっていない)ので、initcallとrootmountを見ることになる。

Linuxのモジュールはみな何でもかんでもinitcallしたがるので、ほとんどの場合はinitcallにかかる時間がネックになる。またinitcallは1スレッドで順番に実行するので、あとでよい・非同期処理でいいものをそのように変更するのが定石となる。

initramfs使っていようがいまいが、結局はrootmount完了して /sbin/init を起こすところまでがある意味Linuxの起動となる。どうでもよいモジュールは後に回すべきだが、rootmountに必要なモジュールは真っ先に初期化すべきとなる。可能ならそのモジュールのinitcallのレベルを変更するところまで手を入れた方がよい。

● eMMC
(あとで書く)

● -EPROBE_DEFER
本来ならinitcallのレベルやらバスの親子関係やらで適切にモジュールを記述すべきなのに、そうなっていないやつらのために用意されている。モジュールがprobe関数で-EPROBE_DEFERを返すと、そのモジュールはdeferred_probeの管理リスト入りする。drivers/base/dd.c あたり。probeに成功するドライバがなくなるまで何度もそのモジュールを呼び出す仕組みになっている。こうすることで「呼ばれたタイミングが悪くて初期化できなかったよ」というモジュールを救済している。

ただこれを行うdeferred_probe_initcall()関数もinitcallの最後に入っているため、probeに成功するドライバがなくなるまで起動完了に行き着かないという挙動になる。結果、-EPROBE_DEFERを返すくせに毎回時間がかかっているモジュールがいると最悪の結果となる。

● PROBE_PREFER_ASYNCHRONOUS
最近のkernelからのようだけど(Ver4.2からっぽい)、struct device_driver の probe_type に PROBE_PREFER_ASYNCHRONOUS などを書けるようになっている。これを書くと、probeが非同期に呼ばれるようになる。時間がかかるけど待ち合わせする必要がないようなモジュールに足すことで簡単に並列処理に変更できて起動時間を早くできる。反面、時々エラーを返したり待ち合わせの必要があったりすると面倒なので、そういう場合は自分でスレッド起こして管理する必要が出てくると思う。

● deferred_module_init
eLinuxでは有名なパッチらしい。簡単に言うと、ユーザランドから cat /proc/deferred_initcalls などされるまでモジュールのprobeを遅らせる仕組み。起動のクリティカルパスには関係なくてあとからゆっくり初期化してくれればよいモジュールに最適。最新のLinuxには当たらなかったので私が手で直したパッチ → 3.18.2向け 4.4.1向け なんで最新じゃなく中途半端なバージョンなんだって?それはお察しください。。

ユーザランドからキックする前提なので、ユーザランドが動いている==initが動いている==rootmountが完了している==deferred_probe()は終わっている、となる。それに対し、PROBE_PREFER_ASYNCHRONOUS は非同期に呼ぶだけで、-EPROBE_DEFERを返すとdeferred_probe()入りし、タイミングが悪いとこれがdeferred_probe_initcall()を待たせることになり起動時間を阻害するので注意。

● ムダなprintk
組み込み系ではconsoleをserialにしていることが多いが、その場合、ムダなprintkは起動時間の足を引っ張る。115200bpsで文字列を出力するのにかかる時間を計算してみるとわかるとおり、1行出すだけで数msecはかかる。この積み重ねは痛い。手っ取り早くはcmdlineにquietを足すことだが、pr_err()とかpr_crit()とかしているドライバは本当にそんなにレベルの高い出力なのか今一度考え直してみる価値はある。逆に、serialにさえ出さなければそれほど時間はかからないので、pr_info()とかpr_warn()とかは放っておいてよいと思われる。

● Linuxの前
この手のは行き着くと結局、CPUの立ち上げ・メモリ初期化・クロック安定待ち・デバイスの応答待ち、あたりに行き着く。Linuxを起動する場合は少なからずブートローダがLinuxをどこからかロードするわけで、そのロード時間をうまく使って上記のような待ちを減らす方法を考えないといけなくなる。もちろんLinux本体を小さくしてロード時間を短くするのも大事だけど、どうせLinux起動完了しても真の起動完了(目的により)までにロードしないといけないものもあるので、時間かかるものをどう並列化するか視野に入れるのも大事。

最近のCortex-AとかはCPUの速度が速いので、Linux本体を圧縮して配置しておいた方が起動が速くなることが多い。ストレージからの読み出し速度と解凍処理を行うCPU速度との兼ね合いになるので、どんな圧縮形式がよいかは一概には言えない。とはいえ、だいたいの場合は、lzf, lz4, zlib あたりになると思われる。マルチコアを生かすアイディアもあるが、ブートローダでマルチCPUのスケジューリングまでやりたい?

● 最後に
駆け足で私がよくやることをまとめましたが、こんなのがあるよ、などがあれば歓迎します。記事にコメントとつけるか @rarul あたりまで。

このブログ記事について

このページは、らるるが2016年2月 8日 06:19に書いたブログ記事です。

ひとつ前のブログ記事は「「Effective Modern C++」読んだ」です。

次のブログ記事は「今日の出来事」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

月別 アーカイブ

ウェブページ

Powered by Movable Type 5.2.13