2015年12月03日

Raspberry Pi 2 で PXE boot してみる

はてなブックマークに登録

このエントリーは、KLab Advent Calendar 2015 の12/3の記事です。

KLabとしては久々のAdvent Calendar参戦です。3番手も緊張しますね。全国行脚の旅に出ている pandax381 です。よろしくお願いします。

はじめに

今日は大阪〜岡山の移動で念願のエヴァ新幹線に搭乗してきました。

IMG_1563
IMG_4729
IMG_1751

さて、本題。

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 さんです、お楽しみに。

klab_gijutsu2 at 19:06│Comments(0)TrackBack(0)

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント