LeavaTailの日記

LeavaTailの日記

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

QEMUでx86_64用Linuxカーネルを起動する

関連記事

概要

x86_64用にLinuxカーネルをビルド、BuildRootで構築したルートファイルシステムを用意する。
また、QEMUでこれらをバイナリを動かし、ルートファイルシステムがマウントできることを確認した。

はじめに

x86Intelが開発したマイクロプロセッサの命令セットアーキテクチャであり、x86_64x86を64ビットに拡張した命令セットアーキテクチャである。
x86はパーソナルコンピュータやサーバなど幅広く使われている。

一方、プロセッサエミュレータQEMUは、ブートローダの再設定などせずにカーネルramdiskを直接ロードすることができる。 そこで、プロセッサエミュレータでもあるQEMUを用いてx86_64用にビルドされたLinuxカーネルを動かす方法を解説する。

本記事では、以下の動作をする環境を目指す。

カーネル起動ワークフローとメモリマップイメージ図

環境構成

ホスト環境x86_64アーキテクチャに構築する。

実施環境

起動対象のLinuxカーネルは、ホストOS(Ubuntu 18.04)上にインストールしたQEMUから動作させる。 また、今回はクロスコンパイラ環境をDockerで構築した。

ルートファイルシステムとして、Buildrootで生成したルートファイルシステムの非圧縮のcpio形式のアーカイブを利用する。

本記事は、下記の環境とソフトウェアバージョンに基づいて説明する。

環境 パラメータ
ホスト環境 x86_64
ホストOS Ubuntu 20.04
Buildroot buildroot-2020.02.8
QEMU QEMU emulator version 4.2.1
linux 5.7
Docker version 19.03.13
Docker image ubuntu:20.04

