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

カーネルとデバイスツリーのロード

Linuxを起動するには、カーネルイメージ(uImage / zImage / Image など)と、ハードウェア構成を記述したデバイスツリーブロブ(DTB)を、それぞれ決まったメモリアドレスへ読み込む必要があります。ストレージからは ext4load や fatload、ネットワークからは tftpboot を使い、ロード先アドレスは loadaddr / fdt_addr などの変数で管理します。古い uImage はU-Bootヘッダ付きで bootm が扱い、生の Image は booti、zImage は bootz が扱います。起動コマンドにはカーネルアドレスに続けてDTBアドレスを渡し、カーネルが正しいハードウェア情報を受け取れるようにします。

U-BootがLinuxを起動するためには、二つのものを正しいメモリアドレスへ読み込んでおく必要があります。一つはカーネルイメージそのもの、もう一つはハードウェア構成を記述したデバイスツリーブロブ(DTB)です。前のトピックで見た bootcmd の中身も、突き詰めればこの「二つを読み込み、起動コマンドで実行する」という作業の具体化でした。ここでは、カーネルイメージにどんな種類があり、どのコマンドでメモリへ読み込み、どの起動コマンドで実行するのか——その対応関係を整理します。この組み合わせを取り違えると起動に失敗するため、組込みLinuxの起動で最もつまずきやすく、かつ重要なポイントになります。

カーネルイメージの種類

Linuxカーネルのイメージには、いくつかの形式があり、それぞれ起動のさせ方が異なります。代表的なのは三つです。第一に Image は、ARM64などで使われる、圧縮されていない生のカーネルイメージです。第二に zImage は、自己展開機能付きの圧縮イメージで、起動時に自分自身をメモリ上で展開してから動き出します。サイズが小さくストレージを節約できるため、ARM32環境で広く使われます。第三に uImage は、zImage などのイメージにU-Boot独自のヘッダ(イメージの種類・ロードアドレス・エントリポイント・チェックサムなどの情報)を付け足したものです(boot-uimage)。このヘッダのおかげで、U-Bootはイメージの素性を確認したうえで適切に起動できます。uImage は mkimage というツールで作成します。どの形式を使うかは、対象のアーキテクチャやプロジェクトの方針で決まります。

イメージの種類と起動コマンドの対応

カーネルの形式ごとに、対応する起動コマンドが決まっている点が肝心です。U-Boot独自ヘッダ付きの uImage を起動するのは bootm コマンドです。bootm はヘッダを読んでイメージの種類やロード先を把握し、必要なら展開してからカーネルへジャンプします。一方、ヘッダのない生の zImage を起動するのは bootz コマンド、同じくヘッダのない Image(主にARM64)を起動するのは booti コマンドです。つまり「ヘッダ付き uImage なら bootm、生の zImage なら bootz、生の Image なら booti」という対応を覚えておく必要があります。形式と起動コマンドがちぐはぐだと、「Bad Magic Number」などのエラーで起動できません。手元のカーネルがどの形式かを確認し、対応するコマンドを選ぶことが、確実な起動の前提になります。

メモリへ読み込むコマンドとアドレス管理

起動コマンドを実行する前に、まずカーネルとDTBをストレージやネットワークからメモリへ読み込みます。ストレージから読み込む場合、ファイルシステムの種類に応じたコマンドを使います。ext4ファイルシステムからは ext4load、FAT領域からは fatload を使い、書式は ext4load mmc 0:1 ${kernel_addr_r} zImage のように「デバイス 区画番号 ロード先アドレス ファイル名」を指定します。新しめのU-Bootには、ファイルシステムを自動判別する汎用の load コマンドもあります。ネットワーク経由で読み込む場合は tftpboot を使います(詳細は次のトピックで扱います)。読み込み先のアドレスは、環境変数で管理するのが一般的で、カーネルのロード先を kernel_addr_r、DTBのロード先を fdt_addr_r、汎用のロード先を loadaddr といった変数で表します。これらの変数はボードごとに適切な値が用意されており、メモリ上でカーネルとDTBが重ならないように配置されています。

起動コマンドへの引数の渡し方

カーネルとDTBをメモリへ読み込んだら、いよいよ起動コマンドを実行します。このとき重要なのが、起動コマンドにカーネルのアドレスだけでなく、DTBのアドレスも一緒に渡すことです。書式は、bootz カーネルアドレス 初期RAMディスクアドレス DTBアドレス の三つの引数を取ります。初期RAMディスク(initramfs)を使わない場合は、その位置にハイフン(-)を置いて「なし」を示します。たとえば bootz ${kernel_addr_r} - ${fdt_addr_r} と書けば、「カーネルは kernel_addr_r、initramfsはなし、DTBは fdt_addr_r」という意味になります。booti や bootm も同じ引数の並び順です。このDTBアドレスの受け渡しによって、カーネルは起動直後に自分が載っているハードウェアの構成(boot-dtb)を知ることができます。

DTBのアドレスを渡し忘れたり、間違ったアドレスを指定したりすると、カーネルはハードウェア構成を取得できず、コンソールに何も出ないまま停止したり、ペリフェラルを認識できずに起動が途中で止まったりします。これは初学者がよく遭遇するトラブルで、「カーネルは読み込めているのに起動しない」というときは、DTBのロードと、起動コマンドへのDTBアドレスの受け渡しを真っ先に疑うべきポイントです。アドレスを選ぶときにも勘どころがあります。カーネルの展開先・initramfsの展開先・DTBの置き場所がメモリ上で互いに重なってしまうと、一方が他方を上書きして起動に失敗します。ボードごとに用意された kernel_addr_r・fdt_addr_r・ramdisk_addr_r といった既定値は、こうした衝突が起きないよう間隔をあけて決められているので、特別な理由がなければ既定の変数をそのまま使うのが安全です。

実務では、これら一連の読み込みと起動を毎回手で打つのは大変なので、確定した手順を bootcmd にまとめておき、開発中に問題が起きたときだけ各コマンドを手動で分解して実行し、どの段階で失敗しているかを切り分ける——という進め方が定着しています。カーネルの形式を見極め、正しいロードコマンドでメモリへ置き、対応する起動コマンドにカーネルとDTBの両方のアドレスを渡す。この一連の流れが、U-BootからLinuxを起動する作業の中核です。

この項目に出てくる用語

uImage/zImageゆーいめーじ/ぜっといめーじ
カーネルイメージの形式。uImageはU-Bootヘッダ付き、zImageは圧縮済みカーネル。
デバイスツリー(DTB)でばいすつりー
ハードウェア構成を木構造で記述したデータ。U-Bootが読み込みカーネルへ渡す。

関連コマンド

ext4loadbootmfdt

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