<RISC-V版> QEMU+OpenSBI(boot loader)でlinux kernelの起動

はじめに

他のサイトでもいろいろな構成でbuildされているが、今回は、OpenSBIというBoot Loaderを用いて、linux kernel(v5.3.6)を動かしてみた。 /sbin/init以下で動作させるためのスクリプトも用意する必要があるが、これはbusyboxというユーザーランドでの実行ファイル/コマンド群を生成してくれるツールを使う。各種distibutionに頼っていないので、構成としてはコンパクトだと思う。

Note) 自分で調べた限りだと、busybear-linuxbuildrootがあるが、これらはbusyboxをbuildの最中で構成しているし、結構いろいろなライブラリを突っ込んでいるので実験的にlinux kernelをbuildするのには少し仕組みが見えにくいかな、と思った。

reference

手順

準備

vagrant imageの "ubuntu/bionic64"(18.04)をベースに作成する:

# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/bionic64"
  config.vm.synced_folder "./", "/home/vagrant/shared"
  config.vm.provider "virtualbox" do |vb|
    vb.cpus = 2
    vb.memory = 4096
    vb.customize ["modifyvm", :id, "--ioapic", "on"]
  end
  config.disksize.size = '100GB'
end

今回は、15~20GBくらいのライブラリを展開することになるので、vagrant-disksizeというプラグインを用いている。導入手順は https://qiita.com/yut_h1979/items/c84c490053877beee5c1 を参考のこと。

注意) config.vm.synced_folderの上で(上の設定の場合、"/home/vagrant/shared"ディレクトリ以下で)ビルドを実行すると、busyboxのbuild時のハードリンク作成コマンド(ln -f)で失敗してしまう(おそらく、mount filesystem https://forums.virtualbox.org/viewtopic.php?f=3&t=91596 が原因)ため、避けたほうが良い。

vagrant up && vagrant sshとして、ゲストOSに入った後、以下のライブラリをinstallしておく:

sudo apt-get update
# for riscv-gnu-toolchain
sudo apt-get install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev
# for qemu
sudo apt-get install pkg-config libglib2.0-dev libpixman-1-dev
# for linux-kernel menuconfig
sudo apt-get install libncurses-dev
cd ~/ # move to working directory

riscv-gnu-toolchain

これは、gccやらGNURISC-V versionのツール群であり、RISC-Vで動く種々のコードをビルドするのに必要である。

最初に以下のようにRISCV, PATHという環境変数をセットしておく:

export RISCV=/opt/riscv # ~/.bashrc
export PATH="$RISCV/bin:$PATH" # ~/.profile
# https://github.com/riscv/riscv-gnu-toolchain (@ d5bea51083ec38172b84b7cd5ee99bfcb8d2e7b0)
git clone --recursive https://github.com/riscv/riscv-gnu-toolchain
cd riscv-gnu-toolchain
./configure --prefix=$RISCV
## `make` or `make newlib`
sudo make newlib # generates riscv64-unknown-elf-** for opensbi
sudo make linux # for generates riscv64-unknown-linux-gnu-** for building linux-kernel
cd ..

Note) riscv64-unknown-linux-gnu-riscv64-unknown-elf-どちらもビルドする必要がある。riscv64-unknown-elf-はopensbiのbuildに必要であり、riscv64-unknown-linux-gnu-linux kernelのbuildに必要である(linuxをbuildするのにriscv64-unknown-elf-を用いると、linkerの部分で落ちる)。

qemu

qemuは2019/10/15時点での最新バージョン4.1.0を用いている:

# download && install qemu(See Reference[1])
wget https://download.qemu.org/qemu-4.1.0.tar.xz
tar -Jxvf qemu-4.1.0.tar.xz
cd qemu-4.1.0
./configure --disable-werror --prefix=$RISCV --target-list="riscv64-softmmu"
make
sudo make install
cd ../

qemuとtoolchainのinstallで以下の実行可能ファイルが生成されているはずだ:

