関連記事
- 概要
- はじめに
- initramfs とは
- 環境構成
- ARM用のLinuxを構築する
- ARM用のinitramfsを構築する
- U-Bootからカーネルをロードする
- 作成ファイル置き場
- おわりに
- FAQ
- 変更履歴
- 参考
概要
x86_64の開発PCで、BuildRootによりARM用にLinuxカーネルとU-Bootをビルド、busybnoxで簡易initramfsを用意する。
また、QEMU (vexpress-a9ボード)でこれらのバイナリを動かし、BuildRootで作成したルートファイルシステムがマウントされることを確認した。
はじめに
ARMアーキテクチャは、<CPUアーキテクチャの一つである。
私たちの身近なPCはx86_64アーキテクチャであることが多いが、組み込み機器はARMアーキテクチャであることが多い。
また2020年11月に、AppleがARMアーキテクチャを採用したMacを発表したことによって、ARMアーキテクチャはより一層注目されている。
一方で、アーキテクチャ毎に命令セットが異なるため、ARM用にビルドされたバイナリを別のアーキテクチャで実行することはできない。
そこで、プロセッサエミュレータでもあるQEMUを用いてARM環境を構築し、ARM用にビルドされたLinuxカーネルを動かす方法を解説する。
また本記事では、以下の動作をする環境を目指す。
- QEMUからU-Bootが起動できる。
- U-BootからARM用Linuxカーネルが起動できる
- カーネルの起動時、initramfsをマウントする
- initramfsのマウント後、SDカード内のルートファイルシステムをマウントする
initramfs とは
initramfsでは、kernel起動直後にRAM上に展開されるファイルシステムとなっている。
カーネルドキュメントに詳細な説明が記載されているので、興味のある方は一読することを推奨する。
https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt
initramfsを利用する用途として、ルートファイルシステムをマウントする前に必要なドライバのインストールや処理 (ファイルシステムの復号や改ざんチェックなど)があげられる。
同じような仕掛けとしてinitrdがある。それらの大きな違いはフォーマット形式である。 initramfsはアーカイブ形式(cpio + gzip)、initrdはファイルシステム形式(gzip)となっている。
そして、initramfsはinitスクリプト内で下記のようなルートファイルシステムを変更する処理が必要となる。
一般的には、switch_root
コマンドで実現している。
環境構成
本記事は、下記の環境とソフトウェアバージョンに基づいて説明する。
環境 | パラメータ |
---|---|
ホスト環境 | x86_64 |
ホストOS | Ubuntu 20.04 (python3 とarm-linux-gnueabi- をインストール済み) |
Buildroot | buildroot-2020.02.8 |
QEMU | QEMU emulator version 5.0.0 |
ターゲットボード | vexpress-a9 |
linux | 5.4.58 |
U-Boot | 2020.07 |
Busybox | 1_32_stable |
ARM用のLinuxを構築する
Buildrootを用いてLinux(kernel, rootfs, qemu, device tree)を構築する。
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
arm専用のデフォルトコンフィグ
qemu_arm_vexpress_defconfig
(ちなみに、vexpress
はVersatile Express
の略で汎用的なarmの評価ボードを意味する) を利用する。leava@ubuntu-bionic:~/buildroot-2020.02.8$ make qemu_arm_vexpress_defconfig
Buildrootのビルド対象にU-Bootを追加する。
leava@ubuntu-bionic:~/buildroot-2020.02.8$ make menuconfig Bootloaders ---> [*] U-Boot (vexpress_ca9x4) Board defconfig
Buildrootのビルド (
root
ユーザでビルドしてしまうと失敗してしまうので注意)leava@ubuntu-bionic:~/buildroot-2020.02.8$ make
-
leava@ubuntu-bionic:~/buildroot-2020.02.8$ ./output/images/start-qemu.sh serial-only ... buildroot login: # rootでログイン可能 #
ARM用のinitramfsを構築する
BusyBoxを用いて簡易initramfsを構築する。
Buildrootをインターネットからダウンロード、stable versionにチェックアウトする。(投稿時は
1_32_stable
が最新版)leava@ubuntu-bionic:~$ git clone git://busybox.net/busybox.git leava@ubuntu-bionic:~$ cd busybox leava@ubuntu-bionic:~/busybox$ git checkout remotes/origin/1_32_stable
arm専用のデフォルトコンフィグを利用する。
leava@ubuntu-bionic:~/busybox$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- defconfig
initramfs用に最小限の設定を変更する。(initramfsの場合、スタティックリンクであるほうが好ましい)
leava@ubuntu-bionic:~/busybox$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig Settings ---> [*] Build static binary (no shared libs)
BusyBoxのビルド (
make install
を実行してもホスト環境に何かしらパッケージを変更するわけではなく、_install
ディレクトリにディレクトリツリーが構築される)leava@ubuntu-bionic:~/busybox$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- leava@ubuntu-bionic:~/busybox$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- install
initramfs用のディレクトリに移動する
leava@ubuntu-bionic:~/busybox$ cd _install
initramfsとして必要なディレクトリを作成する
leava@ubuntu-bionic:~/busybox/_install$ mkdir dev leava@ubuntu-bionic:~/busybox/_install$ mkdir proc leava@ubuntu-bionic:~/busybox/_install$ mkdir sys leava@ubuntu-bionic:~/busybox/_install$ mkdir -p mnt/newroot
initramfsからルートファイルシステムをマウントするように
init
スクリプトを修正する。leava@ubuntu-bionic:~/busybox/_install$ cat <<EOF > init #!/bin/busybox sh echo "Mounting Proc and Sysfs" # Mount the /proc and /sys filesystems. mount -t devtmpfs devtempfs /dev mount -t proc none /proc mount -t sysfs none /sys # Mount the root filesystem mount -t ext4 /dev/mmcblk0 /mnt/newroot # Switch mount point mount -n -o move /sys /mnt/newroot/sys mount -n -o move /proc /mnt/newroot/proc mount -n -o move /dev /mnt/newroot/dev # Execute new mount rootfilesystem exec switch_root -c /dev/console /mnt/newroot /sbin/init EOF
作成したディレクトリツリーを用いて、initramfsを構築する
leava@ubuntu-bionic:~/busybox/_install$ find . | cpio -o --format=newc > ../rootfs.img;
initramfsを使用するようにQEMUの起動スクリプトを修正する
leava@ubuntu-bionic:~/busybox/_install$ cd ~/buildroot-2020.02.8 leava@ubuntu-bionic:~/buildroot-2020.02.8$ sed -e '$d' ./output/images/start-qemu.sh > ./output/images/start-qemu2.sh leava@ubuntu-bionic:~/buildroot-2020.02.8$ echo "exec qemu-system-arm -M vexpress-a9 -smp 1 -m 256 -kernel \${IMAGE_DIR}/zImage -dtb \${IMAGE_DIR}/vexpress-v2p-ca9.dtb -initrd ../rootfs.img -drive file=\${IMAGE_DIR}/rootfs.ext2,if=sd,format=raw -append \"console=ttyAMA0,115200 rootwait\" -net nic,model=lan9118 -net user \${EXTRA_ARGS}" >> output/images/start-qemu2.sh
initramfsからルートファイルシステムを正常にマウントできるかどうか、QEMUで確認してみる。
leava@ubuntu-bionic:~/buildroot-2020.02.8$ ./output/images/start-qemu2.sh serial-only ... Mounting Proc and Sysfs # initramfsのinitスクリプトが実行されている ... buildroot login: # rootでログイン可能 #
U-Bootからカーネルをロードする
initramfsを使用するようにQEMUの起動スクリプトを修正する
leava@ubuntu-bionic:~/buildroot-2020.02.8$ sed -e '$d' ./output/images/start-qemu.sh > ./output/images/start-qemu3.sh leava@ubuntu-bionic:~/buildroot-2020.02.8$ echo "exec qemu-system-arm -M vexpress-a9 -smp 1 -m 256 -kernel \${IMAGE_DIR}/../build/uboot-2020.07/u-boot -device loader,file=\${IMAGE_DIR}/zImage,addr=0x62000000 -device loader,file=\${IMAGE_DIR}/vexpress-v2p-ca9.dtb,addr=0x63000000 -device loader,file=urootfs.img,addr=0x63008000 -drive file=\${IMAGE_DIR}/rootfs.ext2,if=sd,format=raw -net nic,model=lan9118 -net user \${EXTRA_ARGS}" >> output/images/start-qemu3.sh
U-Bootを起動させる。
このとき、カーネルを0x62000000
、device treeを0x63000000
、initramfsを0x630080000
にロードする。 (これは参考文献より、今回のボードvexpress-a9は0x6000000からSDRAMが実装されているためである)leava@ubuntu-bionic:~/buildroot-2020.02.8$ ./output/images/start-qemu3.sh serial-only U-Boot 2020.07 (Dec 12 2020 - 04:16:22 +0000) DRAM: 256 MiB WARNING: Caches not enabled Flash: 128 MiB MMC: MMC: 0 *** Warning - bad CRC, using default environment In: serial Out: serial Err: serial Net: smc911x-0 Hit any key to stop autoboot: 0 =>
device treeが展開されているアドレスなどを指定してカーネル起動させる。
=> bootz 0x62000000 0x63008000 0x63000000 Kernel image @ 0x62000000 [ 0x000000 - 0x46a478 ] ## Loading init Ramdisk from Legacy Image at 63008000 ... Image Name: Image Type: ARM Linux RAMDisk Image (gzip compressed) Data Size: 1123310 Bytes = 1.1 MiB Load Address: 00000000 Entry Point: 00000000 Verifying Checksum ... OK ## Flattened Device Tree blob at 63000000 Booting using the fdt blob at 0x63000000 Loading Ramdisk to 6fd65000, end 6fe773ee ... OK Loading Device Tree to 6fd5e000, end 6fd6473e ... OK Starting kernel ... ... buildroot login: # rootでログイン可能 #
作成ファイル置き場
#!/bin/busybox sh echo "Mounting Proc and Sysfs" # Mount the /proc and /sys filesystems. mount -t devtmpfs devtempfs /dev mount -t proc none /proc mount -t sysfs none /sys # Mount the root filesystem mount -t ext4 /dev/mmcblk0 /mnt/newroot # Switch mount point mount -n -o move /sys /mnt/newroot/sys mount -n -o move /proc /mnt/newroot/proc mount -n -o move /dev /mnt/newroot/dev # Execute new mount rootfilesystem exec switch_root -c /dev/console /mnt/newroot /sbin/init
#!/bin/sh IMAGE_DIR="${0%/*}/" if [ "${1}" = "serial-only" ]; then EXTRA_ARGS='-nographic' else EXTRA_ARGS='-serial stdio' fi export PATH="/srv/src/buildroot-2020.08/output/host/bin:${PATH}" exec qemu-system-arm -M vexpress-a9 -smp 1 -m 256 -kernel ${IMAGE_DIR}/zImage -dtb ${IMAGE_DIR}/vexpress-v2p-ca9.dtb -initrd ../rootfs.img -drive file=${IMAGE_DIR}/rootfs.ext2,if=sd,format=raw -append "console=ttyAMA0,115200 rootwait" -net nic,model=lan9118 -net user ${EXTRA_ARGS}
#!/bin/sh IMAGE_DIR="${0%/*}/" if [ "${1}" = "serial-only" ]; then EXTRA_ARGS='-nographic' else EXTRA_ARGS='-serial stdio' fi export PATH="/srv/src/buildroot-2020.08/output/host/bin:${PATH}" exec qemu-system-arm -M vexpress-a9 -smp 1 -m 256 -kernel ${IMAGE_DIR}/../build/uboot-2020.07/u-boot -device loader,file=${IMAGE_DIR}/zImage,addr=0x62000000 -device loader,file=${IMAGE_DIR}/vexpress-v2p-ca9.dtb,addr=0x63000000 -device loader,file=urootfs.img,addr=0x63008000 -drive file=${IMAGE_DIR}/rootfs.ext2,if=sd,format=raw -net nic,model=lan9118 -net user ${EXTRA_ARGS}
おわりに
本記事では、QEMUでARM用Linuxカーネルを起動させる手順を説明した。
Buildrootで構築した場合、自動でセットアップしてくれるため非常に使いやすく便利である。
また、今回はその恩恵にあやかっていないが、initramfsを使用した起動方法を紹介した。 initramfsを利用しないと実現できない要件もあるので、役割や作り方を一度おさらいしておくとよい。
FAQ
- initramfs がうまく起動しない。
- 横着して
find _install | cpio -o --format=newc > ../rootfs.img;
などとしていませんか?initramfs用のディレクトリ直下で実行しましょう。
- 横着して
- BusyBoxの
switch_root
コマンドで、ルートファイルシステムの変更に失敗する。/sbin/switch_root
でコマンドを実行するとPIDが変わってしまい起動に失敗します。exec
コマンドを使用しましょう。
- u-bootで、
bootz
コマンドを実行後にStarting kernel ...
で止まってしまう。- メモリマップを確認して、「ロードしたイメージと衝突していないか」と「ロードしたイメージ同士が衝突していないか」を確認しましょう。
変更履歴
- 2020/12/12: 記事公開
- 2021/2/25: initramfsの作成手順を修正
参考
- linux - switch_root busybox init problems? - Stack Overflow
- BusyBoxのswitch_rootコマンドを使うときの注意が記述されている
- [QEMU][boot] upstream U-boot on arm QEMU | archmemo
- QEMUでU-Bootを起動させる
- [QEMU][Linux] boot upstream Linux from U-boot on QEMU | archmemo
- LinuxカーネルをARM向けにビルドしてQEMUで起動する - hiroki.kanaの日常
- initramfsについて - Qiita
- initramfsやcpio形式について解説あり
- 仙石浩明の日記: initramfs (initrd) の init を busybox だけで書いてみた
- initramfsのinitスクリプトの記載あり