ロスコンパイラ環境の構築手順

  1. ホスト環境にDockerをインストールする。 docs.docker.com

  2. ロスコンパイラ環境を構築する。(作成が億劫な人は、著者自作Dockerfile(https://github.com/LeavaTail/kernel-build)を使用してほしい)

     leava@ubuntu-bionic:~$ docker run --rm --name=kbuild -h "kbuild" -v /srv:/work -it ubuntu:20.04 /bin/bash 
    
  3. コンテナ環境下にカーネルのビルドに必要なパッケージをインストールする。(今回はUbuntuコンテナを利用する)

     root@kbuild:/# apt install git bc bison flex libssl-dev make libncurses-dev libelf-dev file wget cpio unzip rsync build-essential
    
  4. カーネルソースの取得

     root@kbuild:/# cd /work
     root@kbuild:/work# git clone https://github.com/torvalds/linux.git
     root@kbuild:/work# cd linux
     root@kbuild:/work/linux# git checkout -b v5.7 refs/tags/v5.7
    
  5. ロスコンパイラ環境用の環境変数を設定する。

     root@kbuild:/work/linux# export ARCH="x86"
    
  6. x86Linuxカーネル用のコンフィグを生成する。

     root@kbuild:/work/linux# make x86_64_defconfig
    
  7. カーネルをビルドする。

     root@kbuild:/work/linux# make -j `getconf _NPROCESSORS_ONLN` bzImage
    

ルートファイルシステムの構築

Buildrootを用いてrootfsを構築する。

  1. Buildrootをインターネットからダウンロード、ファイルを解凍する。

     leava@ubuntu-bionic:~$ wget https://buildroot.org/downloads/buildroot-2020.02.8.tar.gz
     leava@ubuntu-bionic:~$ tar zxvf  buildroot-2020.02.8.tar.gz && cd buildroot-2020.02.8
    
  2. x86_64専用のデフォルトコンフィグqemu_x86_64_defconfigを利用する。

     leava@ubuntu-bionic:~/buildroot-2020.02.8$ make qemu_x86_64_defconfig
    
  3. Buildrootのビルド

     leava@ubuntu-bionic:~/buildroot-2020.02.8$ make
    
  4. ルートファイルシステムの確認

     leava@ubuntu-bionic:~/buildroot-2020.02.8$ ls -l output/images/rootfs.ext4
     lrwxrwxrwx 1 root root 11 Sep  8 15:45 output/images/rootfs.ext4 -> rootfs.ext2
    

カーネルの起動

  1. x86QEMUをインストールする。

     leava@ubuntu-bionic:~$ sudo apt install qemu-system-x86
    
  2. 作成したカーネルQEMUで実行する。

     leava@ubuntu-bionic:~$ qemu-system-x86_64 \
         -kernel /srv/linux/arch/x86/boot/bzImage \
         -drive file=/srv/buildroot-2020.02.8/output/images/rootfs.ext2,if=ide,format=raw
         -nographic \
         -append "root=/dev/sda console=ttyS0"
    

おわりに

本記事では、QEMUx86_64用Linuxカーネルを起動させる手順を説明した。
Buildrootで構築した場合、自動でセットアップしてくれるため非常に使いやすく便利である。

変更履歴

  • 2020/05/26: 記事公開
  • 2020/12/11: ブログタイトルを "x86_64用" に訂正
  • 2020/12/14: GDBスタブの手順を削除

参考

QEMULinuxカーネルを起動する

linuxカーネルデバッグ

Raspberry Pi 3 Model B をネットワークブートで起動させる (U-Boot編)

関連記事

概要

Raspberry Pi 3 Model B からSDカードに保存したU-Boot (32bit) を起動し、メインラインカーネル (32bit) をネットワークブートするための環境を構築する方法を解説する。

Raspberry Pi 3Bのネットワークブート概要図 (U-boot)

Raspberry Pi 3B にはU-Bootのバイナリを格納したSDカードを差し込み、U-Bootを利用してネットワーク上にあるネットワークサーバからブートイメージとルートファイルシステムを取得する。
ネットワークブート用のサーバはUbuntu 18.04 LTS上にdnsmasqとnfs-kernel-serverをインストールし、メインラインカーネル (32bit) *1をビルドしておく。

はじめに

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

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

しかし、Raspberry Pi 3B のROMには幾つかの不具合が報告されており、ROMによるネットワークブートが安定しない。

一方で、Raspberry Pi 3B のROMから別のブートローダをロードし、そのブートローダからネットワークブートする手法が存在する。 Das U-Boot(U-Boot)は組込み系で広く利用されているブートローダの一つであり、Raspberry Pi 3B でも利用することができる。

U-Bootには機能として、簡易のコマンドやドライバが組み込まれており、TFTPを利用してネットワークブートすることができる。

U-Bootでネットワークブートを用いたRaspberry Pi 3Bの起動シーケンスを下記に示す。

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

  1. Raspberry Pi 3Bに電源投入すると、GPUが起動する。
  2. GPUは1段目のブートローダを起動。One-Time Programmable (OTP) メモリのブートモードに従ったメディア(今回はSD card)から、2段目のブートローダbootcode.binを取得する。
  3. bootcode.binを実行する。
  4. bootcode.binSDRAMを有効化する。
  5. bootcode.binファームウェアstart.elfを読み込む。
  6. start.elfを実行する。
  7. start.elfconfig.txtを指定されたメディア(今回はSD card)から取得する。
  8. start.elfcmdline.txtを指定されたメディア(今回はSD card)から取得する。
  9. start.elfconfig.txtのkernelパラメータで指定されたu-boot.binを指定されたメディア(今回はSD card)から取得する。
  10. u-bootを実行する。
  11. u-bootTFTP経由でzImageを取得する。
  12. 取得したzImageSDRAMの指定場所(${kernel_addr_r})に読み込む。
  13. u-bootTFTP経由でbcm2837-rpi-3-b.dtbを取得する。
  14. 取得したbcm2837-rpi-3-b.dtbSDRAMの指定場所(${fdt_addr_r})に読み込む。
  15. ARM coreにリセット指令を発行し、${kernel_addr_r}番地からプログラムを実行する。

下記は、Raspberry Pi 3Bが起動するまでに必要となるファイル群である。

ファイル名 格納先 概要
config.txt SD card システム構成パラメータ
cmdline.txt SD card カーネルコマンドライン(もしかしたら不要)
start.elf SD card 一般的なRaspberry Piファームウェア
fixup.dat SD card start.elfのリンカファイル
bootcode.bin SD card SoCに備わっているブートローダ
zImage Server カーネルイメージ
bcm2710-rpi-3-b.dtb Server バイスリーファイル (bcm2837-rpi-3-b.dtb でも代用可能)

そこで本記事では、Raspberry Pi 3B でメインラインカーネルをネットワークブートするための環境を構築する方法を紹介する。

各手法における特徴

前回の記事では、Raspberry Pi 3Bをネットワークブートする手法としてROM内のファームウェアを利用した。 ネットワークブートのための手法はいくつか存在しているが、それぞれ特徴が異なる。

ROM内のファームウェアを利用した場合の特徴としては以下の点があげられる。

  • SDカード周りのデバッグやドライバの開発などに向いている。
  • 運用中はSDカードが不要なので、SDカードを用意するコストを最小限に抑えることができる。

一方、U-Bootを利用した場合の特徴としては以下の点があげられる。

  • 起動対象の差を設定によって吸収できる。
  • (Raspberry Pi 3B の場合) 不具合の影響が小さい。
  • U-Bootに備わっている簡易コマンドを利用することで、別プログラムをロードしたり多段ブートすることを容易である。

実行環境

実験環境は前回構築した環境をそのまま使用する。

ネットワーク構成図

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

名前 詳細
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
ストレージ HIDISC microSDHCカード 8GB CLASS10 UHS-1対応
バイスファイル /dev/sdb1

目標とする環境

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

ネットワークブートの動作イメージ

Raspberry Pi 3Bに挿入してあるU-Boot(32bit)がネットワークブートを担うこと」と「起動するカーネルはメインラインカーネルのARM(32bit)」以外は前回の記事と同様のことを目指す。

今回使用したソフトウェアのバージョンは下記のとおりである。

名前 バージョン
U-Boot v2020.07-rc1
mainline kernel v5.7-rc3
dnsmasq v2.79-1
nfs-common 1:1.3.4-2.1ubuntu5.2

Raspberry Pi 3Bの設定

ネットワークブートサーバの設定は、システム構成パラメータの更新U-Bootの構築の2ステップ必要になる。

システム構成パラメータの更新

  1. config.txtから読み込むカーネルファイルをU-Bootに変更する。

     user@server:~$ mount /dev/sdb1 /mnt/tmp
     user@server:~$ echo 'kernel=u-boot.bin' > /mnt/tmp/config.txt
     user@server:~$ umount /mnt/tmp
    

U-Bootの構築

今回の手順では、別のマシン(ネットワークブートサーバ)でU-Bootをビルドし、バイナリをSDカードにコピーする。

  1. リポジトリをダウンロードする。

      user@server:~$ git clone git://git.denx.de/u-boot.git
      user@server:~$ cd u-boot
    
  2. ARMクロスコンパイラ(32bit)をインストールする。

      user@server:~$ apt-get install gcc-arm-linux-gnueabi
    
  3. Raspberry Pi 3B (32bit) 用の.configファイルを生成する。

      user@server:~$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- USE_PRIVATE_LIBGCC=yes rpi_3_32b_defconfig
    
  4. U-Bootをビルドする。

      user@server:~$ make
    
  5. 生成したバイナリ(u-boot.bin)をSDカードにコピーする。

      user@server:~$ mount /dev/sdb1 /mnt/tmp
      user@server:~$ cp u-boot.bin /mnt/tmp/
      user@server:~$ umount /mnt/tmp
    

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

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

ブートイメージの作成以外は、前回の記事で構築した環境をそのまま利用する。

ブートイメージの作成

  1. メインラインカーネルをビルドするのに必要なパッケージをインストールする。

      user@server:~$ sudo apt install git bc bison flex libssl-dev make
    
  2. mainlineのkernelのソースコードをクローンする。

      user@server:~$ git clone --depth=1 https://github.com/torvalds/linux.git
      user@server:~$ cd linux
    
  3. ARM用の.configファイルを生成する。

      user@server:~$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- defconfig
    
  4. カーネルをビルドする。

      user@server:~$ make -j$(nproc) ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- zImage modules dtbs
    
  5. 生成したカーネルイメージとDevice Tree blobsをtftpサーバの公開ディレクトリにコピーする。

      user@server:~$ sudo cp arch/arm/boot/dts/*.dtb /srv/tftpboot/
      user@server:~$ sudo cp arch/arm/boot/zImage /srv/tftpboot/
    

実行結果

  1. minicom経由でアクセスする。

     U-Boot 2020.04-00687-gd16d37bcd4 (Apr 29 2020 - 09:27:20 +0000)
    
     DRAM:  948 MiB
     RPI 3 Model B (0xa32082)
     MMC:   mmc@7e202000: 0, sdhci@7e300000: 1
     Loading Environment from FAT... *** Warning - bad CRC, using default environment
    
     In:    serial
     Out:   vidconsole
     Err:   vidconsole
     Net:   No ethernet found.
     starting USB...
     Bus usb@7e980000: scanning bus usb@7e980000 for devices... 3 USB Device(s) found
            scanning usb for storage devices... 0 Storage Device(s) found
    
  2. Device Tree blobsをメモリ上に展開する。

     U-Boot> dhcp ${fdt_addr_r} ${fdtfile}
     Waiting for Ethernet connection... done.
     BOOTP broadcast 1
     DHCP client bound to address 172.16.1.2 (6 ms)
     Using smsc95xx_eth device
     TFTP from server 172.16.1.1 our IP address is 172.16.1.2
     Filename 'bcm2837-rpi-3-b.dtb'.
     Load address: 0x2600000
     Loading: ##################################################  13.8 KiB
              1 MiB/s
     done
    
  3. カーネルイメージをメモリ上に展開する。

     U-Boot> tftp ${kernel_addr_r} zImage
     Waiting for Ethernet connection... done.
     Using smsc95xx_eth device
     TFTP from server 172.16.1.1; our IP address is 172.16.1.2
     Filename 'zImage'.
     Load address: 0x80000
     Loading: ##################################################  9.3 MiB
              2.7 MiB/s
     done
    
  4. カーネルパラメータを設定する。

     U-Boot> setenv bootargs root=/dev/nfs nfsroot=${serverip}:/srv/rootfs,vers=3,proto=tcp rw rootwait ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}::eth0:off nfsrootdebug
    
  5. 指定番地にあるカーネルを起動させる。

     U-Boot> bootz ${kernel_addr_r} - ${fdt_addr_r}                                                                                                                            
     Kernel image @ 0x080000 [ 0x000000 - 0x953200 ]
     ## Flattened Device Tree blob at 02600000
        Booting using the fdt blob at 0x2600000
        Using Device Tree in place at 02600000, end 02606725
    
     Starting kernel ...
    
     <<< snip >>>
    
     Raspbian GNU/Linux 10 raspberrypi ttyS0
    
     raspberrypi login: 
    

おわりに

本記事では、U-Bootを利用してRaspberry Pi 3Bをネットワークブートする方法を紹介した。

U-Bootを利用したことで、Raspberry Pi 3Bで報告されている不具合を回避しながらも、SDカードの書き込み寿命の浪費を防ぐことができる。
U-Bootにはあらかじめスクリプトを書いておき、起動時に自動で実行する機構もあるので、そちらを利用するとより一層便利になる。

変更履歴

  • 2020/04/30: 記事公開
  • 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 のブートで必要なファイル郡

U-Bootに関する説明資料

*1:Raspbianにあるカーネルはそのままだと起動できなかった

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+ではバグが修正されているので、そちらで実施している人が多い

組込みLinuxディストリビューションを構築する(Yocto編)

関連記事

概要

Yocto (Zeus)でARM64用のLinuxディストリビューション一式 (Linuxカーネル、U-Boot、ルートファイルシステム、ツールチェイン) を生成する。
また、QEMU (virtボード)でU-Bootをロードし、そこからLinxuカーネルの起動とルートファイルシステムのマウントまでの手順を確認した。

はじめに

前回の記事で、QEMUx86_64アーキテクチャ上でARM用にビルドしたLinuxカーネルを起動させることに成功した。

leavatail.hatenablog.com

その時はLinuxシステムの構築にBuildRootを使用したが、今回はYoctoを使用してLinuxシステムを構築する。

Yoctoについて

Yocto Projectは、開発者がLinuxディストリビューションを構築する仕組みを提供するプロジェクトである。 Yoctoの特徴としては、以下のようなものがあげられる。

Yoctoは複数のコンポーネントから構成されている。下図はコンポーネントについて簡略に表したものである。

Yoctoの全体像イメージ

  • poky : リファレンス・ビルド・システム
    独自のLinuxディストリビューションを構築するためのシステム。 (厳密には違うが) pokyはYocto専用のLinuxディストリビューションであり、ここでカーネルイメージやrootfsを生成する。 記事によっては、yocto=pokyのことを指していることもある。

  • bitbake: ビルドツール
    Yocto(poky)上で利用される組込みLinuxシステム向けのビルドツール。 Makeのようなもので、依存関係を解決したうえでイメージ(カーネルイメージやrootfsなど)を生成する。
    具体的にbitbakeでは、「レシピの解析」「ソースコードのダウンロード」「パッチ適用」「ビルド」を一括で実施してくれる。

  • レシピ: ソフトウェアのビルド定義
    ソフトウェアのビルドやインストール方法が書かれたファイル。

  • レイヤー: メタ情報の集合
    レシピなどを含めた情報を集めた層。レイヤーは重ねることで機能を追加することができ、アーキテクチャによる依存を最小限にすることができる。
    レイヤーやレシピの詳細な説明については、参考資料を参照。

  • 構成情報: ターゲットマシンの構成定義
    CPUアーキテクチャなどのターゲットマシンごとの定義。

環境構成

前回の記事で作成した環境を利用する。

実行環境

仮想マシンの構築にはVagrantVirtualBox、BoxイメージにはUbuntu /bionic64を利用する。

今回使用するホスト環境は下記の通り。

テスト環境 詳細
CPU intel core i7-9700
メモリ DDR4-2666 32G
ストレージ M.2 SSD(500GB)
Virtualbox v6.0.18

実行環境の準備

Yoctoの要件として空きディスク容量が50Gであることが求められる。 しかし、Vagrantのデフォルト仮想マシンのディスク容量は10Gであるので、Vagrantの定義ファイルを修正する必要がある。

今回は、vagrant-disksizeを使用して、ディスク容量を拡張する。 またyoctoではイメージ生成時間が長くなる傾向があるので、CPU数とメモリ容量も拡張しておく。 Vagrantfileの修正箇所は下記の通りである。

$ git diff
diff --git a/Vagrantfile b/Vagrantfile
index 1c4ee1e..bb5337f 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -45,17 +45,20 @@ Vagrant.configure("2") do |config|
   # argument is a set of non-required options.
   # config.vm.synced_folder "../data", "/vagrant_data"
 
+  config.ssh.forward_x11 = true
+
   # Provider-specific configuration so you can fine-tune various
   # backing providers for Vagrant. These expose provider-specific options.
   # Example for VirtualBox:
   #
-  # config.vm.provider "virtualbox" do |vb|
-  #   # Display the VirtualBox GUI when booting the machine
+  config.vm.provider "virtualbox" do |vb|
+    # Display the VirtualBox GUI when booting the machine
   #   vb.gui = true
-  #
-  #   # Customize the amount of memory on the VM:
-  #   vb.memory = "1024"
-  # end
+
+    # Customize the amount of memory on the VM:
+    vb.cpus = 8
+    vb.memory = "16384"
+  end
+
+  config.disksize.size = '200GB'
   #
   # View the documentation for the provider you are using for more
   # information on available options.

Yoctoを使用するにあたってX11転送は必須ではないが、デスクトップ環境イメージを利用する際に不便なので設定している。

システム構築のためのセットアップ

  1. 必要なパッケージをインストールする。

     vagrant@ubuntu-bionic:~$ sudo apt-get install gawk wget git-core diffstat unzip texinfo gcc-multilib \
                              build-essential chrpath socat cpio python python3 python3-pip python3-pexpect \
                              xz-utils debianutils iputils-ping python3-git python3-jinja2 libegl1-mesa libsdl1.2-dev \
                              pylint3 xterm
    
  2. Pokyリポジトリをクローンする。(執筆時点の最新バージョンyocto-3.0にチェックアウトする)

     vagrant@ubuntu-bionic:~$ git clone git://git.yoctoproject.org/poky
     vagrant@ubuntu-bionic:~$ cd poky/
     vagrant@ubuntu-bionic:~/poky$ git fetch --tag
     vagrant@ubuntu-bionic:~/poky$ git tag
     vagrant@ubuntu-bionic:~/poky$ git checkout tags/yocto-3.0 -b yocto-3.0
    
  3. ビルド環境の初期化する。

     vagrant@ubuntu-bionic:~/poky$ source oe-init-build-env
    
     You had no conf/local.conf file. This configuration file has therefore been
     created for you with some default values. You may wish to edit it to, for
     example, select a different MACHINE (target hardware). See conf/local.conf
     for more information as common configuration options are commented.
    
     You had no conf/bblayers.conf file. This configuration file has therefore been
     created for you with some default values. To add additional metadata layers
     into your configuration please add entries to conf/bblayers.conf.
    
     The Yocto Project has extensive documentation about OE including a reference
     manual which can be found at:
         http://yoctoproject.org/documentation
    
     For more information about OpenEmbedded see their website:
         http://www.openembedded.org/
    
    
     ### Shell environment set up for builds. ###
    
     You can now run 'bitbake <target>'
    
     Common targets are:
         core-image-minimal
         core-image-sato
         meta-toolchain
         meta-ide-support
    
     You can also run generated qemu images with a command like 'runqemu qemux86'
    
     Other commonly useful commands are:
      - 'devtool' and 'recipetool' handle common recipe tasks
      - 'bitbake-layers' handles common layer tasks
      - 'oe-pkgdata-util' handles common target package tasks
    
  4. 構成ファイルlocal.confを修正する。

diff --git a/build/conf/local.conf b/build/conf/local.conf
index 2e0bb41e64..1941917154 100644
--- a/build/conf/local.conf
+++ b/build/conf/local.conf
@@ -18,7 +18,7 @@
 # of emulated machines available which can boot and run in the QEMU emulator:
 #
 #MACHINE ?= "qemuarm"
-#MACHINE ?= "qemuarm64"
+MACHINE ?= "qemuarm64"
 #MACHINE ?= "qemumips"
 #MACHINE ?= "qemumips64"
 #MACHINE ?= "qemuppc"
@@ -265,3 +265,6 @@ PACKAGECONFIG_append_pn-qemu-system-native = " sdl"
 # track the version of this file when it was generated. This can safely be ignored if
 # this doesn't mean anything to you.
 CONF_VERSION = "1"
+
+BB_NUMBER_THREADS = '8'
+PARALLEL_MAKE = '-j 8'

修正内容は次の通りとなっている。

  • MACHINE ?=でターゲット対象アーキテクチャをarm64に変更する。
  • BB_NUMBER_THREADS:で並列処理するレシピ数を8に変更する。
  • PARALLEL_MAKE:コンパイル時に使用するコア数を8に変更する。

Yoctoによるビルド実行

今回は、最小限のシステムデスクトップ環境の2種類のLinuxディストリビューションを構築している。

最小システムのビルドと起動

  1. 最小限のシステムをビルドする。

     vagrant@ubuntu-bionic:~/poky/build$ bitbake core-image-minimal
    
  2. ビルドしたイメージは tmp/deploy/images に格納される。

     vagrant@ubuntu-bionic:~/poky/build$ ls -l tmp/deploy/images/qemuarm64/
     total 423572
     lrwxrwxrwx 2 vagrant vagrant        73 Jun 12 05:56 Image -> Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin
     -rw-r--r-- 2 vagrant vagrant  20445696 Jun 12 05:56 Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin
     lrwxrwxrwx 2 vagrant vagrant        73 Jun 12 05:56 Image-qemuarm64.bin -> Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin
     -rw-r--r-- 2 vagrant vagrant      1779 Jun 12 06:06 core-image-minimal-qemuarm64-20220612044554.qemuboot.conf
     -rw-r--r-- 2 vagrant vagrant  11512832 Jun 12 06:57 core-image-minimal-qemuarm64-20220612044554.rootfs.ext4
     -rw-r--r-- 2 vagrant vagrant       721 Jun 12 06:06 core-image-minimal-qemuarm64-20220612044554.rootfs.manifest
     -rw-r--r-- 2 vagrant vagrant   2753520 Jun 12 06:06 core-image-minimal-qemuarm64-20220612044554.rootfs.tar.bz2
     -rw-r--r-- 2 vagrant vagrant    242743 Jun 12 06:06 core-image-minimal-qemuarm64-20220612044554.testdata.json
     lrwxrwxrwx 2 vagrant vagrant        55 Jun 12 06:06 core-image-minimal-qemuarm64.ext4 -> core-image-minimal-qemuarm64-20220612044554.rootfs.ext4
     lrwxrwxrwx 2 vagrant vagrant        59 Jun 12 06:06 core-image-minimal-qemuarm64.manifest -> core-image-minimal-qemuarm64-20220612044554.rootfs.manifest
     lrwxrwxrwx 2 vagrant vagrant        57 Jun 12 06:06 core-image-minimal-qemuarm64.qemuboot.conf -> core-image-minimal-qemuarm64-20220612044554.qemuboot.conf
     lrwxrwxrwx 2 vagrant vagrant        58 Jun 12 06:06 core-image-minimal-qemuarm64.tar.bz2 -> core-image-minimal-qemuarm64-20220612044554.rootfs.tar.bz2
     lrwxrwxrwx 2 vagrant vagrant        57 Jun 12 06:06 core-image-minimal-qemuarm64.testdata.json -> core-image-minimal-qemuarm64-20220612044554.testd
     -rw-r--r-- 2 vagrant vagrant   2004411 Jun 12 05:56 modules--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.tgz
     lrwxrwxrwx 2 vagrant vagrant        75 Jun 12 05:56 modules-qemuarm64.tgz -> modules--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.tgz
    
  3. QEMUで生成したイメージをエミュレートする。(nographicを指定することで、現在のターミナルからシリアル接続することができる)

     vagrant@ubuntu-bionic:~/poky/build$ runqemu qemuarm64 nographic
     runqemu - INFO - Running MACHINE=qemuarm64 bitbake -e ...
     runqemu - INFO - Continuing with the following parameters:
     KERNEL: [/home/vagrant/poky/build/tmp/deploy/images/qemuarm64/Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin]
     MACHINE: [qemuarm64]
     FSTYPE: [ext4]
     ROOTFS: [/home/vagrant/poky/build/tmp/deploy/images/qemuarm64/core-image-sato-qemuarm64-20220612065747.rootfs.ext4]
     CONFFILE: [/home/vagrant/poky/build/tmp/deploy/images/qemuarm64/core-image-sato-qemuarm64-20220612065747.qemuboot.conf]
    
     runqemu - INFO - Setting up tap interface under sudo
     runqemu - INFO - Network configuration: ip=192.168.7.2::192.168.7.1:255.255.255.0::eth0:off:8.8.8.8
     runqemu - INFO - Running /home/vagrant/poky/build/tmp/work/x86_64-linux/qemu-helper-native/1.0-r1/recipe-sysroot-native/usr/bin/qemu-system-aarch64 -device virtio-net-device,netdev=net0,mac=52:54:00:12:34:02 -netdev         tap,id=net0,ifname=tap0,script=no,downscript=no -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0 -drive id=disk0,file=/home/vagrant/poky/build/tmp/deploy/images/qemuarm64/        core-image-sato-qemuarm64-20220612065747.rootfs.ext4,if=none,format=raw -device virtio-blk-device,drive=disk0 -device qemu-xhci -device usb-tablet -device usb-kbd  -machine virt -cpu cortex-a57 -smp 4 -m 512 -serial mon:stdio     -serial     null -nographic -device virtio-gpu-pci -kernel /home/vagrant/poky/build/tmp/deploy/images/qemuarm64/Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin -append 'root=/dev/vda rw  mem=512M     ip=192.168.7.2::192.168.7.1:255.255.255.0::eth0:off:8.8.8.8 console=ttyAMA0 console=hvc0  '
    

デスクトップ環境を構築する

  1. デスクトップ環境をビルドする

     vagrant@ubuntu-bionic:~/poky/build$ bitbake core-image-sato
    
  2. ビルドしたイメージは tmp/deploy/images に格納される。

     vagrant@ubuntu-bionic:~/poky/build$ ls -l tmp/deploy/images/qemuarm64/
     total 423580
     lrwxrwxrwx 2 vagrant vagrant        73 Jun 12 05:56 Image -> Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin
     -rw-r--r-- 2 vagrant vagrant  20445696 Jun 12 05:56 Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin
     lrwxrwxrwx 2 vagrant vagrant        73 Jun 12 05:56 Image-qemuarm64.bin -> Image--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.bin
     -rw-r--r-- 2 vagrant vagrant      1767 Jun 12 08:46 core-image-sato-qemuarm64-20220612065747.qemuboot.conf
     -rw-r--r-- 2 vagrant vagrant 522982400 Jun 25 09:26 core-image-sato-qemuarm64-20220612065747.rootfs.ext4
     -rw-r--r-- 2 vagrant vagrant     28808 Jun 12 08:46 core-image-sato-qemuarm64-20220612065747.rootfs.manifest
     -rw-r--r-- 2 vagrant vagrant  96465741 Jun 12 08:46 core-image-sato-qemuarm64-20220612065747.rootfs.tar.bz2
     -rw-r--r-- 2 vagrant vagrant    243264 Jun 12 08:46 core-image-sato-qemuarm64-20220612065747.testdata.json
     lrwxrwxrwx 2 vagrant vagrant        52 Jun 12 08:46 core-image-sato-qemuarm64.ext4 -> core-image-sato-qemuarm64-20220612065747.rootfs.ext4
     lrwxrwxrwx 2 vagrant vagrant        56 Jun 12 08:46 core-image-sato-qemuarm64.manifest -> core-image-sato-qemuarm64-20220612065747.rootfs.manifest
     lrwxrwxrwx 2 vagrant vagrant        54 Jun 12 08:46 core-image-sato-qemuarm64.qemuboot.conf -> core-image-sato-qemuarm64-20220612065747.qemuboot.conf
     lrwxrwxrwx 2 vagrant vagrant        55 Jun 12 08:46 core-image-sato-qemuarm64.tar.bz2 -> core-image-sato-qemuarm64-20220612065747.rootfs.tar.bz2
     lrwxrwxrwx 2 vagrant vagrant        54 Jun 12 08:46 core-image-sato-qemuarm64.testdata.json -> core-image-sato-qemuarm64-20220612065747.testdata.json
     -rw-r--r-- 2 vagrant vagrant   2004411 Jun 12 05:56 modules--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.tgz
     lrwxrwxrwx 2 vagrant vagrant        75 Jun 12 05:56 modules-qemuarm64.tgz -> modules--5.15.32+git0+63e25b5717_387a676543-r0-qemuarm64-20220612044554.tgz        
    
  3. QEMUで生成したイメージをエミュレートする。

     vagrant@ubuntu-bionic:~/poky/build$ runqemu qemuarm64
    

これにより、新規のウインドウが立ち上がる。

Yoctoの起動画面

デスクトップ環境ではのメインメニューから"Terminal"を選択することで、コンソールにログインできる。

アプリケーション選択画面

ツールチェインの生成

  1. ツールチェインをビルドする。

     vagrant@ubuntu-bionic:~/poky/build$ bitbake meta-toolchain
    
  2. ビルドしたツールチェインは tmp/deploy/sdk に格納される。

     vagrant@ubuntu-bionic:~/poky/build $ ls -l tmp/deploy/sdk/
     total 135232
     -rw-r--r-- 2 vagrant vagrant     11909 Jun 25 10:28 poky-glibc-x86_64-meta-toolchain-cortexa57-qemuarm64-toolchain-4.0.host.manifest
     -rwxr-xr-x 2 vagrant vagrant 138233871 Jun 25 10:30 poky-glibc-x86_64-meta-toolchain-cortexa57-qemuarm64-toolchain-4.0.sh
     -rw-r--r-- 2 vagrant vagrant      1730 Jun 25 10:28 poky-glibc-x86_64-meta-toolchain-cortexa57-qemuarm64-toolchain-4.0.target.manifest
     -rw-r--r-- 2 vagrant vagrant    225233 Jun 25 10:28 poky-glibc-x86_64-meta-toolchain-cortexa57-qemuarm64-toolchain-4.0.testdata.json
    
  3. ツールチェインをインストールする。

     vagrant@ubuntu-bionic:~/poky/build$ ./tmp/deploy/sdk/poky-glibc-x86_64-meta-toolchain-cortexa57-qemuarm64-toolchain-4.0.sh 
     Poky (Yocto Project Reference Distro) SDK installer version 4.0
     ===============================================================
     Enter target directory for SDK (default: /opt/poky/4.0): 
     You are about to install the SDK to "/opt/poky/4.0". Proceed [Y/n]? Y
     Extracting SDK...............................................done
     Setting it up...done
     SDK has been successfully set up and is ready to be used.
     Each time you wish to use the SDK in a new shell session, you need to source the environment setup script e.g.
      $ . /opt/poky/4.0/environment-setup-cortexa57-poky-linux        
    

ツールチェインの利用

Autotoolsを利用するプロジェクトを生成したツールチェインでビルドする。

github.com

  1. 環境をセットアップする (現在のプロセス shell 単位に反映される)

     vagrant@ubuntu-bionic:/vagrant/Autotools-tutorial$  . /opt/poky/4.0/environment-setup-cortexa57-poky-linux 
    
  2. 設定された環境変数configureを実施する

     vagrant@ubuntu-bionic:/vagrant/Autotools-tutorial$ ./configure ${CONFIGURE_FLAGS}
    
  3. 設定された環境設定でプロジェクトのビルドを実施する

     vagrant@ubuntu-bionic:/vagrant/Autotools-tutorial$ make
    

おわりに

Linuxシステムを構築するツールYoctoを使用して、組込みLinuxディストリビューションを構築した。 YoctoはBuildRootなどと比較して、設定が複雑な印象があるが、純正性の高いディストリビューションを作成することができそうだ。またレイヤーによる概念により、再利用性が高くなっている。

変更履歴

  • 2020/02/23: 記事公開
  • 2022/06/08: 章構成を変更
  • 2022/06/25: ビルドによる成果物一覧を追加

参考

Yocto Projectの公式ガイド

Yoctoについて解説している記事

Yoctoのレシピ/レイヤーについて解説

BitBakeの解説

組込みLinuxディストリビューションを構築する(BuildRoot編)

関連記事

概要

BuildRootでARM64用のLinuxディストリビューション一式 (Linuxカーネル、U-Boot、ルートファイルシステム、ツールチェイン) を生成する。
また、QEMU (virtボード)でU-Bootをロードし、そこからLinxuカーネルの起動とルートファイルシステムのマウントまでの手順を確認した。

はじめに

前回の記事で、QEMUx86_64アーキテクチャ上でARM64用にビルドしたLinuxカーネルを起動させることに成功した。

leavatail.hatenablog.com

しかし、前回作成した環境はLinuxカーネルを起動してシェルから最低限の操作ができるものだった。

ここから、Linuxシステムとして利用できるようにするためには、uClibcなどを利用してツールチェーンを生成したり、必要に応じてブートローダをビルドする必要がある。

上記の手順を一から手動で構築するには手間がかかり、依存関係の乱れによるケアレスミスなど発生する恐れがある。

BuildRootは、上記のような問題を解決することができるLinuxシステムを構築するツールである。   BuildRootを使用することで、下記のものを構築することができる。

今回はBuildRootを使用して、組込みLinuxディストリビューションを構築する。

環境構成

前回の記事で作成した環境を利用する。

実行環境

仮想マシンの構築にはVagrantVirtualBox、BoxイメージにはUbuntu /bionic64を利用する。

TargetBoardにはARM仮想ボードの「virt-2.11」を使用する。 このTargetBoardに、ルートファイルシステムLinuxカールの入ったディスクを接続し、フラッシュメモリブートローダをロードする。

今回使用するTargetBoardの抽象図を下記に示す。

QEMUでエミュレートするボードの抽象図

依存パッケージ

公式サイト(The Buildroot user manual)より、下記のコマンドを必要とする。

  • sed
  • make (version 3.81 or any later)
  • binutils
  • build-essential (only for Debian based systems)
  • gcc (version 4.8 or any later)
  • g++ (version 4.8 or any later)
  • bash
  • patch
  • gzip
  • bzip2
  • perl (version 5.8.7 or any later)
  • tar
  • cpio
  • unzip
  • rsync
  • file (must be in /usr/bin/file)
  • bc
  • wget

システム構築の手順

  1. BuildRootに必要としているパッケージをインストールする。(そのほかのコマンドは、Vagrantのイメージでもインストール済み)

     vagrant@ubuntu-bionic:~$ libncurses-dev unzip
    
  2. BuildRootを入手する。(執筆時点の安定版である2019.11.1を使用する)

     vagrant@ubuntu-bionic:~$ git clone git://git.buildroot.net/buildroot
     vagrant@ubuntu-bionic:~/buildroot$ cd buildroot
     vagrant@ubuntu-bionic:~/buildroot$ git checkout -b 2019.11.1 2019.11.1
    
  3. QEMU用のARM64アーキテクチャqemu_aarch64_virt_defconfigのconfigファイルを生成する。1

     vagrant@ubuntu-bionic:~/buildroot$ make qemu_aarch64_virt_defconfig
    
  4. ビルドの設定を変更する。

     vagrant@ubuntu-bionic:~/buildroot$ make menuconfig
    

このとき、ブートローダU-Bootのビルドを有効化するために次の設定をする。

  • Bootloadersを選択する。
  • U-Bootを選択、Board defconfigに(qemu_arm64)を入力する。

その後、生成したconfigをもとにビルドする。(一般権限で実行すること)

vagrant@ubuntu-bionic:~/buildroot$ make

Linuxの起動

  1. カーネルを格納するイメージ2を生成する。

     vagrant@ubuntu-bionic:~$ dd if=/dev/zero of=boot.img bs=512 count=65536
     vagrant@ubuntu-bionic:~$ mkfs.vfat boot.img 
     mkfs.fat 4.1 (2017-01-24)
    
  2. 作成したイメージにカーネルを格納する。

     vagrant@ubuntu-bionic:~$ sudo mount -o loop boot.img /mnt/
     vagrant@ubuntu-bionic:~$ sudo cp buildroot/output/images/Image /mnt/
     vagrant@ubuntu-bionic:~/buildroot$ sudo umount /mnt 
    
  3. 作成したカーネルを格納したイメージとルートファイルシステムイメージを接続、ファームウェアにU-Bootのイメージをロードし、QEMUを起動させる。

     vagrant@ubuntu-bionic:~$ qemu-system-aarch64 \
       -M virt \
       -cpu cortex-a53 \
       -bios ~/buildroot/output/images/u-boot.bin \
       -drive file=~/boot.img,if=none,format=raw,id=hd0 \
       -device virtio-blk-device,drive=hd0 \
       -drive file=~/buildroot/output/images/rootfs.ext4,if=none,format=raw,id=hd1 \
       -device virtio-blk-device,drive=hd1 \
       -nographic
    
  4. U-Bootのデフォルト設定では今回の環境を動作させることができないので、設定したうえでカーネルを起動させる。

     => load virtio 1 ${kernel_addr_r} /Image
     => setenv bootargs root=/dev/vda rdinit=/bin/sh
     => booti ${kernel_addr_r} - ${fdt_addr}
    

これらの設定は、それぞれ次のようなことをしている。

Linuxの起動画面

BuildRootではユーザ名root、パスワードなしでログインすることができる。

ツールチェインの取得

生成したツールチェインはoutput/host/以下に格納されている。

vagrant@ubuntu-bionic:~$ ls -l buildroot/output/host/
total 48
drwxr-xr-x  6 vagrant vagrant  4096 Feb  4 15:33 aarch64-buildroot-linux-uclibc
drwxr-xr-x  2 vagrant vagrant 12288 Feb  9 16:03 bin
drwxr-xr-x  4 vagrant vagrant  4096 Feb  9 15:52 etc
drwxr-xr-x 23 vagrant vagrant  4096 Feb  9 15:52 include
drwxr-xr-x  8 vagrant vagrant  4096 Feb  9 15:52 lib
lrwxrwxrwx  1 vagrant vagrant     3 Feb  4 15:10 lib64 -> lib
drwxr-xr-x  3 vagrant vagrant  4096 Feb  4 15:44 libexec
drwxr-xr-x  3 vagrant vagrant  4096 Feb  9 15:49 man
drwxr-xr-x  2 vagrant vagrant  4096 Feb  9 15:51 sbin
drwxr-xr-x 21 vagrant vagrant  4096 Feb  4 15:44 share
lrwxrwxrwx  1 vagrant vagrant     1 Feb  4 15:10 usr -> .
drwxr-xr-x  3 vagrant vagrant  4096 Feb  4 15:44 var

おわりに

Linuxシステムを構築するツールBuildRootを使用して、組込みLinuxディストリビューションを構築した。 BuildRootでは、BusyBoxやuClibcなどを組み込んで構築してくれるので、依存関係の意識せずとも構築することができる。 しかし、BuildRootに頼りすぎてしまうと内部構造がブラックボックス化してしまうのであまりよくないと考えられる。 legal-infomakeターゲットを指定することでライセンスの一覧を取得することはできるが、これだけでは不十分だろう。

次回は、そのほかのLinux構築ツールyoctoなどを使用して同様の環境を構築してみたいと思う。

変更履歴

  • 2020/2/11: 記事公開
  • 2020/12/18: 投稿画像のアップデート
  • 2022/06/07: デザイン修正

参考


  1. 指定可能なターゲットはlist-defconfigsで確認することができる。

  2. 現状はカーネルのみ格納できれば良いので32Mで作成する。

QEMUでARM64用Linuxカーネルを起動する

関連記事

概要

x86_64の開発PCでARM64用にLinuxカーネルをビルド、busyboxで簡易initramfsを用意する。
また、QEMU (virtボード)でこれらのバイナリを動かした。

はじめに

Linuxカーネルは様々なアーキテクチャに対応している。
その中でもARMアーキテクチャでは、スマートフォンやゲーム機器などモバイル機器に広くから採用されている。

しかし、アーキテクチャ毎に命令セットが異なるため、ARM用にビルドされたバイナリを別のアーキテクチャで実行することはできない。

そこで、プロセッサエミュレータでもあるQEMUを用いてARM環境を構築し、ARM用にビルドされたLinuxカーネルを動かす方法を解説する。
また本記事では、以下の動作をする環境を目指す。

カーネル起動ワークフローとメモリマップイメージ図

環境構成

ホスト環境x86_64アーキテクチャに構築する。

実行環境

本記事は、下記の環境とソフトウェアバージョンに基づいて説明する。

環境 パラメータ
ホスト環境 x86_64
ホストOS Ubuntu 20.04
QEMU QEMU emulator version 4.2.1
ターゲットボード virt
linux 5.4.83
Busybox 1_32_stable
Docker version 19.03.13
Docker image ubuntu:20.04

ロスコンパイル環境の構築

  1. Docker imageからコンテナを作成する

     leava@ubuntu-bionic:~$ docker run --rm --name=kbuild -h "kbuild" -v /srv:/work -it ubuntu:20.04 /bin/bash 
    
  2. ARM64用のgccコンパイラをインストールする

     root@kbuild:/# apt update
     root@kbuild:/# apt install gcc-aarch64-linux-gnu 
    
  3. コンテナ内部でLinuxカーネルをビルドするために必要なパッケージをインストールする

    root@kbuild:/# apt install build-essential bc bison flex libncurses-dev libelf-dev libssl-dev git wget
    
  4. LinuxカーネルをARM64用にセットアップする

     root@kbuild:/# wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.83.tar.xz 
     root@kbuild:/# tar xf linux-5.4.83.tar.xz -C /work/
     root@kbuild:/# cd work/linux-5.4.83
     root@kbuild:/work/linux-5.4.83# export ARCH="arm64"
     root@kbuild:/work/linux-5.4.83# export CROSS_COMPILE="aarch64-linux-gnu-"
    
  5. カーネルをビルドする。

     root@kbuild:/work/linux-5.4.83# make defconfig
     root@kbuild:/work/linux-5.4.83# make -j `getconf _NPROCESSORS_ONLN` Image dtbs modules
    

上記のコマンドによって、/srvディレクトリに Kernel ImageとDevice Tree Bolb(DTB)、Loadable Moduleが生成される。

QEMUカーネルを起動させる

  1. ARM64用QEMUをインストールする

     leava@ubuntu-bionic:~$ sudo apt install qemu-system-aarch64
    
  2. 作成したカーネルQEMUで実行する

     leava@ubuntu-bionic:~$ qemu-system-aarch64 \
         -M virt \
         -cpu cortex-a53 \
         -kernel /srv/linux-5.4.83/arch/arm64/boot/Image \
         -nographic \
         -append "console=ttyAMA0"
    

利用したオプションは下記の通り。
また、デバイスツリーを指定していないにも関わらず起動できているのは、QEMUのvirtがデバイスツリーを自動生成しているからである。

オプション 概要
-M 利用するターゲットボードを指定する。今回は汎用ボードのvirtを使用する。
-cpu CPUプロセッサを指定する。今回はARM64系のcortex-a53を使用する
-kernel カーネルイメージを指定する。先ほどビルドしたイメージを使用する。
-nographic GUIを立ち上げない
-append カーネルパラメータ。 ARMアーキテクチャのシリアルポートデバイスttyAMA0を使用する。

上記のコマンドを実行すると、kernel Panicしてしまう。

    [    0.440663] VFS: Cannot open root device "(null)" or unknown-block(0,0): error -6
    [    0.440874] Please append a correct "root=" boot option; here are the available partitions:
    [    0.441240] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
    [    0.441534] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.4.83 #1
    [    0.441648] Hardware name: linux,dummy-virt (DT)
    [    0.441849] Call trace:
    [    0.441927]  dump_backtrace+0x0/0x140
    [    0.442063]  show_stack+0x14/0x20
    [    0.442143]  dump_stack+0xb4/0x114
    [    0.442212]  panic+0x158/0x324
    [    0.442275]  mount_block_root+0x1d0/0x284
    [    0.442349]  mount_root+0x124/0x158
    [    0.442421]  prepare_namespace+0x12c/0x18c
    [    0.442489]  kernel_init_freeable+0x210/0x23c
    [    0.442553]  kernel_init+0x10/0x100
    [    0.442619]  ret_from_fork+0x10/0x1c
    [    0.442977] Kernel Offset: disabled
    [    0.443166] CPU features: 0x0002,24002004
    [    0.443266] Memory Limit: none
    [    0.443628] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---

initramfsを用意する

上記のメッセージを確認すると、「rootファイルシステムがない」からPanicしたことがわかるので、C-a xQEMUを終了する。

そこで、BusyBoxを利用して最低限起動できるrootファイルシステムを作成する。
BusyBoxは、UNIX系ユーティリティツール単一の実行ファイルにまとめたパッケージで、組込みLinuxでも利用されている。

  1. BusyBoxを取得する。

     root@kbuild:/# cd work
     root@kbuild:/work#  git clone git://git.busybox.net/busybox
     root@kbuild:/work# cd busybox
     root@kbuild:/work/busybox# git checkout remotes/origin/1_32_stable
    
  2. ビルド用の設定を修正する。最低限の動作を目指しているので、デフォルトからCONFIG_STATICを有効にするのみでよい。(CONFIG_STATICSetting->Build static binary (no shared libs)を有効にすることで設定される)

     root@kbuild:/work/busybox# make defconfig
     root@kbuild:/work/busybox# make menuconfig
    
  3. BusyBoxをビルドし、イメージを_installに生成する。

     root@kbuild:/work/busybox# make
     root@kbuild:/work/busybox# make install
    
  4. initramfsとして最低限必要なファイルやディレクトリを作成する。

     root@kbuild:/work/busybox# cd _install
     root@kbuild:/work/busybox/_install# mkdir proc
     root@kbuild:/work/busybox/_install# mkdir sys
     root@kbuild:/work/busybox/_install# mkdir dev
     root@kbuild:/work/busybox/_install# sudo mknod dev/null c 1 3
     root@kbuild:/work/busybox/_install# cat <<EOF > init
     #!/bin/sh
     mount -t proc none /proc
     mount -t sysfs none /sys
     /sbin/mdev -s
     exec  /bin/sh
     EOF
     root@kbuild:/work/busybox/_install# chmod +x init
    
  5. initramfsを生成する。

     root@kbuild:/work/busybox/_install# find . | cpio -o --format=newc > ../rootfs.img
    
  6. 作成したイメージを利用して、再度QEMUを実行する。

     leava@ubuntu-bionic:~$ qemu-system-aarch64 \
         -M virt \
         -cpu cortex-a53 \
         -kernel /srv/linux-5.4.83/arch/arm64/boot/Image \
         -initrd /srv/busybox/rootfs.img \
         -nographic \
         -append "console=ttyAMA0"
    
オプション 概要
-initrd 初期 RAM ディスク。BusyBoxで作成したイメージを使用する。

上記のコマンドを実行すると、シェルが立ち上がる。

    [    0.425197] sdhci: Copyright(c) Pierre Ossman
    [    0.425800] Synopsys Designware Multimedia Card Interface Driver
    [    0.427183] sdhci-pltfm: SDHCI platform and OF driver helper
    [    0.429410] ledtrig-cpu: registered to indicate activity on CPUs
    [    0.431969] usbcore: registered new interface driver usbhid
    [    0.432146] usbhid: USB HID core driver
    [    0.440648] NET: Registered protocol family 17
    [    0.441712] 9pnet: Installing 9P2000 support
    [    0.442051] Key type dns_resolver registered
    [    0.443071] registered taskstats version 1
    [    0.443167] Loading compiled-in X.509 certificates
    [    0.449869] input: gpio-keys as /devices/platform/gpio-keys/input/input0
    [    0.451992] rtc-pl031 9010000.pl031: setting system clock to 2020-12-13T15:31:16 UTC (1607873476)
    [    0.455420] ALSA device list:
    [    0.455514]   No soundcards found.
    [    0.457913] uart-pl011 9000000.pl011: no DMA platform data
    [    0.648255] Freeing unused kernel memory: 4992K
    [    0.648750] Run /init as init process
    /bin/sh: can't access tty; job control turned off
    / # 

おわりに

実際にARMアーキテクチャを所持していなくても、QEMUでARM用Linuxカーネルを起動させることができた。今回の記事では紹介できなかったが、gdbデバッグが容易にできたりとデバッグ時にも大変有効であるので積極的に利用していきたい。

今回はBusyBoxのデフォルト設定でrootファイルシステムも生成したが、こちらは今後の課題となってくる。

変更履歴

  • 2020/01/26: 記事公開
  • 2020/12/11: ブログタイトルを "ARM64用" に訂正
  • 2020/12/14: 実行環境をUbuntu18.04からUbuntu20.04に更新
  • 2022/06/07: デザイン修正

参考

Raspberry Pi 3 Model Bとシリアル通信で接続する

概要

FTDI USBシリアル変換ケーブルとRaspberry Pi 3 Model Bを接続し、screenコマンドでコンソールが表示されることが確認できた。

はじめに

Raspberry Piを使って何か開発をしようとしたときに、ネットワーク経由でしかリモート接続ができない状態は危険である。 例えば、ネットワークの設定を誤ってRaspberry Piにリモート接続できなくなることが考えられる。

そこで、Raspberry Piとシリアル通信することでネットワークが使用できなくなった場合にも接続できるようにする。 Raspberry Pi 3 でシリアル通信するには、USB経由で通信する方法とGPIO経由で通信する方法がある。 ここでは、GPIO経由でシリアル通信する方法を記述する。

Raspberry Pi 3のシリアル通信の記事はこの記事は下記のリンクを参考にさせてもらった。

karaage.hatenadiary.jp

scrapbox.io

必要なもの

接続方法

1. Raspberry Pi側でシリアル通信の接続を有効にする

pi@raspberrypi:~ $ sudo raspi-config

"5 Interfacing Option"を選択する。

"P6 Serial"を選択する。

シリアルでシェルにログインするか聞かれるので"はい"を選択する。


2. FTDI USBシリアル変換ケーブルを接続する。

FTDI USBシリアル変換ケーブルの黒/橙/黄にブレッドボード・ジャンパーワイヤを接続する。

Raspberry Piとケーブルの配線

ブレッドボード・ジャンパーワイヤの黒をRaspberry PiのGround、橙をGPIO15 (RXD)、黄をGPIO14 (TXD) に接続する。

FTDI USBシリアル変換ケーブルの順番(黒/橙/黄)と、Raspberry Piへの接続先の順番 (黒/黄/橙)が異なるので注意。


3. 変換ケーブルのUSB側を操作端末 (今回はUbuntu 18.04を使用する) に接続する。

正しく認識されている場合、デバイスファイルが生成される。

$ ls -l /dev/ttyUSB*
crw-rw---- 1 root dialout 188, 0 12月 30 14:38 /dev/ttyUSB0

このデバイスに対して、シリアルコンソールに対応したターミナルエミュレータ (screen, minicom, etc.) を起動する。

 $ sudo screen /dev/ttyUSB0 115200

Raspbian GNU/Linux 9 raspberrypi ttyS0
raspberrypi login: 

おわりに

FTDI USBシリアル変換ケーブルを用いて、Raspberry Pi 3とGPIO経由でシリアル通信することができた。

ネットワークが切断されてもRaspberry Piと接続できるので、恐れずに開発できるようになった。

変更履歴

  • 2019/12/30: 記事公開

参考

*1:1袋に10本入っているので一つで十分だが、異なる3色のケーブルを買うことをお勧めする