无垠之码

深度剖析代码之道


qemu虚拟机构建

目前构建qemu虚拟机常见有: 下载成品镜像|源码编译内核和根文件系统|光盘安装 三种方式, 以下分别通过构建 riscv64|aarch64|sparc64 虚拟机介绍三种Qemu虚拟机构建方式

0.构建riscv64虚拟机


一些Linux发行版提供了成品虚拟机镜像,Qemu用户可以直接加载使用。例如,Ubuntu提供了多个架构的preinstalled-server镜像,可从cdimage.ubuntu.com下载,经过解压和简单配置即可启动。

sudo apt-get install u-boot-qemu opensbi qemu-system qemu
wget https://cdimage.ubuntu.com/releases/20.04.2/release/ubuntu-20.04.2-preinstalled-server-riscv64.img.xz
xz -dk ubuntu-20.04.2-preinstalled-server-riscv64.img.xz 
qemu-img resize -f raw ubuntu-20.04.2-preinstalled-server-riscv64.img +5G
sudo qemu-system-riscv64 \
        -machine virt \
        --nographic \
        -m 2G \
        -smp 2 \
        -bios /usr/lib/riscv64-linux-gnu/opensbi/generic/fw_jump.elf \
        -kernel /usr/lib/u-boot/qemu-riscv64_smode/uboot.elf \
        -device virtio-net-device,netdev=eth0 \
        -netdev user,id=eth0,hostfwd=tcp::2222-:22 \
        -drive file=ubuntu-20.04.2-preinstalled-server-riscv64.img,format=raw,if=virtio
        # -writeconfig qemu.conf
sudo qemu-system-riscv64 -readconfig qemu.conf

首次登录用户名密码:ubuntu/ubuntu

1.构建aarch64虚拟机


备忘: ARMv8是继ARMv7之后的一次重要架构升级,其主要变化之一是引入64位架构。在此之前,ARM的产品均为32位架构。具体来说,ARM架构分为以下几种浮点运算模式:ARMEL为32位软浮点,ARMHF为32位硬浮点,而 ARM64(Apple的命名方式)或AArch64(GNU/GCC 的命名方式)为64位硬浮点。(32位和64位架构,并不是单纯指总线宽度,而是指寄存器宽度和指令集的宽度)

sudo qemu-system-aarch64 \
        -machine virt \
        -cpu max \
        --nographic \
        -kernel ./image \
        -nic none \
        -device virtio-net-device,netdev=eth0 \
        -netdev user,id=eth0,hostfwd=tcp::2222-:22 \
        -hda ./rootfs.qcow2 \
        -append 'root=/dev/vda rw nokaslr console=ttyS0'

本小节介绍DIY方式制作一个Qemu虚拟机: step0.源码编译内核 step1.根文件系统 step2.内核模块安装

step0.编译内核

wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.79.tar.xz 
tar -xvf linux-5.15.79.tar.xz
sudo apt-get install -y crossbuild-essential-arm64 libncurses-dev pkg-config qemu-user qemu-user-static u-boot-qemu
make ARCH=arm64 defconfig
make -j4 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

step1.构造rootfs

构造rootfs同样可以在官方网站下载成品或利用工具现场烘焙,甚至可以选择DIY定制化rootfs。定制化制作根文件系统有两大神器:buildroot、yocto,但这里只介绍基于busybox的半手工化的方式rootfs系统。 Linux Containers支持下载各种架系统rootfs

# 制作rootfs(ubuntu/debian)
qemu-img create -f raw rootfs.img 128G
mkfs.ext4 rootfs.img
sudo mount -o loop rootfs.img /mnt
sudo qemu-debootstrap --arch=arm64 stable /mnt http://ftp.cn.debian.org/debian/
qemu-img convert -f raw -O qcow2 rootfs.img rootfs.qcow2
# 制作rootfs(centos)
qemu-img create -f raw rootfs.img 128G
mkfs.ext4 rootfs.img
sudo mount -o loop rootfs.img /mnt
wget https://uk.lxd.images.canonical.com/images/centos/7/arm64/default/20221206_07:11/rootfs.tar.xz
sudo tar -xvf ./rootfs.tar.xz -C /mnt
qemu-img convert -f raw -O qcow2 rootfs.img rootfs.qcow2

备忘: qemu启动包含以下4个阶段: qemu->uboot|bootloader->kernel->initramfs->hda.img,Linux中initramfs和initrd只是一个用于系统初始化的小型文件系统,通常用来加载一些第三方的驱动。为什么要通过这种方式来加载驱动呢?因为由于版权协议的关系,如果要把驱动放到kernel里,意味着你必须要开放源代码。但是有些时候,一些商业公司不想开源自己的驱动,那它就可以把驱动放到initramfs或者initrd。这样既不违背kernel版权协议,又达到不开源的目的。也就是说在正常的linux发行版本中,kernel初始化完成后,会先挂载initramfs/initrd,来加载其他驱动,并做一些初始化设置。然后才会挂载真真的根文件系统,通过一个switch_root来切换根文件系统,执行第二个init程序,加载各种用户程序。在这中间linux kernel跳了两下。

基于busybox的自定义根文件系统制作,包含3个步骤, 1.编译busybox 2.构造initramfs 3.真正的构造rootfs

编译busybox

