LeavaTailの日記

LeavaTailの日記

Linuxエンジニアを目指した技術者の備忘録

Raspberry Pi 3 Model B をネットワークブートで起動させる

関連記事

概要

Raspberry Pi 3 Model BからLinuxカーネル (Raspbian)をネットワークブートするための環境を構築する方法を解説する。

Raspberry Pi 3Bのネットワークブート概要図

Raspberry Pi 3Bは、SDカードを挿入せずにネットワーク上にあるネットワークサーバからブートイメージとルートファイルシステムを取得する。
また、ネットワークブート用のサーバとして、Ubuntu 18.04 LTS上にdnsmasqとnfs-kernel-serverをインストールした。

はじめに

組込みシステムの開発段階やデバッグ段階では、ネットワーク経由でシステムを起動(ネットワークブート)できることが理想的である。
ネットワークブートができる環境が構築されていれば、組込みシステムのファームウェア再書き込みせずにデータを共有することができる。

Raspberry PiARMプロセッサを搭載したシングルボードコンピュータの一つで、手軽に入手できる点や世の中に情報が多い点から組込みシステムの入門として用いられることが多い。

Raspberry Pi 3Bについて

Raspberry Pi 3 Model B(Raspberry Pi 3B)は複数のブートモードがある。下記は、Raspberry Pi 3Bの起動シーケンスを示している。

Raspberry Pi 3Bの起動シーケンス

Raspberry Pi 3B では、電源投入されるとOne-Time Programmable (OTP) メモリをロードする。
OTPメモリにはブートモードを決定するフラグが保存されており、Raspberry Pi 3B のGPUはこのフラグを基にブートを試みる。

各ブートモードは、ブートに必要なファイル (ブートイメージ) をそれぞれの方法でメモリにロードする。

Raspberry Pi 3B のブートイメージは下記のファイルがある。

状態 概要
config.txt システム構成パラメータ
start.elf 一般的なRaspberry Piファームウェア
fixup.dat start.elfのリンカファイル
bcm2710-rpi-3-b.dtb ハードウェアの構成情報(デバイスリーファイル)
cmdline.txt カーネルコマンドライン
kernel7.img カーネルイメージ
bootcode.bin ブートローダ

Raspberry Pi 3Bのネットワークブートでは、TFTPでブートイメージを取得する。 ネットワークブートを用いたRaspberry Pi 3Bの起動シーケンスを下記に示す。

ネットワークブートのシーケンス

  1. Raspberry Pi 3BではオンボードEthernetドライバの初期化をした後、LAN内にDHCPリクエスをブロードキャストする。
  2. DHCPリクエストを受け取ったネットワークブートサーバは、TFTPサーバのIPアドレスを送信する。
  3. Raspberry Pi 3Bは、TFTPサーバのIPアドレスからブートイメージを取得し、カーネルをブートする。

しかし、Raspberry Pi 3Bのネットワークブートには幾つかのバグが報告されている。

Known problems

  • DHCP requests time out after five tries
  • TFTP server on separate subnet not supported
  • DHCP relay broken
  • Raspberry Pi Boot string
  • DHCP UUID constant
  • ARP check can fail to respond in the middle of TFTP transaction
  • DHCP request/reply/ack sequence not correctly implemented

このため、Raspberry Pi 3Bでネットワークブートを試している人は少ない。*1 そこで本記事では、Raspberry Pi 3BからRaspbianをネットワークブートするための環境を構築する方法を紹介する。

実行環境

実験環境として、セグメントが二つに分かれている下記のネットワーク構成のものを使用する。

ネットワーク構成図

ホームセグメントは、一般的なPCや家電製品などが接続されており家庭用ルータによって管理されている。 開発用セグメントは、Raspberry Pi 3B が接続されておりネットワークブートサーバによって管理されている。

本運用では、ホームセグメントにあるクライアントPC (Windows 10 Home) からネットワークブートサーバ(Ubuntu 18.04 LTS) にSSH経由でアクセスする。 また、ネットワークブートサーバとRaspberry Pi 3Bはシリアル接続されている。

ネットワークブートサーバの詳細は下記のとおりである。

名前 詳細
OS Ubuntu 18.04 LTS
Kernel 5.3.0-40-generic
NIC (ホームセグメント側) eth0
NIC (開発側セグメント) eth1
IPアドレス (eth0) 192.168.1.11
IPアドレス (eth1) 172.16.1.1
ホスト名 server
Raspberry Pi 3Bのブートイメージ格納予定 /srv/boot
Raspberry Pi 3Bのルートファイルシステム格納予定 /srv/rootfs
シリアルデバイスファイル /dev/ttyUSB0
シリアルポートのボーレート 115200

