Raspberry Pi 2 で PXE boot してみる
このエントリーは、KLab Advent Calendar 2015 の12/3の記事です。
KLabとしては久々のAdvent Calendar参戦です。3番手も緊張しますね。全国行脚の旅に出ている pandax381 です。よろしくお願いします。
はじめに
今日は大阪〜岡山の移動で念願のエヴァ新幹線に搭乗してきました。
さて、本題。
RPi2 で遊んでいてネットワークブートしたい衝動に刈られ「PXEブートできると嬉しいけど、あれってIntel NIC 以外でもできるんだっけ?」とか「そもそも ARM だけど SYSLINUX 対応してるの?」とか思いながら調べていたら U-Boot が PXE クライアント機能を持っているらしいということが分かったので実際に PXE Boot 環境を構築してみました。
出回っている情報が古かったり、ドキュメント読んでも簡単に見つけられない事などが多かったので、備忘録も兼ねて公開することにしました。
準備
Raspbian をインストールした RPi2 を使いますので、公式サイトから Raspbian のイメージをダウンロードして microsd に書き込みます。
https://www.raspberrypi.org/downloads/raspbian/
最新版の 2015-11-21-raspbian-jessie-lite.img を dd コマンドで microsd に書き込みます
$ sudo dd if=~/Downloads/2015-11-21-raspbian-jessie-lite.img of=/dev/disk2 bs=1m 1391+0 records in 1391+0 records out 1458569216 bytes transferred in 166.354200 secs (8767853 bytes/sec)
多少のバージョンの違いは気にしなくても大丈夫です。
U-Boot のインストール
U-Boot(http://www.denx.de/wiki/U-Boot)は、様々なプラットフォームに対応したブートローダで、RPi2もサポートされています。U-Boot には PXE クライアントの機能があるため、これを利用して RPi2 で PXE ブートを試みます。
U-Boot をビルドするために git と bc が必要になるので、インストールされていなければ先にインストールしておきます。
$ sudo apt-get install git bc
まず、U-Boot の git リポジトリをクローンしてソースコードを入手します。最新のリリースは「v2016.01-rc1」というタグが付いているので、ビルド用のブランチを作ってそこにチェックアウトします。
$ git clone git://git.denx.de/u-boot.git $ cd u-boot $ git checkout -b build v2016.01-rc1
RPi2 用のコンフィグ「rpi_2_defconfig」が用意されているのでこれを使って make します。make all を実行すると u-boot.bin が生成されるので、これを /boot 直下へコピーします。
$ make rpi_2_defconfig $ make all $ sudo cp u-boot.bin /boot/
続いては RPi2 のファームウェアの設定です。組み込みのブートローダから実行される RPi2 のファームウェアは、次に実行するプログラムとして Linux Kernel をロードするようになっていますが、この動作はファームウェアの設定ファイルにて変更することが出来ます。RPi2 のファームウェアが Linux Kernel の代わりに u-boot.bin を実行するように、/boot/config.txt に以下の設定を追加します。
kernel=u-boot.bin
これで U-Boot のインストールは完了です。この状態で RPi2 をリブートすると、Linux ではなく U-Boot が起動するようになりました...が、次の手順を踏むまでまだ再起動はしないでください。
U-Boot を使って microSD からブートする
U-Boot が起動しても、U-Boot のコマンドを実行しなければ Linux をブートすることはできません。スクリプトファイルを作成しておくと、U-Boot が起動時にスクリプトファイルを読み込んで自動実行してくれる機能があるので、これを利用します。まず、以下の内容で boot.txt を作成します。
mmc dev 0 setenv bootargs "earlyprintk console=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait" fatload mmc 0:1 ${kernel_addr_r} kernel7.img fatload mmc 0:1 ${fdt_addr_r} bcm2709-rpi-2-b.dtb bootz ${kernel_addr_r} - ${fdt_addr_r}
boot.txt を U-Boot が読み込めるイメージ形式に変換するために mkimage コマンドを実行します。最終的な出力ファイルは /boot/boot.scr で、U-Boot は起動時にこのファイルが存在するかどうかチェックしています。
$ sudo ~/u-boot/tools/mkimage -A arm -O linux -T script -C none -a 0 -e 0 -n "MMC Boot" -d boot.txt /boot/boot.scr
ここまで出来たら、一度 RPi2 をリブートさせます。
$ sudo reboot
はじめに U-Boot が起動し、自動的に boot.scr スクリプトが実行されて Linux Kernel が起動します。RPi2 のファームウェアと Linux Kernel の間に U-Boot が追加されましたが、これまでと同じように Linux が起動しているはずです。
U-Boot 2016.01-rc1 (Dec 01 2015 - 19:08:12 +0900) DRAM: 944 MiB WARNING: Caches not enabled RPI 2 Model B MMC: bcm2835_sdhci: 0 reading uboot.env ** Unable to read "uboot.env" from mmc0:1 ** Using default environment In: serial Out: lcd Err: lcd Net: Net Initialization Skipped No ethernet found. Hit any key to stop autoboot: 0 switch to partitions #0, OK mmc0 is current device Scanning mmc 0:1... Found U-Boot script /boot.scr reading /boot.scr 358 bytes read in 46 ms (6.8 KiB/s) ## Executing script at 02000000 switch to partitions #0, OK mmc0 is current device reading kernel7.img 4035396 bytes read in 5894 ms (668 KiB/s) reading bcm2709-rpi-2-b.dtb 11113 bytes read in 54 ms (200.2 KiB/s) Kernel image @ 0x1000000 [ 0x000000 - 0x3d9250 ] ## Flattened Device Tree blob at 00000100 Booting using the fdt blob at 0x000100 Loading Device Tree to 3ab42000, end 3ab47b68 ... OK Starting kernel ... Uncompressing Linux... done, booting the kernel. [ 0.000000] Booting Linux on physical CPU 0xf00 [ 0.000000] Initializing cgroup subsys cpuset [ 0.000000] Initializing cgroup subsys cpu [ 0.000000] Initializing cgroup subsys cpuacct ... Raspbian GNU/Linux 8 raspberrypi ttyAMA0 raspberrypi login:
U-Boot を使って PXE でネットワークブートする
U-Boot が正常に動作することが確認できたので、本題の PXE boot の設定をします。まず、先ほど作成した boot.scr を boot_mmc.scr にリネームして退避させておきます。
$ sudo mv /boot/boot.scr /boot/boot_mmc.scr
ここから PXE でネットワークブートするための設定をしていきますが、失敗した場合など再び microSD からブートしたくなるかもしれません。その場合には、U-Boot が起動して「Hit any key to stop autoboot:」というメッセージが表示されたら何かキーを押すとコマンド入力できるようになります。その状態で以下のコマンドを入力すると、boot_mmc.scr を使って先ほどと同様に microSD からブートできます。
U-Boot> load mmc 0:1 0x2000000 boot_mmc.scr U-Boot> source 0x2000000
PXE Boot 用に U-Boot のブートスクリプト boot.txt を作成します。
setenv autoload no setenv bootcmd_pxe 'usb start; setenv ethaddr ${usbethaddr}; dhcp; if pxe get; then pxe boot; fi' run bootcmd_pxe
先ほどと同様に mkimage コマンドで boot.scr を生成します。
$ sudo ~/u-boot/tools/mkimage -A arm -O linux -T script -C none -a 0 -e 0 -n "PXE Boot" -d boot.txt /boot/boot.scr
U-Boot の PXE クライアントが TFTP で取得する設定ファイルを作成します。設定ファイルの命名規則は SYSLINUX とほぼ同じなので http://www.syslinux.org/wiki/index.php/PXELINUX#Configuration_filename あたりを参考にしてください。
U-Boot の PXE クライアントは、pxeuuid が設定されていなければ、01-xx-xx-xx-xx-xx-xx というMACアドレスベースのファイルを一番はじめに取得します。したがって、pxelinux.cfg ディレクトリ配下にクライアント毎にMACアドレスベースの設定ファイルを作成しておけば大丈夫です。
SYSLINUX(PXELINUX)のコンフィグと雰囲気は似ていますが、若干書式が異なります。smsc95xx.macaddr に U-Boot の環境変数を使って MAC アドレスを渡していますが、これを指定し忘れると Kernel が実際の MAC アドレスを得られず自動生成されたものが使われてしまうので注意してください。
tftp-server$ cat /srv/tftpboot/pxelinux.cfg/01-xx-xx-xx-xx-xx-xx menu title PXE boot menu default mmc prompt 1 timeout 30 label mmc menu label Linux raspberrypi 4.1.13-v7+ (mmc) kernel kernel7.img devicetree bcm2709-rpi-2-b.dtb append earlyprintk dwc_otg.lpm_enable=0 smsc95xx.macaddr=${usbethaddr} console=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait
上記の設定は、Kernel と DeviceTree ファイルだけ TFTP で取得するものの rootfs は microSD 内にあるため、完全なネットワークブートではありませんが、U-Boot の PXE クライアント機能が正しく動作するか確認するために、この状態でリブートしてみます。
$ sudo reboot
PXE クライアントが pxelinux.cfg 配下の設定ファイルを取得し、続いて Kernel と DeviceTree ファイルを取得してブートし始めます。いずれかのファイルが取得できずにエラーとなるようであれば、TFTP サーバの設定やファイル名に誤りがある可能性が高いです。
U-Boot 2016.01-rc1 (Dec 01 2015 - 19:08:12 +0900) DRAM: 944 MiB WARNING: Caches not enabled RPI 2 Model B MMC: bcm2835_sdhci: 0 reading uboot.env ** Unable to read "uboot.env" from mmc0:1 ** Using default environment In: serial Out: lcd Err: lcd Net: Net Initialization Skipped No ethernet found. Hit any key to stop autoboot: 0 switch to partitions #0, OK mmc0 is current device Scanning mmc 0:1... Found U-Boot script /boot.scr reading /boot.scr 205 bytes read in 47 ms (3.9 KiB/s) ## Executing script at 02000000 starting USB... USB0: Core Release: 2.80a scanning bus 0 for devices... 3 USB Device(s) found scanning usb for storage devices... 0 Storage Device(s) found scanning usb for ethernet devices... 1 Ethernet Device(s) found Waiting for Ethernet connection... done. BOOTP broadcast 1 BOOTP broadcast 2 BOOTP broadcast 3 DHCP client bound to address 192.168.0.15 (1015 ms) missing environment variable: pxeuuid missing environment variable: bootfile Retrieving file: pxelinux.cfg/01-xx-xx-xx-xx-xx-xx Waiting for Ethernet connection... done. Using sms0 device TFTP from server 192.168.0.1; our IP address is 192.168.0.15 Filename 'pxelinux.cfg/01-xx-xx-xx-xx-xx-xx'. Load address: 0x100000 Loading: # 19.5 KiB/s done Bytes transferred = 342 (156 hex) Config file found PXE boot menu 1: Linux raspberrypi 4.1.13-v7+ (mmc) Enter choice: 1: Linux raspberrypi 4.1.13-v7+ (mmc) missing environment variable: bootfile Retrieving file: kernel7.img Waiting for Ethernet connection... done. Using sms0 device TFTP from server 192.168.0.1; our IP address is 192.168.0.15 Filename 'kernel7.img'. Load address: 0x1000000 Loading: ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ######### 326.2 KiB/s done Bytes transferred = 4035396 (3d9344 hex) append: earlyprintk dwc_otg.lpm_enable=0 smsc95xx.macaddr=xx:xx:xx:xx:xx:xx console=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait missing environment variable: bootfile Retrieving file: bcm2709-rpi-2-b.dtb Waiting for Ethernet connection... done. Using sms0 device TFTP from server 192.168.0.1; our IP address is 192.168.0.15 Filename 'bcm2709-rpi-2-b.dtb'. Load address: 0x100 Loading: ### 252 KiB/s done Bytes transferred = 11113 (2b69 hex) Kernel image @ 0x1000000 [ 0x000000 - 0x3d9250 ] ## Flattened Device Tree blob at 00000100 Booting using the fdt blob at 0x000100 Loading Device Tree to 3ab41000, end 3ab46b68 ... OK Starting kernel ... Uncompressing Linux... done, booting the kernel. [ 0.000000] Booting Linux on physical CPU 0xf00 [ 0.000000] Initializing cgroup subsys cpuset [ 0.000000] Initializing cgroup subsys cpu [ 0.000000] Initializing cgroup subsys cpuacct
ブートできないケースとして、DHCP サーバの設定に next-server が設定されていない可能性もあります。PXEブートでは、DHCP の next-server で指定されたサーバに TFTP でファイルを取得しに行きます。そのため、DHCP サーバの設定には必ず next-server として TFTPサーバのアドレスを指定してください。なお、一般的な PXE 環境では filename に pxelinux.0 を指定しますが、これは不要です。pxelinux.0 は PXE クライアントプログラムですが、U-Boot には pxelinux.0 相当のクライアント機能が備わっているため、この設定は必要ありません。
initramfs を作って完全なネットワークブートにする
上記の例では rootfs が microsd にあるため、まだネットワークブートとは呼べない状態です。ここでは、U-Boot 以外の必要なファイルを全てネットワーク越しに取得し、ネットワークブートと呼べる状態にします。
手軽に確認できるよう、単純な initramfs を rootfs として利用します。initramfs は busybox があれば簡単につくれます。
$ sudo apt-get install busybox-static $ sudo mkdir -p initramfs/{bin,dev,proc,sys} $ sudo busybox --install -s initramfs/bin $ sudo cp /bin/busybox initramfs/bin
initramfs では /init スクリプトが実行されるので、initramfs 直下に、init という名前のシェルスクリプトを作成します。
#!/bin/sh export PATH="/bin" mount -t devtmpfs devtmpfs /dev mount -t proc proc /proc mount -t sysfs sysfs /sys sleep 3 exec setsid cttyhack sh
実行権限を忘れずに付与します。
$ sudo chmod +x initramfs/init
これを cpio でアーカイブすれば initramfs の出来上がりです。この initramfs.cpio.gz を Kernel や DeviceTree ファイルと同じように、TFTP サーバのドキュメントルート直下にコピーしておきます。
$ cd rootfs $ sudo find . | cpio -o -H newc | gzip -c > ../initramfs.cpio.gz
PXE クライアントの設定ファイルに initrafms を使ってブートする設定を追加します。initramfs を使っていても initrd として記述します。
tfpt-server$ cat /srv/tftpboot/pxelinux.cfg/01-xx-xx-xx-xx-xx-xx menu title PXE boot menu default mmc prompt 1 timeout 30 label mmc menu label Linux raspberrypi 4.1.13-v7+ (mmc) kernel kernel7.img devicetree bcm2709-rpi-2-b.dtb append earlyprintk dwc_otg.lpm_enable=0 smsc95xx.macaddr=${usbethaddr} console=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait label initramfs menu label Linux raspberrypi 4.1.13-v7+ (initramfs) kernel kernel7.img devicetree bcm2709-rpi-2-b.dtb initrd initramfs.cpio.gz append earlyprintk dwc_otg.lpm_enable=0 smsc95xx.macaddr=${usbethaddr} console=ttyAMA0,115200 elevator=deadline rootwait
RPi2 をリブートして動作確認します。
$ sudo reboot
今回は、PXEクライアントの設定ファイルに2つのエントリが存在するため、どちらの設定で起動するか選択することになります。なにも選択せずにいるとデフォルトの「1」が選ばれるので、素早く「2」を入力します。initramfs が正常に機能していれば、ブート後に busybox の ash が起動するはずです。
U-Boot 2016.01-rc1 (Dec 01 2015 - 19:08:12 +0900) ... PXE boot menu 1: Linux raspberrypi 4.1.13-v7+ (mmc) 2: Linux raspberrypi 4.1.13-v7+ (initramfs) Enter choice: 2 ... BusyBox v1.22.1 (Raspbian 1:1.22.0-9+deb8u1) built-in shell (ash) Enter 'help' for a list of built-in commands. / #
ここでは単純な initramfs だけで完結する例を示しましたが、initramfs の中から HTTP などで rootfs をダウンロードし swich_root するようなコードを書けば、しっかりとした実用的な PXE Boot のシステムが構築できます。
おわりに
明日は hasi_t さんです、お楽しみに。