wget https://busybox.net/downloads/busybox-1.36.0.tar.bz2
tar jxvf busybox-1.36.0.tar.bz2 && cd busybox-1.36.0
mkdir -p obj/arm64 && make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=obj/arm64 defconfig
make O=obj/arm64 menuconfig # 静态链接 Busybox Settings -> Build Options -> Build BusyBox as a static binary

提示:

  1. gunzip initramfs-busybox-arm64.cpio.gz
  2. cpio -i -d -H newc -F initramfs-busybox-arm64.cpio –no-absolute-filenames

构造initramfs

cd obj/arm64 && make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- install && cd ../..
mkdir -p initramfs/arm64 && cd initramfs/arm64 && mkdir -pv {bin,sbin,etc/init.d,proc,sys/kernel/debug,usr/{bin,sbin},dev/pts,mnt,tmp}
touch etc/init.d/rcS && chmod +x etc/init.d/rcS
cp -av ../../obj/arm64/_install/* ./
cat << EOF > init
#!/bin/sh
echo "init script for initrd/initramfs"
mount -t proc proc /proc
mount -t devpts devpts /dev/pts
mount -t sysfs sys /sys
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
mdev -s
exec /bin/sh

# mount /dev/vda /mnt
# umount -f /proc   
# umount -f /dev/pts    
# umount -f /sys/kernel/debug
# umount -f /sys
# umount -f /tmp
# exec switch_root -c /dev/console /mnt /init
EOF
chmod +x init 
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs-busybox-arm64.cpio.gz

构造rootfs

qemu-img create -f raw rootfs.img 128G
mkfs.ext4 rootfs.img
sudo mount -o loop ./rootfs.img /mnt
cd obj/arm64 && sudo make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- CONFIG_PREFIX=/mnt install && cd ../..
sudo mkdir -pv /mnt/{bin,sbin,etc/init.d,proc,sys/kernel/debug,usr/{bin,sbin},dev/pts,mnt,tmp}
sudo install ./examples/inittab /mnt/etc/inittab
sudo touch /mnt/etc/init.d/rcS && sudo chmod +x /mnt/etc/init.d/rcS
echo "nameserver 8.8.8.8" | sudo tee -a /mnt/etc/resolv.conf 
sudo mknod /mnt/dev/null c 1 3
sudo mknod /mnt/dev/console c 5 1
sudo mknod /mnt/dev/ram b 1 0

step2.内核模块

sudo make -j4 ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- modules_install INSTALL_MOD_PATH=/mnt
sudo make -j4 ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- headers_install INSTALL_HDR_PATH=/mnt

2.构建sparc64虚拟机


最后我们使用光盘安装的方式,在Qemu上创建一个支持sparc架构netbsd系统的虚拟机

qemu-img create -f raw netbsd.img 128G
sudo apt-get install openbios-sparc
wget https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.3/images/NetBSD-9.3-sparc64.iso
sudo qemu-system-sparc64 \
        --nographic \
        -boot once=c \ {>>a:floppy, c:hard disk, d:CD-ROM, n:Etherboot, hard disk is the default<<}
        -cdrom ./NetBSD-9.3-sparc64.iso \
        -hda ./netbsd.img #(1)!

3.Qemu虚拟机调试


qemu向外界暴露的唯一接口就是qemu monitor,通过monitor可以查询虚拟机信息,控制虚拟机。qemu monitor支持两种协议类型:qmp(QEMU Machine Protocol)和hmp(Human Machine Protocol),通过monitor能够完成查询虚拟机内部状态,进行设备热插拔,虚拟机的迁移/备份/快照等功能。

这里强调一下,一般情况只能在虚拟机未启动时使用qemu-img snapshot命令对虚拟机进行快照的创建应用删除,当然如果在虚拟机启动时增加qemu参数snapshot,可利用qemu console对运行中的虚拟机进行快照操作。

qemu-img snapshot -c snapshot-name rootfs.qcow2  // create snapshot  
qemu-img snapshot -l rootfs.qcow2                // list all snapshots  
qemu-img snapshot -a snapshot-name rootfs.qcow2  // apply snapshot 

hmp接口

  1. ++ctrl+a++ ++c++ 切换qemu console和machine console
  2. 虚拟机启动时增加参数monitor,使用telnet登录qemu console
# monitor可选:tcp套接字,unix套接字

qemu-system-x86_64 \
        -m 2048 \
        -hda ./rootfs.img \
        -enable-kvm \
        -snapshot \
        -monitor [tcp:localhost:1234|unix:/tmp/qmp-test],server,nowait  #(1)!
telnet localhost 1234 | nc -U /tmp/qmp-test   

在虚拟机启动时增加qemu参数snapshot,利用qemu console可[保存|加载|删除]虚拟机快照:[savevm name|loadvm name|info snapshots|delvm name]

qmp接口

qemu-system-x86_64 \
        -m 2048 \
        -hda ./rootfs.img \
        -enable-kvm \
        -snapshot \
        -qmp tcp:localhost:1234,server,nowait
telnet localhost 1234 
  1. QMP进入命令模式 { “execute”: “qmp_capabilities” }
  2. 查询当前版本QMP支持的命令 { “execute”: “query-commands” }

4.Todo


  • 编译uboot,qemu加载uboot驱动加载initramfs
  • 内核代码优化关闭,gdb调试
  • 丰富虚拟机调试小节内容

5.参考文献

  1. mux-chardev
comments powered by Disqus