また、使用するRaspberry Pi 3Bの詳細は下記のとおりである。

名前 詳細
ファームウェア 4.19.97-v7+
NIC eth0
IPアドレス 172.16.1.2 (DHCP)
ホスト名 raspberry
ストレージ なし

Raspberry Pi 3Bの初期セットアップ

本記事が目標とするネットワークブートの構成は下記のとおりである。

ネットワークブートのフロー図

Raspberry Pi 3BのUSBブートフラグをONにし、ネットワークブートで起動できるように設定をする。

  • 注意: USBブートフラグをONにするために、Raspbianの入ったSDカードが必要になる。

ネットワークブートサーバには、DockerコンテナとしてDHCP/TFTPサーバを構築しブートイメージを格納しておく。
また、ホストにNFSサーバを構築しルートファイルシステムを格納しておく。

今回は、Raspbian Buster Liteのブートイメージとルートファイルシステムを利用する。

この状態で、SDカードからRaspbianを起動する

  1. 現在のブートフラグを確認する (OPT bitより、Bit29がネットワークブートのフラグとなっている)

     pi@raspberry:~$ vcgencmd otp_dump | grep 17:
     > 17:1020000a    # 現在はOFFになっている
    
  2. /boot/config.txtを修正する

    pi@raspberry:~$ echo "program_usb_boot_mode=1" | sudo tee -a /boot/config.txt 
    
  3. 再起動する

     pi@raspberry:~$ sudo reboot
    
  4. 再度、ブートフラグを確認する

     pi@raspberry:~$ vcgencmd otp_dump | grep 17:
     > 17:3020000a    # 現在はONになっている
    

上記の設定により、OTPメモリにUSBブートフラグが設定された。
これ以降ネットワークブートが可能になったので、Raspberry Pi 3BからSDカードを抜いておく

ネットワークブートサーバの設定

ネットワークブートサーバの設定は、「ブートイメージとルートファイルシステムの取得」と「DHCP/TFTPサーバの構築」、「NFSサーバの構築」の3ステップ必要になる。

ブートイメージとルートファイルシステムの取得

  1. Raspbianのイメージを取得する

     user@server:~$ wget https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2020-02-14/2020-02-13-raspbian-buster-lite.zip
     user@server:~$ unzip 2020-02-13-raspbian-buster-lite.zip
    
  2. 取得したディスクイメージをマウントする

     user@server:~$ sudo kpartx -a 2020-02-13-raspbian-buster-lite.img 
     user@server:~$ sudo mount /dev/mapper/loop0p1 /mnt/boot
     user@server:~$ sudo mount /dev/mapper/loop0p2 /mnt/rootfs
    
  3. ディスクイメージをTFTPサーバとNFSサーバの公開先ディレクトリにコピーする

     user@server:~$ sudo rsync -av /mnt/boot /srv/tftpboot/
     user@server:~$ sudo rsync -av /mnt/rootfs /srv/rootfs
    
  4. ディスクイメージをアンマウントする

     user@server:~$ sudo umount /mnt/rootfs
     user@server:~$ sudo umount /mnt/boot
     user@server:~$ sudo kpartx -d 2020-02-13-raspbian-buster-lite.img 
    
  5. カーネルコマンドラインを修正し、NFSルートとシリアルコンソールを設定する

     user@server:~$ sudo mv /srv/tftpboot/cmdline.txt /srv/tftpboot/cmdline.txt.old
     user@server:~$ echo "console=serial0,115200 console=tty1 root=/dev/nfs nfsroot=172.16.1.1:/srv/rootfs,vers=3,proto=tcp rw ip=dhcp rootwait elevator=deadline" | sudo tee /srv/tftpboot/cmdline.txt 
    
  6. UARTを有効にする

     user@server:~$ echo "enable_uart=1" | sudo tee -a /srv/tftpboot/config.txt 
    
  7. Raspberry Pi 3B側のファイルシステムテーブルを修正する

     user@server:~$ sudo mv /srv/rootfs/etc/fstab /srv/rootfs/etc/fstab.old
     user@server:~$ echo "proc            /proc           proc    defaults          0       0" | sudo tee /srv/rootfs/etc/fstab 
    

DHCP/TFTPサーバの構築

著者が使用しているDockerイメージはGitHubにて公開しているので、構築が手間な方はそちらを使用してほしい。

  1. ベースとして使用するAlpine Linuxのイメージを取得する

     user@server:~$ docker pull alpine:3.11.3
    
  2. Dockerコンテナを起動させる

     user@server:~$ docker run --privileged --net=host -v /srv/tftpboot:/srv  -it alpine:3.11.3 /bin/sh
    
  3. dnsmasqをインストールする

     / # apk update
     / # apk add dnsmasq
    
  4. dnsmasqの設定ファイルを修正する (修正箇所は下記のとおり)

     @@ -10 +10 @@
     -#port=5353
     +port=0
     @@ -21 +21 @@
     -#bogus-priv
     +bogus-priv
     @@ -106 +106 @@
     -#interface=
     +interface=eth1
     @@ -157 +157 @@
     -#dhcp-range=192.168.0.50,192.168.0.150,12h
     +dhcp-range=172.16.1.2, 172.16.1.65, 12h
     @@ -477 +477 @@
     -#pxe-service=x86PC, "Boot from local disk"
     +pxe-service=0, "Raspberry Pi Boot"
     @@ -499 +499 @@
     -#enable-tftp
     +enable-tftp
     @@ -502 +502 @@
     -#tftp-root=/var/ftpd
     +tftp-root=/srv
     @@ -664 +664 @@
     -#log-dhcp
     +log-dhcp
    
  5. dnsmasqを起動させる

     / # dnsmasq
    
  6. dockerコンテナからデタッチする

     / # <Ctrl-p> <Ctrl-q>
    

NFSサーバの構築

NFSサーバもDockerコンテナとして管理しても良いが、ディレクトリ共有のオーバーヘッドを懸念して、ホストに直接立てることにした。

  1. NFSサーバのnfs-kernel-serverをインストールする

     user@server:~$ sudo apt install nfs-kernel-server 
    
  2. NFSで公開するディレクトリを追加する

     user@server:~$ echo "/srv/rootfs  172.16.1.0/255.255.255.0(rw,sync,no_root_squash,no_subtree_check)" | sudo tee -a 
     /etc/exports 
     user@server:~$ sudo exportfs -ra
    

実行結果

minicom経由でアクセスする。

    user@server:~$ sudo minicom /dev/ttyUSB0 
    <--snip-->
    Raspbian GNU/Linux 10 raspberrypi ttyS0

    raspberrypi login: 

おわりに

本記事では、Raspberry Pi 3BにSDカードを挿入せずブートすることができるネットワークブートの方法を紹介した。
このように、バグが報告されているRaspberry Pi 3Bでもネットワークブートすることができた。

今回は、ネットワークブートサーバにdnsmasqとnfs-kernel-serverをインストールする手法を紹介したが、その他のパッケージ (isc-dhcp-serverやtftpd-hpa)を用いても構築することができる。
また、公開されているRaspbian Buster Liteのディスクイメージのルートファイルシステムをそのまま使用しているので、SSHホストキーの再生成は実施しておくとよい。

変更履歴

  • 2020/03/15: 記事公開
  • 2022/06/07: 章構成の修正

参考

SDカードを使わず、network bootでRaspberry Pi 3Bを起動する

SDカードにU-Bootのみ格納し、network bootでRaspberry Pi 3Bを起動する

SDカードを使わず、network bootでRaspberry Pi 3B+を起動する

Raspberry Pi の起動シーケンス

Raspberry Pi のブートで必要なファイル郡

付録

Dockerで使用したオプションは以下のとおりである。

オプション名 詳細 指定理由
--privileged コンテナを特権モードで動作させる DHCPリレーサービスで物理ノードを参照するため。
指定しないと、ARP-cache injection failed: Operation not permitted dockerとエラーになってしまう
--net=host ホスト側のネットワークスタックをコンテナに接続する ホスト側のネットワークセグメントでDHCPを公開する範囲を制限するため。
-v /srv/tftpboot:/srv ホスト側のブートイメージ格納先をマウントする コンテナ内のTFTPサービスからブートイメージを参照するため。

dnsmasqで指定したオプションは以下のとおりである。

パラメータ 詳細
port 0 DNS サーバーは不要なので無効化しておく
bogus-priv プライベートIPの逆引きは上位DNSに転送しない
interface eth1 開発用セグメントを指定する
dhcp-range 172.16.1.2, 172.16.1.65, 12h DHCPで払い出すIPアドレスの範囲を指定する
pxe-service Raspberry Pi Boot Raspberry Pi では「Raspberry Pi Boot」と指定されているパケットのみ受け付ける
enable-tftp TFTPサーバを有効化する
tftp-root /srv TFTPサーバが提供するルートディレクトリをコンテナ起動時にマウントしたディレクトリを指定する。
log-dhcp dhcp関連の詳細なログを出力する

*1:Raspberry Pi 3 Model B+ではバグが修正されているので、そちらで実施している人が多い