🐧 Linux 総合学習プラットフォーム
ブートローダ U-Boot ・ 上級

起動の流れとbootcmd

U-Bootは起動時、まず bootdelay 秒だけコンソール入力を待ちます。この間にキー入力がなければ、環境変数 bootcmd に書かれたコマンド列を自動で実行して起動を進めます。典型的な bootcmd は「ストレージからカーネルとデバイスツリーをメモリへロードし、bootm や booti で起動する」という一連の流れを並べたものです。手動でこれらを順に打てば、bootcmd と同じことを対話的に再現できます。開発中は bootcmd を書き換えてTFTP起動に切り替え、量産では確定した手順を saveenv で固定します。

U-Bootが起動してから、実際にLinuxカーネルが立ち上がるまでの流れには、はっきりとした筋道があります。この流れの主役が、前のトピックで触れた環境変数 bootcmd と、待ち時間を決める bootdelay です。U-Bootは起動すると、まずハードウェアの初期化を済ませたあと、自動起動に移る前に一定時間だけ人間の介入を待ちます。この「待つ→何もなければ自動で進む」という仕組みを理解すると、開発時に手動でコマンドを打つ場面と、量産時に勝手に起動してほしい場面の両方を、同じU-Bootで矛盾なく扱えるようになります。ここでは、その自動起動(autoboot)の仕組みを順を追って見ていきます。

bootdelay によるカウントダウン

U-Bootは起動の最後に、環境変数 bootdelay で指定された秒数だけ、シリアルコンソールからのキー入力を待ちます。このときコンソールには「Hit any key to stop autoboot:」のようなメッセージとともに、残り秒数のカウントダウンが表示されます。この待ち時間の間にキーを押すと、自動起動が中断されてU-Bootのプロンプト(=> など)に入り、手動操作に切り替わります。一方、何も押さずにカウントダウンが終わると、U-Bootは自動起動へと進みます。bootdelay の値は setenv bootdelay 5 のように設定でき、開発中は介入しやすいよう長め(3〜5秒)に、量産では待ち時間をなくすため 0 に設定するのが定石です。bootdelay を -1 にすると自動起動そのものを無効化してプロンプトで止め、0 にすると待たずに即座に自動起動する、という挙動の違いも押さえておくと便利です。

bootcmd が起動を進める

カウントダウン中に何も入力がなかった場合、U-Bootは環境変数 bootcmd に書かれたコマンド列を自動的に実行します。この bootcmd こそが、自動起動の本体です(boot-bootcmd)。bootcmd には、Linuxを起動するために必要な一連のコマンドが、セミコロン(;)で区切って並べられています。典型的な中身は、「ストレージからカーネルイメージをメモリへ読み込む」「同じくデバイスツリー(DTB)をメモリへ読み込む」「読み込んだカーネルを起動コマンドで実行する」という三つの段階です。たとえば、SDカードから読み込んで起動する bootcmd は、load mmc 0:1 ${kernel_addr_r} zImage; load mmc 0:1 ${fdt_addr_r} board.dtb; bootz ${kernel_addr_r} - ${fdt_addr_r} のような形になります。U-Bootはこれを上から順に実行し、最後の起動コマンドでカーネルへ制御を渡します。

ここで大切なのは、bootcmd に書かれているのは特別な魔法ではなく、自分でもプロンプトから一つずつ打てる普通のコマンドの並びだ、という点です。つまり、カウントダウンを止めてプロンプトに入り、bootcmd の中身と同じコマンドを手で順番に打てば、自動起動とまったく同じ結果を対話的に再現できます。これは開発時に非常に役立ちます。たとえば「自動起動がうまくいかない」というとき、bootcmd の各コマンドを一行ずつ手で実行してみれば、どの段階で失敗しているのか(カーネルの読み込みか、DTBか、起動コマンドか)を切り分けられます。printenv bootcmd で現在の中身を確認し、それを参考にしながら手動で各段を試す——これがU-Bootでのトラブルシューティングの基本動作になります。

bootcmd の中身は、変数参照や run コマンドを使って何段にも部品化されていることが少なくありません。実際のボードでは、bootcmd が直接ロードと起動を書くのではなく、setenv bootcmd 'run bootargs_set; run load_kernel; run do_boot' のように、別の変数に分けた処理を run で順に呼び出す構成になっていることがよくあります。これは手順を整理し、一部だけを差し替えやすくするための工夫です。最近のU-Bootには、こうした起動先の探索を標準化した distro boot(distroboot)という仕組みもあり、SDカード・eMMC・USB・ネットワークの順に起動可能なメディアを自動で探して、見つかった設定ファイルに従って起動する、という賢い既定の bootcmd が用意されていることもあります。複雑に見えても、最終的には「カーネルとDTBを読んで起動コマンドを呼ぶ」という骨格に行き着くので、run で呼ばれている変数を printenv で順にたどっていけば、全体の流れを解きほぐせます。

開発から量産への流れ

この仕組みを使い分けることで、同じU-Bootを開発と量産の両方に対応させられます。開発中は、bootcmd を一時的に書き換えて、ネットワーク経由の起動に切り替えるのが典型です。setenv bootcmd 'tftpboot ${kernel_addr_r} zImage; tftpboot ${fdt_addr_r} board.dtb; bootz ${kernel_addr_r} - ${fdt_addr_r}' のようにTFTPを使う内容に変更しておけば(boot-tftp)、ビルドしたカーネルをサーバに置いてボードを再起動するだけで最新版で起動でき、ストレージへの書き込みを省けます。このとき saveenv せずにRAM上だけの変更にしておけば、再起動で元の設定に戻せるため、安全に試行錯誤できます。

起動手順が固まったら、量産向けに確定させます。最終的な bootcmd をストレージから起動する内容に設定し、saveenv で不揮発領域へ保存して固定します。あわせて bootdelay を 0 にして、電源投入後に待ち時間なく自動でカーネルが立ち上がるようにします。こうしておけば、製品の電源を入れた人が何も操作しなくても、U-Bootが保存済みの bootcmd を黙々と実行してLinuxを起動します。「bootdelay で待ち、入力がなければ bootcmd を実行する」という一つの単純な仕組みが、開発時の柔軟な手動介入と、量産時の確実な無人起動の両方を支えている——この対称性を理解しておくと、U-Bootの起動シーケンスがすっきりと頭に入ります。次のトピックでは、bootcmd の中で実際にカーネルとDTBをどうメモリへ読み込むのか、その具体を掘り下げます。

この項目に出てくる用語

bootcmdぶーとこまんど
自動起動時にU-Bootが実行するコマンド列を入れた環境変数。
環境変数(U-Boot)かんきょうへんすう
U-Bootの挙動を決める設定値。printenvで確認、setenvで変更、saveenvで永続化。

関連コマンド

bootbootmsetenv

▶ 学習アプリでこの続きを学ぶ・演習する