搭建qemu运行arm64 Linux环境

概述

        QEMU(Quick EMUlator)是一款通用的开源模拟器和虚拟化工具,它可以模拟绝大多数的CPU平台,比如x86、ARM、ARM64、RISC-V、PowerPC、MIPS等等,还能模拟各种硬件外设,如内存、emmc、sdcard、usb等等,所以通过qemu就能模拟出一块开发板。芯片公司研发芯片过程中,在芯片回片之前,一般都会先通过qemu来做前期的软件开发调试工作。这篇文章我们介绍如何通过qemu模拟ARM64平台并跑起来linux kernel,进而方便内核开发和调试。

这篇文章的涉及到的工具和源码的版本信息如下:

Ubuntu: Ubuntu 22.04.2 LTSqemu: 8.2.0kernel: Linux 6.1.72busybox: busybox-1.36.1

如果您当前还没有ubuntu的开发环境,可以参考 VMware Workstation安装Ubuntu-22.04虚拟机 这篇教程来安装ubuntu系统。

编译qemu模拟器

安装工具

安装编译qemu源码需要用到的工具,命令和日志如下:

max@ubuntu2204:~$ sudo apt update
[sudo] password for max: 
Hit:1 http://mirrors.tuna.tsinghua.edu.cn/ubuntu jammy InRelease
Hit:2 http://mirrors.tuna.tsinghua.edu.cn/ubuntu jammy-updates InRelease
……
……
Building dependency tree... Done
Reading state information... Done
407 packages can be upgraded. Run 'apt list --upgradable' to see them.
max@ubuntu2204:~$
max@ubuntu2204:~$ sudo apt -y install python3.10-venv python3-sphinx ninja-build libpixman-1-dev pkg-config flex bison libfdt-dev libglib2.0-dev
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
……
……
Setting up python3-docutils (0.17.1+dfsg-2) ...
Setting up python3-sphinx (4.3.2-1) ...
max@ubuntu2204:~$

下载qemu源码

qemu模拟器的官网地址为https://www.qemu.org/,主页如下:

图片

在qemu官网download页面查看qemu发布的所有的版本源码包,如下:

图片

通过wget下载qemu模拟器的源码,命令和日志如下:

max@ubuntu2204:~$ wget https://download.qemu.org/qemu-8.2.0.tar.xz
--2024-01-13 21:46:41--  https://download.qemu.org/qemu-8.2.0.tar.xz
Resolving download.qemu.org (download.qemu.org)... 143.244.51.238, 89.187.187.12, 185.180.13.211, ...
……
……
qemu-8.2.0.tar.xz                                  100%[===============================================================================================================>] 123.99M  2.54MB/s    in 58s     
2024-01-13 21:47:42 (2.14 MB/s) - ‘qemu-8.2.0.tar.xz’ saved [130008888/130008888]
max@ubuntu2204:~$

编译qemu模拟器

解压下载的qemu-8.2.0.tar.xz压缩包,并编译qemu源码,命令和日志如下:

max@ubuntu2204:~$ tar xvJf qemu-8.2.0.tar.xz
qemu-8.2.0/
qemu-8.2.0/.patchew.yml
qemu-8.2.0/ebpf/
……
……
qemu-8.2.0/subprojects/libvhost-user/libvhost-user-glib.h
qemu-8.2.0/subprojects/keycodemapdb.wrap
qemu-8.2.0/qemu.nsi
max@ubuntu2204:~$ 
max@ubuntu2204:~$ cd qemu-8.2.0
max@ubuntu2204:~/qemu-8.2.0$ 
max@ubuntu2204:~/qemu-8.2.0$ ./configure
Using './build' as the directory for build output
python determined to be '/usr/bin/python3'
python version: Python 3.10.12
……
……
Found ninja-1.10.1 at /usr/bin/ninja
Running postconf script '/home/max/qemu-8.2.0/build/pyvenv/bin/python3 /home/max/qemu-8.2.0/scripts/symlink-install-tree.py'
max@ubuntu2204:~/qemu-8.2.0$
max@ubuntu2204:~/qemu-8.2.0$ make -j8
changing dir to build for make ""...
make[1]: Entering directory '/home/max/qemu-8.2.0/build'
ninja: no work to do.
/home/max/qemu-8.2.0/build/pyvenv/bin/meson introspect --targets --tests --benchmarks | /home/max/qemu-8.2.0/build/pyvenv/bin/python3 -B scripts/mtest2make.py > Makefile.mtest
……
……
[9342/9342] Linking target tests/qtest/dbus-display-test
make[1]: Leaving directory '/home/max/qemu-8.2.0/build'
max@ubuntu2204:~/qemu-8.2.0$

