uchan note

プログラミングや電子工作の話題を書きます

UEFI + iPXE で自作 OS をネットワーク起動する

概要

開発マシン上に置いた自作 OS のカーネルを,ネットワーク経由でターゲットマシンに読み込ませ,起動させるやり方についての記事です.

ディスクイメージをネットワークブートする - Raphine Project の記事を大いに参考にしました.Raphine Project の記事は BIOS を対象にしていますが,本記事は BIOS ではなく UEFI で起動させることが主な違いです.

UEFI + USB メモリによる起動

ネットワーク起動を説明する前に,簡単に USB メモリでの起動を説明します.

USB メモリに入れたカーネルUEFI で起動させるために,私は次の方法を用いています.

  • USB メモリを FAT でフォーマットして
  • /EFI/BOOT/BOOTX64.EFIUEFI アプリとして作成した自作ブートローダを配置し
  • カーネル本体となる ELF ファイルを適当なパスに配置し
  • ブートローダで ELF ファイルを読み取って起動する.

これは UEFI を使った起動方法の中で比較的シンプルな仕組みです.

UEFI を使う中で最もシンプルな起動方法は,大神祐真著の フルスクラッチで作る!UEFIベアメタルプログラミング で紹介された,UEFI アプリとして OS っぽいものを作る方法でしょう. この方法ではカーネル本体とブートローダを分ける必要がなくシンプルです.

ただ,個人的にはブートローダカーネル本体は分けて作りたい1ので,2 段階のブート方式を採用しました.

ネットワーク起動

ネットワーク起動とは,ネットワーク上に起動用データを置いておき,それを読み取って起動する方法です. 標準的な規格に PXE というものがあります.

標準の PXE では DHCP サーバを立て,起動用データの置き場所などを指定する必要がありますが,Raphine Project の記事では iPXE という PXE の実装を利用しています. iPXE には,DHCP を使わずに IP アドレス直指定でデータの場所を指定することができる,TFTP サーバではなく HTTP サーバから起動用データを取得できる,という機能があります.めっちゃ楽です. 本記事でもそれにならいます.

タイトルの「UEFI + iPXE」は,iPXE 自体とそこから起動されるものを UEFI アプリとする,という 2 つの意味があります. 次のような構成で起動させます.

  • USB メモリを FAT でフォーマットして
  • /EFI/BOOT/BOOTX64.EFI に iPXE のバイナリを配置し
  • 自作ブートローダカーネル本体となる ELF ファイルを HTTP サーバに配置し
  • iPXE でブートローダーと ELF ファイルを読み取って起動する.

Raphine Project の記事では UEFI の代わりに BIOS で起動させるためのディスクイメージを用意しているので,上記構成とはちょっと違いますね.

iPXE 起動用 USB メモリの準備

まずは iPXE のビルドに必要な依存パッケージをインストールします2

$ sudo apt-get install build-essential binutils-dev zlib1g-dev libiberty-dev liblzma-dev

次にダウンロード&ビルドします.

$ git clone git://git.ipxe.org/ipxe.git
$ cd ipxe/src
$ make bin-x86_64-efi/ipxe.efi

上記コマンドでは,アーキテクチャ x86_64 向けの UEFI アプリ(拡張子 .efi)を生成するように指示しています. ビルドが完了したら,USB メモリにファイルをコピーします. FAT でフォーマットしてある USB メモリが /mnt/usb にマウントされていると仮定して説明します.

$ mkdir -p /mnt/usb/EFI/BOOT
$ cp bin-x86_64-efi/ipxe.efi /mnt/usb/EFI/BOOT/BOOTX64.EFI

これで iPXE 起動用の USB メモリは完成です.

ブートローダカーネルの準備

自作ブートローダUEFI アプリ)とカーネル本体を適当なディレクトリに置いておき(仮に /path/to/files とします),そこを HTTP サーバからアクセスできるようにします.

$ cd /path/to/files
$ python3 -m http.server 8080

Python を使うと手軽に HTTP サーバを構築できるのでおすすめです.

iPXE による起動

Raphine Project の手順の kernel と initrd を次のように設定すればできます.

起動用スクリプトの記述や iPXE バイナリへの埋め込み方法は Raphine Project の記事を参考にしてください.

iPXE の高速化

何も設定をいじらずに iPXE をビルドすると余計な機能が組み込まれてしまい,iPXE の初期化時間が延びる原因となります.

iPXE initialising devices...

私の環境では上記の状態で数十秒時間がかかりました.

そんな時は ipxe/src/config/general.h でビルドオプションを調整すると改善する可能性があります. 私の環境では次に示す WiFi 関係のオプションを #undef したら非常に高速化しました.

#define CRYPTO_80211_WEP
#define CRYPTO_80211_WPA
#define CRYPTO_80211_WPA2

私が検証に使った MinnowBoard Turbot には無線 LAN インターフェースがないので,上記機能を初期化しようとしてタイムアウトしていた可能性がありますね.


  1. カーネル本体とブートローダーのビルドを分けられるのが大きな利点です.UEFI アプリとしてカーネルを作ってしまうと,コンパイラなどの選択が自由にできません.

  2. Raphine Project の記事では liblzma-dev を入れてないですが,私の場合は入れておかないとビルドが通りませんでした.