$ ls /opt/riscv/bin/
ivshmem-client                     riscv64-unknown-elf-run
ivshmem-server                     riscv64-unknown-elf-size
qemu-edid                          riscv64-unknown-elf-strings
qemu-ga                            riscv64-unknown-elf-strip
qemu-img                           riscv64-unknown-linux-gnu-addr2line
qemu-io                            riscv64-unknown-linux-gnu-ar
qemu-nbd                           riscv64-unknown-linux-gnu-as
qemu-pr-helper                     riscv64-unknown-linux-gnu-c++
qemu-system-riscv64                riscv64-unknown-linux-gnu-c++filt
riscv64-unknown-elf-addr2line      riscv64-unknown-linux-gnu-cpp
riscv64-unknown-elf-ar             riscv64-unknown-linux-gnu-elfedit
riscv64-unknown-elf-as             riscv64-unknown-linux-gnu-g++
riscv64-unknown-elf-c++            riscv64-unknown-linux-gnu-gcc
riscv64-unknown-elf-c++filt        riscv64-unknown-linux-gnu-gcc-9.2.0
riscv64-unknown-elf-cpp            riscv64-unknown-linux-gnu-gcc-ar
riscv64-unknown-elf-elfedit        riscv64-unknown-linux-gnu-gcc-nm
riscv64-unknown-elf-g++            riscv64-unknown-linux-gnu-gcc-ranlib
riscv64-unknown-elf-gcc            riscv64-unknown-linux-gnu-gcov
riscv64-unknown-elf-gcc-9.2.0      riscv64-unknown-linux-gnu-gcov-dump
riscv64-unknown-elf-gcc-ar         riscv64-unknown-linux-gnu-gcov-tool
riscv64-unknown-elf-gcc-nm         riscv64-unknown-linux-gnu-gdb
riscv64-unknown-elf-gcc-ranlib     riscv64-unknown-linux-gnu-gdb-add-index
riscv64-unknown-elf-gcov           riscv64-unknown-linux-gnu-gfortran
riscv64-unknown-elf-gcov-dump      riscv64-unknown-linux-gnu-gprof
riscv64-unknown-elf-gcov-tool      riscv64-unknown-linux-gnu-ld
riscv64-unknown-elf-gdb            riscv64-unknown-linux-gnu-ld.bfd
riscv64-unknown-elf-gdb-add-index  riscv64-unknown-linux-gnu-nm
riscv64-unknown-elf-gprof          riscv64-unknown-linux-gnu-objcopy
riscv64-unknown-elf-ld             riscv64-unknown-linux-gnu-objdump
riscv64-unknown-elf-ld.bfd         riscv64-unknown-linux-gnu-ranlib
riscv64-unknown-elf-nm             riscv64-unknown-linux-gnu-readelf
riscv64-unknown-elf-objcopy        riscv64-unknown-linux-gnu-run
riscv64-unknown-elf-objdump        riscv64-unknown-linux-gnu-size
riscv64-unknown-elf-ranlib         riscv64-unknown-linux-gnu-strings
riscv64-unknown-elf-readelf        riscv64-unknown-linux-gnu-strip
$ which qemu-system-riscv64
/opt/riscv/bin/qemu-system-riscv64
$ which riscv64-unknown-elf-gcc
/opt/riscv/bin/riscv64-unknown-elf-gcc
$ which riscv64-unknown-linux-gnu-gcc
/opt/riscv/bin/riscv64-unknown-linux-gnu-gcc

opensbi

同様のbootloaderにriscv-pkというのもある。これは、BBL(Berkeley Boot Loader)を生成するためのパッケージであるが、legacyな扱いらしい1。コード読んでもこちらのほうが詳細なので、こちらを用いる。

no payload