上面命令会把qemu支持的所有平台的模拟器都编译出来,耗时会较长。如果想要节省编译时间,在执行configure命令时可以通过配置 target-list参数来选择只编译aarch64的模拟器,如下:

./configure --target-list=aarch64-softmmu,aarch64-linux-user,aarch64_be-linux-user --enable-kvm --enable-debug

到此qemu模拟器就编译完成了,查看生成的各个平台的模拟器,如下:

max@ubuntu2204:~/qemu-8.2.0$ ls build/qemu-system-*
build/qemu-system-aarch64  build/qemu-system-hppa         build/qemu-system-microblazeel  build/qemu-system-nios2    build/qemu-system-riscv64  build/qemu-system-sparc    build/qemu-system-xtensaeb
build/qemu-system-alpha    build/qemu-system-i386         build/qemu-system-mips          build/qemu-system-or1k     build/qemu-system-rx       build/qemu-system-sparc64
build/qemu-system-arm      build/qemu-system-loongarch64  build/qemu-system-mips64        build/qemu-system-ppc      build/qemu-system-s390x    build/qemu-system-tricore
build/qemu-system-avr      build/qemu-system-m68k         build/qemu-system-mips64el      build/qemu-system-ppc64    build/qemu-system-sh4      build/qemu-system-x86_64
build/qemu-system-cris     build/qemu-system-microblaze   build/qemu-system-mipsel        build/qemu-system-riscv32  build/qemu-system-sh4eb    build/qemu-system-xtensa

后面就可以通过 build/qemu-system-aarch64 模拟arm64平台处理器来运行Linux kernel了。

安装arm64交叉编译工具链

当前我们选择用linaro的交叉编译工具链来编译uboot、kernel、busybox等,linaro交叉编译工具链的下载网址为:https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/aarch64-linux-gnu/,网页如下:

图片

我们先创建一个~/cross-compiler目录,然后在该目录下下载交叉编译工具链压缩包gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz 并解压,命令和日志如下:

max@ubuntu2204:~$ mkdir ~/cross-compiler && cd ~/cross-compiler
max@ubuntu2204:~/cross-compiler$ wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/aarch64-linux-gnu/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz
--2024-01-14 00:07:24--  https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/aarch64-linux-gnu/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz
Resolving releases.linaro.org (releases.linaro.org)... 52.215.200.125
……
……
gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu. 100%[===============================================================================================================>] 112.43M  1.13MB/s    in 4m 37s  

2024-01-14 00:12:04 (416 KB/s) - ‘gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz’ saved [117896452/117896452]
max@ubuntu2204:~/cross-compiler$
max@ubuntu2204:~/cross-compiler$ tar xf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz
max@ubuntu2204:~/cross-compiler$

把该交叉编译工具链的bin目录添加到 PATH 环境变量中,如下:

PATH=/home/max/cross-compiler/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH

这样我们就可以使用这个交叉编译工具链了,如下:

max@ubuntu2204:~/cross-compiler$ aarch64-linux-gnu-gcc -v
Using built-in specs.
COLLECT_GCC=aarch64-linux-gnu-gcc
COLLECT_LTO_WRAPPER=/home/max/cross-compiler/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/../libexec/gcc/aarch64-linux-gnu/7.5.0/lto-wrapper
……
……
Thread model: posix
gcc version 7.5.0 (Linaro GCC 7.5-2019.12) 
max@ubuntu2204:~/cross-compiler$

到此arm64交叉编译工具链就安装完成了。

编译linux kernel

安装工具

在ubuntu上安装编译内核需要用到的工具,命令和日志如下:

