之前有寫過一篇 QEMU 虛擬機器設定的文章,是在講如何用 QEMU安裝 Windows 或是 Linux。但不論是 Windows 或 Linux,只要沒有硬體加速 (ex. Intel HAXM),跑起來都是很辛苦的,所以用處其實不大,實際上用 Virtualbox 或 VMware 會方便很多。
但想不到最近在開發工作上發揮了奇效,用 QEMU 來執行 ARM 的模擬,可以很順利的執行各種 user level 的程式,kernel module 只要跟硬體不相關,也都可以運行的很順利。只要對需要真正硬體部份的程式碼稍做修改,做一些測試的程式碼 (ex. register rw),就可以有效的達到一定的模擬效果。
本文會以 Ubuntu 20.04 為平台,在上面安裝 QEMU,以 Buildroot 來建立 rootfs,使用 kernel 4.4 分別為 i386 / arm /arm64 建立一個最基本的可開機的系統。這邊以 buildroot 來做為 rootfs,在實際開發專案時,只要將 rootfs 與 toolchain 替代成自己平台的的檔案系統即可。
UBuntu 20.04 套件安裝
在執行期間需要一些套件,請下達以下面命令,來安裝所需的套件。
1 2 |
sudo apt-get update sudo apt-get install qemu-user qemu-system-x86 qemu-system-arm libssl-dev ncurses-dev genext2fs pigz lrzsz unzip git texinfo build-essential net-tools |
範本檔案
為了簡化示範的流程,本文會用一個預先建立的目錄結構與script,來解說命令,檔案會附在文末。請先下載下來後,放在 ubuntu 內。用以下命令解壓縮。
1 2 |
tar zvxf qemu_sys_template.tar.gz cd qemu_sys_template |
下載編譯 Buildroot 以建立檔案系統與Toolchain
通常在拿到一個實際的開發平台時,都會有一些預編的檔案系統,基本上都會以這個為基礎,在加上自己專案的檔案。但是由於本文沒有這些東西,所以就從 buildroot 自行編譯一份,這樣較容易讓讀者學習,但功能會較為簡單。
執行下列命令,下載 buildroot 套件,解壓縮並進入設定畫面
1 2 3 4 |
wget https://buildroot.org/downloads/buildroot-2020.02.11.tar.gz tar zvxf buildroot-2020.02.11.tar.gz cd buildroot-2020.02.11 make menuconfig |
如果buildroot編譯後想重新設定,建議將 output 目錄整個刪除,會比較沒有問題。
i386 設定
若要建立 32bit x86 系統,進入系統後,勾選下列項目後,選 exit 離開儲存設定。
- Toolchain –> Kernel Headers ,選擇 4.4.x
- Toolchain –> GCC compiler Version ,選擇 gcc 7.x
- Toolchain –> Enable C++ support,勾選
ARM 設定
若要建立 32bit ARM 系統,進入系統後,勾選下列項目後,選 exit 離開儲存設定。
- Target options –> Architecture,選擇 ARM little endian
- Toolchain –> Kernel Headers ,選擇 4.4.x
- Toolchain –> GCC compiler Version ,選擇 gcc 7.x
- Toolchain –> Enable C++ support,勾選
ARM64 設定
若要建立 64bit ARM 系統,進入系統後,勾選下列項目後,選 exit 離開儲存設定。
- Target options –> Architecture,選擇 AArch64 little endian
- Toolchain –> Kernel Headers ,選擇 4.4.x
- Toolchain –> GCC compiler Version ,選擇 gcc 7.x
- Toolchain –> Enable C++ support,勾選
選定完畢後,下達 make 來編譯。
解壓 rootfs
完成後,會產生下列檔案
- rootfs : output/images/rootfs.tar
- toolchain 執行檔 : output/host/bin
為了符合範例script的設定,請下達下面命令,將 rootfs.tar 解開
1 2 3 4 |
export PATH=`pwd`/output/host/bin:$PATH cd .. cd sim/rootfs tar vxf ../../buildroot-2020.02.11/output/images/rootfs.tar |
頭一行命令,會先指定 toolchain 的路徑,以方便後面的kernel編譯。
下載並編譯 Kernel 4.4
接下來要下載並編譯 kernel 4.4,編譯器會使用剛 buildroot 所產生的編譯器。
1 2 3 4 |
cd ../devtool wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.4.1.tar.xz tar Jvxf linux-4.4.1.tar.xz cd linux-4.4.1 |
i386
1 2 |
make ARCH=i386 defconfig make ARCH=i386 bzImage -j `nproc` |
ARM
1 2 |
make ARCH=arm vexpress_defconfig make ARCH=arm CROSS_COMPILE=arm-linux- bzImage dtbs -j `nproc` |
ARM64
1 2 |
make ARCH=arm64 ARCH=arm64 defconfig make ARCH=arm64 CROSS_COMPILE=aarch64-linux- Image dtbs -j `nproc` |
啟動 QEMU 開機
當完成上面的動作後,基本上就具備了 rootfs / kernel,就可以準備開機了,下達下面的命令,來使用script開機。
1 |
../runqemu [i386/arm/arm64] |
runqemu 後面請輸入自己的平台種類,以執行不同的命令。
進入系統後,請用 root 來登入。由於 /dev 內的檔案並沒有自動產生,會發生一些錯誤訊息,請下達下面命令來產生裝置檔。
1 2 |
rm /dev/null mdev -s |
啟動網路
runqemu 裡的腳本,已經設定好了網路,但系統內並沒有啟動。下達以下命令,可以用 dhcpc 來取得 ip。預設都是取得 10.0.2.15 這個 IP,除了 ping (ICMP) 不能使用外,其它網路連線皆可正常透過 NAT 連外。
1 |
udhcpc -i eth0 |
可以試著以 telnet 到 ptt 看看。
1 |
telnet ptt.cc |
離開 QEMU
要離開 QEMU,可以在虛擬機內以 poweroff 關機,就會離開。或者按 Ctrl+a,再按x,就可跳離了。
runqemu 說明
執行 QEMU 的主要命令,就是 runqemu 這個程式了,這邊稍微做一下說明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
#!/bin/bash if [ "$1" = "" ];then echo "Please sepcify ARCH. arm, arm64 or i386" exit 1 fi ARCH=$1 QEMU_KERNEL=`pwd` # 檔案系統所在目錄,也就是剛用 buildroot 編出來的東西 ROOTFS=../../rootfs # 指定啟動時要使用的 Kernel 和 devicetree # 因為範例也是網上找的,arm64 和 i386 不用指定的原因不確定 if [ $ARCH = "arm" ];then KERNEL=arch/arm/boot/zImage DTB=arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dtb elif [ $ARCH = "arm64" ];then KERNEL=arch/arm64/boot/Image DTB= elif [ $ARCH = "i386" ];then KERNEL=arch/i386/boot/bzImage DTB= fi # 由於 QEMU 是用 NAT 連線的方式,無法直接由 Host OS 連進來 # 故這邊要指定從 Host 做 port forwarding # 由 host TCP 2353 轉進 Guest 的 23 port # 由 host UDP 6759 轉進 Guest 6789 port REDIR="hostfwd=tcp::2353-:23" REDIR="$REDIR,hostfwd=udp::6759-:6789" CORES=2 MEM=512M # 由於本範例是使用 ramdisk 當做 rootfs ,重開後資料會不見。 # 因此這邊產生一塊約 100MB 的空間,做為可寫入的區塊 # 進系統後可以 fdisk 和 mke2fs 來分割、格式化使用 if [ ! -e ../empty.ext2.gz ];then mkdir empty genext2fs -v -b 100000 -N 5000 -d empty -e 0 - | gzip -5 > ../empty.ext2.gz fi [ ! -e ../img.ext2 ] && gunzip -c ../empty.ext2.gz > ../img.ext2 # 將 rootfs 用 cpio 和 pigz (gzip) 打包 cd ${ROOTFS} find . | cpio -o -H newc | pigz -5 > ${QEMU_KERNEL}/rootfs.img cd ${QEMU_KERNEL} if [ "$ARCH" = "arm" ];then # -M vexpress-a15 : 指定 ARM 的 machine type # -nographic : 不要使用 GUI 視窗 # --append : 指定 rootfs 的裝置, console 為 kernel config 內的設定 ttyAMA0 # rdinit 為 rootfs /linuxrc。若是使用自己的 rootfs,要依實際的檔名 # 做修改 (ex. sysinit) # initrd : 指定 ramdisk 的檔案 # -drive : 指定附加的可寫入的磁碟區塊 # -net : 指定網路的型態和 port forwarding 的 list qemu-system-arm -M vexpress-a15 -nographic -m ${MEM} \ -kernel ${KERNEL} -dtb ${DTB} \ --append "root=/dev/ram0 console=ttyAMA0 rdinit=/linuxrc" \ -initrd rootfs.img \ -drive if=none,file=../img.ext2,id=hd0,format=raw -device virtio-blk-device,drive=hd0 \ -net user,hostfwd=tcp::8489-:80,$REDIR -net nic -smp ${CORES} elif [ "$ARCH" = "i386" ];then # --append : x86 一般 console 會用 ttyS0 # -serial : 上網查到的 qemu-system-i386 -m ${MEM} -kernel ${KERNEL} \ --append "root=/dev/ram0 rdinit=/linuxrc console=ttyS0 " \ -smp ${CORES} -nographic -serial mon:stdio \ -initrd rootfs.img \ -net user,hostfwd=tcp::8489-:80,$REDIR -net nic elif [ "$ARCH" = "arm64" ];then # -machine virt -cpu cortex-a57 : 上網查到的 qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine type=virt \ -nographic -m ${MEM} -kernel ${KERNEL} \ --append "root=/dev/ram0 console=ttyAMA0 rdinit=/linuxrc" -initrd rootfs.img \ -drive if=none,file=../img.ext2,id=hd0,format=raw -device virtio-blk-device,drive=hd0 \ -net user,hostfwd=tcp::8489-:80,$REDIR -net nic -smp ${CORES} fi exit 0 |
使用平台的檔案系統與toolchain
本文的例子是用 buildroot 的檔案系統和toolchain,當使用自己的開發平台時,流程會做一些更動,這邊稍微說明一下。就以附件壓縮檔解開後的位置做操作,目錄結構如下
1 2 3 4 5 |
qemu_sys_template `-- sim |-- devtool | `-- runqemu `-- rootfs |
Buildroot
Buildroot 的部份就不需要執行了,將你開發的檔案系統所有檔案,複製到 rootfs 下。
Toolchain
既然是用自己的開發平台,相信這部份你也有了,只要將其 bin/ 錄徑設置到 PATH 內即可。
Kernel 編譯
由於我們無法模擬硬體,所以 Kernel 還是使用下載下來的 kernel 4.4,再以自己的 toolchain 做編譯。編譯的命令,參考前面「下載並編譯 Kernel 4.4」的部份,依你是 arm 或 arm64 下達不同的命令,主要的差別會是在 CROSS_COMPILER 的部份,這邊就是改成你toolchain的前綴字,只要填對前綴字應該都可成功編譯過。
執行
最後就是執行 runqemu 了,唯一要注意的就是 rdinit=”/linuxrc” 的這個地方。你可以看自己的 rootfs 根目錄下是使用是用哪個程式做為 init 檔,一般常見的就是 linuxrc, sysvinit 和 init,把它改成你的檔案即可。
其它問題
由於對處理器的架構指令不專門,所以當碰到系統不穩定,其實也沒什麼好辦法,這邊提供一點經驗分享
- 如有不穩定,可以試著增加記憶體看看
- 如果碰到 illegal instruction 的訊息,或者是 QEMU 突然跳掉,有可能是 compiler 產生的程式碼該模擬的CPU不支援。以我的經驗來說,ARM的 Machine 一開始是選 vexpress-a9,就碰到上述的跳掉或非法命令的問題,最後試著將機器改成 vexpress-a15,問題就不見了。若有做這方面的修改,記得 dtb 也要做變更。
結語
QEMU 的模擬對我目前的工作相當的有幫助,但仔細想想以往的工作經驗,好像對之前的那些工作就用不上。也難怪我一直推荐朋友QEMU,回應都很冷淡 XD 希望對有需要的人有幫助了~~
附件
qemu_sys_template.tar QEMU使用範例檔
Latest Comments