とりあえず、opensbiだけを動作させるには、qemuとriscv64-unknown-elf-gccがあればよい。以下のコマンドで起動する(Reference[4] (https://github.com/riscv/opensbi/blob/v0.5/docs/platform/qemu_virt.md#execution-on-qemu-risc-v-64-bit)の"No Payload Case"の部分を参考にした):

# build(https://github.com/riscv/opensbi/blob/v0.5/docs/platform/qemu_virt.md#execution-on-qemu-risc-v-64-bitの"No Payload Case"より)
make CROSS_COMPILE=riscv64-unknown-elf- PLATFORM=qemu/virt
# run
qemu-system-riscv64 -M virt -m 256M -nographic -kernel build/platform/qemu/virt/firmware/fw_payload.elf

build時はCLOSS_COMPILEがないと、以下のようなエラーが出力されてしまうので、付加する:

 CC        lib/sbi/riscv_asm.o
cc: error: unrecognized argument in option ‘-mabi=lp64’
cc: note: valid arguments to ‘-mabi=’ are: ms sysv
cc: error: unrecognized argument in option ‘-mcmodel=medany’
cc: note: valid arguments to ‘-mcmodel=’ are: 32 kernel large medium small; did you mean ‘medium’?
cc: error: unrecognized command line option ‘-mno-save-restore’; did you mean ‘-Wno-selector’?
cc: error: unrecognized command line option ‘-mstrict-align’; did you mean ‘-Wstrict-aliasing’?
Makefile:296: recipe for target '/home/vagrant/opensbi/build/lib/sbi/riscv_asm.o' failed

build時は以下のコマンドが実行される => https://gist.github.com/knknkn1162/d7bca477883c898e33111b311bb7244b

結果は以下のようになり、うまくいく => https://gist.github.com/knknkn1162/cfbe72349c427e6aed16d32056fb3705

Linux Kernel Payload

  • /arch/riscv/boot/Image .. linux kernel

  • rootfs .. busyboxにて種を作成

が必要でこれらを準備した後、qemuを用いてbootする。

linux-kernel

linux-kernel(v5.32)のビルド:

# create .config
git clone -b v5.3.6 --depth 1 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git # root directoryがlinux-stableになる。
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- defconfig
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- menuconfig
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- all
// 省略
  DTC     arch/riscv/boot/dts/sifive/hifive-unleashed-a00.dtb
  GEN     .version
  CHK     include/generated/compile.h
  LD      vmlinux.o
  MODPOST vmlinux.o
  MODINFO modules.builtin.modinfo
  KSYM    .tmp_kallsyms1.o
  KSYM    .tmp_kallsyms2.o
  LD      vmlinux
  SYSMAP  System.map
  Building modules, stage 2.
  MODPOST 2 modules
  CC      drivers/video/backlight/lcd.mod.o
  LD [M]  drivers/video/backlight/lcd.ko
  CC      fs/nfs/flexfilelayout/nfs_layout_flexfiles.mod.o
  LD [M]  fs/nfs/flexfilelayout/nfs_layout_flexfiles.ko
  OBJCOPY arch/riscv/boot/Image
  GZIP    arch/riscv/boot/Image.gz

arch/riscv/boot以下で作成されたファイルは https://gist.github.com/knknkn1162/bf862b4361ca04458ad76e6210f829fa のようになった。

OpenSBIのbuild

linux kernelのbuildでarch/riscv/boot/Imageが用意できたので、OpenSBIのbuild処理が実行できる:

make CROSS_COMPILE=riscv64-unknown-elf- PLATFORM=qemu/virt FW_PAYLOAD_PATH=../linux-stable/arch/riscv/boot/Image

build時に実行されるコマンドは以下の通り => https://gist.github.com/knknkn1162/dab88ab196a1b915469a9e3c03e536a0

busyboxのinstall, buildとrootfsの作成

# https://github.com/mirror/busybox/tree/1_31_0
git clone -b 1_31_0 --depth 1 https://github.com/mirror/busybox.git
cd busybox
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- defconfig
# check Busybox Setttings => Build BusyBox as a static binary (no shared libs)
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- menuconfig
make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- install
cd ../

make installすることで、_installというdirectoryができる。これはrootfsの種になるもので以下のようなファイルで構成されている:

$ tree -d ~/busybox/_install
/home/vagrant/busybox/_install
├── bin
├── sbin
└── usr
    ├── bin
    └── sbin
$ ls -ls ~/busybox/_install/sbin/init
0 lrwxrwxrwx 1 vagrant vagrant 14 Oct 14 07:25 /home/vagrant/busybox/_install/sbin/init -> ../bin/busybox

、後に、/mnt/rootfsの中に突っ込んでおく。

rootfsの作成に関しては、reference[6]を参考にした。

# in ${HOME} directory
dd if=/dev/zero of=rootfs.img bs=1M count=300
mkfs.ext2 -L riscv-rootfs rootfs.img
sudo mkdir /mnt/rootfs
sudo mount rootfs.img /mnt/rootfs
# inside /mnt/rootfs
sudo cp -ar ../busybox/_install/* /mnt/rootfs
mkdir /mnt/rootfs/{dev,home,mnt,proc,sys,tmp,var}
sudo chown -R -h root:root /mnt/rootfs/
$ df /mnt/rootfs
Filesystem     1K-blocks  Used Available Use% Mounted on
/dev/loop1        297485  3683    278442   2% /mnt/rootfs
$ mount
/home/vagrant/rootfs.img on /mnt/rootfs type ext2 (rw,relatime)

Note) /mnt/rootfsの中にdev directoryを作らないと、以下のようなmessageが出続けてしまう:

can't open /dev/tty4: No such file or directory

can't open /dev/tty3: No such file or directory
can't open /dev/tty2: No such file or directory
can't open /dev/tty4: No such file or directory
can't open /dev/tty3: No such file or directory
can't open /dev/tty2: No such file or directory

OpenSBIのrun

opensbiのdirectoryにて以下を実行する。主に、https://github.com/riscv/opensbi/blob/v0.5/docs/platform/qemu_virt.md#execution-on-qemu-risc-v-64-bit を参考にした:

# in opensbi directory
qemu-system-riscv64 -M virt -m 256M -nographic -kernel build/platform/qemu/virt/firmware/fw_payload.elf -drive file=../rootfs.img,format=raw,id=hd0 -device virtio-blk-device,drive=hd0 -append "root=/dev/vda rw console=ttyS0

結果は以下のようになり、うまくいっている => https://gist.github.com/knknkn1162/047f186106fdcaa016261eff3ac95854

終わりに

個人的にはrootfsの作成が不慣れなためにかなり時間がかかってしまった。次の記事ではopensbi(boot loader)のコード解説をやっていきたいと思う。xv6-riscvのboot部分より一回り規模の大きなコードになっているが、基本のところはxv6-riscvと共通しているので、慣れればそれほど難しくなさそう。


  1. opensbi(v0.5)のREADME.mdに"These example firmwares can be used to replace the legacy riscv-pk bootloader (aka BBL)“とある

  2. 正確には、v5.3.6を用いている。v4.19.*だと、arch/riscv/boot/Imageが生成されないため、うまく行かなかった。