max@ubuntu2204:~$ sudo apt install -y build-essential libncurses-dev bison flex libssl-dev libelf-dev git
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
……
……
Setting up build-essential (12.9ubuntu3) ...
Processing triggers for man-db (2.10.2-1) ...
Processing triggers for libc-bin (2.35-0ubuntu3.1) ...
max@ubuntu2204:~$

下载linux内核源码

我们这里下载的是linux官方最新的longterm版本linux-6.1.y 的内核,从linux官网下载源码耗时较长,所以我们选择从清华仓库中下载。大家可以参考 获取Linux内核源码 这篇文章来查看linux官方发布的所有版本和各种快速下载内核源码方法。下载命令和日志如下:

max@ubuntu2204:~$ git clone https://mirrors.tuna.tsinghua.edu.cn/git/linux-stable.git -b linux-6.1.y
Cloning into 'linux-stable'...
remote: Enumerating objects: 12104695, done.
Receiving objects: 100% (12104695/12104695), 2.58 GiB | 6.23 MiB/s, done.
remote: Total 12104695 (delta 0), reused 0 (delta 0), pack-reused 12104695
Resolving deltas: 100% (10399156/10399156), done.
Checking objects: 100% (33554432/33554432), done.
Updating files: 100% (78744/78744), done.
max@ubuntu2204:~$

进入linux-stable目录查看下载的内核的版本为Linux 6.1.72,命令和日志如下:

max@ubuntu2204:~$ cd linux-stable/
max@ubuntu2204:~/linux-stable$ git branch -vv
* linux-6.1.y 7c58bfa711cb [origin/linux-6.1.y] Linux 6.1.72
max@ubuntu2204:~/linux-stable$

编译linux内核源码

进入到linux-stable 内核目录下,通过menuconfig配置内核选项,命令和日志如下:

max@ubuntu2204:~/linux-stable$ make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 O=build menuconfig
make[1]: Entering directory '/home/max/linux-stable/build'
  GEN     Makefile
#
# using defaults found in arch/arm64/configs/defconfig
#

*** End of the configuration.
*** Execute 'make' to start the build or try 'make help'.

make[1]: Leaving directory '/home/max/linux-stable/build'
max@ubuntu2204:~/linux-stable$

执行上面的命令会进入到menuconfig 菜单页面,如下:

图片

在菜单中依次选择如下选项:

Device Drivers    >  Block devices        > RAM block device support

并且把 “Default RAM disk size (kbytes)”调整为65536,调整后界面如下:

图片

然后选择save保存,并退出menuconfig菜单,到此内核选项就配置完成了。下面开始编译内核镜像,命令和日志如下:

max@ubuntu2204:~/linux-stable$ make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 O=build -j8
make[1]: Entering directory '/home/max/linux-stable/build'
  SYNC    include/config/auto.conf.cmd
  GEN     Makefile
……
……
  LD [M]  sound/soc/tegra/snd-soc-tegra210-sfc.ko
make[1]: Leaving directory '/home/max/linux-stable/build'
max@ubuntu2204:~/linux-stable$

查看生成的Image内核镜像,命令和日志如下:

max@ubuntu2204:~/linux-stable$ file build/arch/arm64/boot/Image
build/arch/arm64/boot/Image: Linux kernel ARM64 boot executable Image, little-endian, 4K pages
max@ubuntu2204:~/linux-stable$

到此内核镜像Image就编译完成了。

编译busybox

Busybox的官方网页为https://busybox.net/ ,我们通过wget下载主页上的BusyBox 1.36.1版本的源码,命令和日志如下:

max@ubuntu2204:~$ wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
--2024-01-14 11:50:58--  https://busybox.net/downloads/busybox-1.36.1.tar.bz2
Resolving busybox.net (busybox.net)... 140.211.167.122
……
……
busybox-1.36.1.tar.bz2                   100%[================================================================================>]   2.41M  1.11MB/s    in 2.2s    
2024-01-14 11:51:15 (1.11 MB/s) - ‘busybox-1.36.1.tar.bz2’ saved [2525473/2525473]
max@ubuntu2204:~$

解压busybox源码并进入源码目录,然后通过menuconfig菜单选择静态编译,即选择“Build static binary (no shared libs)”选项,命令和日志如下:

max@ubuntu2204:~$ tar xf busybox-1.36.1.tar.bz2
max@ubuntu2204:~$ cd busybox-1.36.1/
max@ubuntu2204:~/busybox-1.36.1$ make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 menuconfig
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/basic/split-include
……
……
*** End of configuration.
*** Execute 'make' to build the project or try 'make help'.
max@ubuntu2204:~/busybox-1.36.1$

执行上面的命令,会显示出busybox的menuconfig菜单,如下:

图片

在菜单中依次选择:

Settings    > [*] Build static binary (no shared libs)

界面如下:

图片

然后保存退出,到此busybox的选项就配置完成了。下面开始编译busybox,命令和日志如下:

max@ubuntu2204:~/busybox-1.36.1$ make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 install -j8
  SPLIT   include/autoconf.h -> include/config/*
  GEN     include/bbconfigopts.h
  GEN     include/common_bufsiz.h
……
……
--------------------------------------------------
You will probably need to make your busybox binary
setuid root to ensure all configured applets will
work properly.
--------------------------------------------------

查看生成的busybox可执行文件,命令如下:???????

max@ubuntu2204:~/busybox-1.36.1$ file _install/bin/busybox
_install/bin/busybox: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), stati

制作 initrd 文件系统镜像

为了方便制作initrd文件系统镜像,我们通过mk_initrd.sh脚本来生成initrd.ext4镜像文件,后面在内核跑起来后会挂载该镜像中的文件系统,脚本如下:

#!/bin/bash
set -x
CURR_DIR=`pwd`
MOUNT_DIR=$CURR_DIR/mount_point
BUSYBOX_PREFIX=/home/max/busybox-1.36.1
dd if=/dev/zero of=initrd.ext4 bs=1M count=32
mkfs.ext4 initrd.ext4
mkdir -p $MOUNT_DIR
sudo mount initrd.ext4 $MOUNT_DIR
cp -arf $BUSYBOX_PREFIX/_install/* $MOUNT_DIR
cd $MOUNT_DIR
mkdir -p etc dev mnt proc sys tmp mnt etc/init.d/
echo "proc /proc proc defaults 0 0" > etc/fstab
echo "tmpfs /tmp tmpfs defaults 0 0" >> etc/fstab
echo "sysfs /sys sysfs defaults 0 0" >> etc/fstab
echo "#!/bin/sh" > etc/init.d/rcS
echo "mount -a" >> etc/init.d/rcS
echo "mount -o remount,rw /" >> etc/init.d/rcS
echo "echo -e "Welcome to ARM64 Linux"" >> etc/init.d/rcS
chmod 755 etc/init.d/rcS
echo "::sysinit:/etc/init.d/rcS" > etc/inittab
echo "::respawn:-/bin/sh" >> etc/inittab
echo "::askfirst:-/bin/sh" >> etc/inittab
chmod 755 etc/inittab
cd dev
mknod console c 5 1
mknod null c 1 3
mknod tty1 c 4 1
cd $CURR_DIR
sudo umount $MOUNT_DIR
echo "make initrd ok!"

用户需要根据自己编译的busybox所在目录来调整脚本中的 BUSYBOX_PREFIX 变量。修改完mk_initrd.sh 脚本后,我们先创建一个make_initrd目录,并把 mk_initrd.sh 脚本拷贝到该目录下,由于在ubuntu下做mount和umount操作需要sudo权限,所以在执行mk_initrd.sh脚本前需要先在终端进入sudo权限,然后再执行该 mk_initrd.sh 脚本生成initrd.ext4镜像,命令和日志如下:???????

max@ubuntu2204:~$ mkdir make_initrd
max@ubuntu2204:~$ cd make_initrd/
max@ubuntu2204:~/make_initrd$ ls
mk_initrd.sh
max@ubuntu2204:~/make_initrd$ 
max@ubuntu2204:~/make_initrd$ sudo su
[sudo] password for max: 
root@ubuntu2204:/home/max/make_initrd# 
root@ubuntu2204:/home/max/make_initrd# sh mk_initrd.sh
+ pwd
+ CURR_DIR=/home/max/make_initrd
+ MOUNT_DIR=/home/max/make_initrd/mount_point
+ BUSYBOX_PREFIX=/home/max/busybox-1.36.1
……
……
+ echo make initrd ok!
make initrd ok!
root@ubuntu2204:/home/max/make_initrd# 
root@ubuntu2204:/home/max/make_initrd# exit
exit
max@ubuntu2204:~/make_initrd$ ls -lh initrd.ext4
-rw-r--r-- 1 root root 32M  1月 14 12:32 initrd.ext4

到此文件系统镜像initrd.ext4就做好了。

使用qemu aarch64模拟器运行Linux内核

首先查看当前qemu支持哪些ARM 64位的CPU,命令和日志如下:???????

max@ubuntu2204:~/qemu-8.2.0/build/aarch64-softmmu$ ./qemu-system-aarch64 -cpu help
Available CPUs:
  a64fx
  arm1026
  arm1136
……
……
  sa1100
  sa1110
  ti925t
max@ubuntu2204:~/qemu-8.2.0/build/aarch64-softmmu$

这里我们选择用ARMv8的64位CPU cortex-a57来运行linux内核,由于命令参数过长,方便起见,我们把运行qemu模拟器的命令写到一个脚本qemu.sh中,脚本如下:???????

#!/bin/bash
set -x
QEMU=~/qemu-8.2.0
KERNEL=~/linux-stable
INITRD=~/make_initrd
$QEMU/build/aarch64-softmmu/qemu-system-aarch64 
-nographic 
-M virt 
-cpu cortex-a57 
-smp 2 
-m 4G 
-kernel $KERNEL/build/arch/arm64/boot/Image 
-append "nokaslr root=/dev/ram init=/linuxrc console=ttyAMA0 console=ttyS0" 
-initrd $INITRD/initrd.ext4

用户需要根据自己编译的qemu、kernel和initrd.ext4镜像所在的目录调整qemu.sh脚本中的QEMU、KERNEL、INITRD三个变量。然后通过qemu.sh脚本来让模拟器运行linux内核,对应的命令和日志如下:???????

max@ubuntu2204:~$ sh qemu.sh 
+ QEMU=/home/max/qemu-8.2.0
+ KERNEL=/home/max/linux-stable
+ INITRD=/home/max/make_initrd
+ /home/max/qemu-8.2.0/build/aarch64-softmmu/qemu-system-aarch64 -nographic -M virt -cpu cortex-a57 -smp 2 -m 4G -kernel /home/max/linux-stable/build/arch/arm64/boot/Image -append nokaslr root=/dev/ram init=/linuxrc console=ttyAMA0 console=ttyS0 -initrd /home/max/make_initrd/initrd.ext4
[    0.000000] Booting Linux on physical CPU 0x0000000000 [0x411fd070]
[    0.000000] Linux version 6.1.72 (max@ubuntu2204) (aarch64-linux-gnu-gcc (Linaro GCC 7.5-2019.12) 7.5.0, GNU ld (Linaro_Binutils-2019.12) 2.28.2.20170706) #1 SMP PREEMPT Sun Jan 14 11:57:15 CST 2024
……
……
[    1.739471] Freeing unused kernel memory: 7552K
[    1.740835] Run /linuxrc as init process
[    1.865209] EXT4-fs (ram0): re-mounted. Quota mode: none.
Welcome to ARM64 Linux

Please press Enter to activate this console. 
~ #

到此我们就用qemu aarch64的模拟器跑起来linux内核了,并挂载上了文件系统。此时我们就能输入命令控制模拟器中的linux系统了,比如:???????

~ # cat /proc/version 
Linux version 6.1.72 (max@ubuntu2204) (aarch64-linux-gnu-gcc (Linaro GCC 7.5-2019.12) 7.5.0, GNU ld (Linaro_Binutils-2019.12) 2.28.2.20170706) #1 SMP PREEMPT Sun Jan 14 11:57:15 CST 2024
~ #

如果要退出qemu模拟器的系统,需要先按下ctrl+a,松开后,再按x,这样就退出qemu模拟器的系统了。

到此我们就成功通过qemu aarch64模拟器跑起来linux内核了!

请关注微信公众号 “Linux研习社” 获取更多技术内容: