QEMU搭建Linux实验环境

前言

嵌入式开发离不开硬件设备:开发板、外设等,但如果只是想研究Linux内核的架构/工作模式,修改一些代码然后烧写到开发板中验证,这样未必有些复杂。然而qemu可以避免频繁在开发板上烧写版本,如果仅仅是内核方面的调试,qemu完全可以完美地胜任。仿真能解决以下痛点:

  • 真实单板难以获取时,可以快速上板,无需轮候
  • 源码级的GDB(这真是一个超级强大的功能,有了它,开发效率会直线上升)
  • 快速单元测试、开发者测试
  • 业务代码无需打桩(桩还是会有条件存在的,但是转移到了qemu侧)

实验目标

使用qemu运行自己编译的Linux系统,并能够进行简单调试。本文不对qemu做过多分析,着重于如何快速搭建环境。

环境准备

qemu可运行在多个平台上,如Linux、windows、mac等。通常嵌入式开发是基于开源Linux的,因此我们也基于Linux环境开展实验。

  1. windows下安装VMware,VMware创建Ubuntu 20.04 LTS版本的虚拟机。Ubuntu版本下载:https://ubuntu.com/download/desktop
  2. 在Ubuntu安装qemu软件
  3. 用qemu模拟运行arm64 Linux系统

qemu安装

qemu安装方式有两种:Linux软件包安装、源码编译安装。

软件包安装

Ubuntu的软件包安装:qemu软件包越来越大,因此被拆分为了多个软件包。不同软件包有不同的功能,比如qemu-system-ARCH提供全系统模拟(ARCH替换为arm/mips等架构名),qemu-utils提供了一些工具。

sudo apt install qemu-system-arm

查看版本号,如果版本号太老,考虑使用源码编译

lv@ubuntu:~$ qemu-system-aarch64 --version
qemu emulator version 4.2.1 (Debian 1:4.2-3ubuntu6.16)
Copyright (c) 2003-2019 Fabrice Bellard and the qemu Project developers

源码安装

qemu官网给出了安装步骤:https://www.qemu.org/download/
qemu版本号会持续更新。make之后,编译的可执行二进制文件在./build目录下

wget https://download.qemu.org/qemu-7.0.0-rc0.tar.xz
tar xvJf qemu-7.0.0-rc0.tar.xz
cd qemu-7.0.0-rc0
./configure
make

内核编译步骤

  1. 下载并解压kernel源码

下载最新的kernel源码,kernel官网:https://www.kernel.org

tar -xf linux-5.12xx.tar

  1. 安装编译工具
  • gcc交叉编译工具链安装

我们要在x86_64 Ubuntu系统下编译arm64镜像,因此需要交叉编译工具链。

sudo apt install gcc-aarch64-linux-gnu

  • 依赖工具

初装的Ubuntu缺少很多编译工具,可不急于全部安装以下工具,如果make menuconfig在哪出错了,再安装对应软件包即可。

sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison

  1. 配置环境变量

要在x86_64的宿主机编译arm64的镜像,需要arm64的gcc工具。每次重新打开shell都需要配置。

export ARCH=arm64
export CROSS_COMPILE=<path>/aarch64-linux-gnu-

查看环境变量是否设置成功:export

  1. 生成.config文件

在顶层目录生成arm64的默认配置.config,其实是把arch/arm64/configs/defconfig复制到kernel源码顶层目录。

make defconfig

  1. 在.config的基础上配置其他特性开关

make menuconfig

  1. 编译镜像,并且10个job运行,加快编译速度。生成的内核镜像在:arch/arm64/boot/Image

make -j 10

启动裸内核

qemu相关使用

  1. 可执行程序

在./build目录可以找到下qemu-system-aarch64可执行文件,同时目录下面还有其他cpu架构的可执行程序,命名都是qemu-system-作为前缀。

  1. 查看qemu帮助信息
qemu-system-aarch64 -h  // 查看全部帮助信息
qemu-system-aarch64 -machine help //查看支持的machine
qemu-system-aarch64 -cpu help //查看machine支持的cpu类型
During emulation, press 'ctrl-a h' to get some help

启动裸内核

执行如下命令尝试启动内核,如果一切顺利,可看到Linux的启动log,但是大概率会运行到根文件系统初始化时挂死。哈哈,不过到这里证明我们成功一半了。

qemu-system-aarch64 -M virt -cpu cortex-a57 -m 1024 -nographic -kernel path-to-Image-file

-M 指定开发板
-m 指定内存ram大小,单位MB
-smp 指定core num
-nographic 指定不需要图形界面
-kernel 指定内核文件
-dtb 指定dtb文件
-hda 硬盘0,Use file as hard disk
-hdb 硬盘1
-append 指定启动参数command line

$ ./build/qemu-system-aarch64 -M virt -cpu cortex-a57 -m 1024 -nographic -kernel /home/xxxx/linux/linux-5.12.4/arch/arm64/boot/Image
[ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x411fd070]
[ 0.000000] Linux version 5.12.4 (xxxx) (aarch64-linux-gnu-gcc (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0, GNU ld (GNU Binutils for Ubuntu) 2.30) #1 SMP PREEMPT Mon May 17 14:36:21 CST 2021
[ 0.000000] Machine model: linux,dummy-virt
[ 0.000000] efi: UEFI not found.

....

[ 1.080779] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
[ 1.081468] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.12.4 #1
[ 1.081886] Hardware name: linux,dummy-virt (DT)

启动内核

上文提到裸内核在初始化文件时出错而停止运行,接下来制作一个根文件系统并传递给内核,启动一个完整的内核程序。

  1. 文件系统和内核是完全独立的两个部分,文件是用户与内核交互的主要工具。
  2. 根文件系统是内核启动时所mount的第一个文件系统,是加载其它文件系统的”根“。
  3. 一套linux体系,只有内核本身是不能工作的,必须要rootfs(etc目录下的配置文件、/bin /sbin等目录下的shell命令,还有/lib目录下的库文件等···)相配合才能工作。

文件系统配置

打开ext4支持
不一定非得是ext4,这里勾选ext4仅仅是因为我们要制作的文件系统是ext4格式

File systems
 ----> {*} The Extended 4 (ext4) filesystem

制作根文件系统

制作一个简易的根文件系统,该文件系统包含的功能极其简陋,仅为了验证qemu启动Linux内核后挂载根文件系统的过程。以后会进一步完善该文件系统。

下载编译busybox

  1. 从官网下载最新的busybox源码,https://busybox.net/downloads/

tar -xf busybox-1.29.3.tar.bz2

  1. busybox的编译和Linux kernel类似都是通过kconfig管理
export ARCH=arm64
export CROSS_COMPILE=<path>/aarch64-linux-gnu-
make menuconfig
  1. 进入menuconfig后,配置为静态编译
setting
 ----> Build Options
  ----> [*] Build static binary (no shared libs)
  1. 编译
    编译完成之后,在busybox根目录下会有_install目录,该目录是编译好的一些命令集合。
make
make install

创建文件系统EXT4格式

  1. 创建空文件

if= / of=,分别指定输入输出文件。/dev/zero是输出一直为零的设备。下面命令创建内容为空的文件rootfs.ext4。

dd if=/dev/zero of=rootfs.ext4 bs=1M count=32

  1. 将文件格式转为ext4文件系统

mkfs.ext4 rootfs.ext4

  1. 拷贝文件

将busybox编译生成的_install目录下的文件全部拷贝到initrd。至此,简易版根文件系统就制作完成,该根文件系统只含有最基本的功能,一些其他功能在以后的操作中会进行添加。

mkdir mnt
sudo mount rootfs.ext4 mnt/
cd mnt
sudo cp -rf busybox-xxx/_install/* .
cd ../
sudo umount mnt
rm -rf mnt

非嵌入式启动

qemu-system-aarch64 -M virt -cpu cortex-a57 -m 1G -nographic -kernel path-to-Image-file -append "root=/dev/vda" -hda ~/rootfs.ext4

嵌入式启动方式

文件系统配置

  1. 内核打开initramfs支持
    ramfs是将文件系统直接编译进内核镜像中。
General setup
 ----> [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
  1. 内核打开ramdisk支持
    ramdisk是在内存中模拟磁盘,使用ramdisk比Initramfs灵活一些,不需要每次都去编译内核。不过在qemu中,都支持通过-initrd指定文件镜像。注意ramdisk大小一定要足够大
 Device Drivers
  ----> [*] Block devices
   ----> <*> RAM block device support
    ----> (65535) Default RAM disk size (kbytes)

制作initramfs

cp busybox/_install/  rootfs
cd rootfs
find . | cpio -o -H newc > rootfs.cpio
gzip -c rootfs1.cpio > rootfs.cpio.gz

启动命令

  1. initramfs启动
qemu-system-aarch64 -M virt -cpu cortex-a57 -m 1G -nographic -kernel path-to-Image-file -initrd ~/rootfs.cpio.gz -append "rdinit=/linuxrc"
  1. ramdisk启动
qemu-system-aarch64 -M virt -cpu cortex-a57 -m 1G -nographic -kernel path-to-Image-file -append "root=/dev/ram0 ramdisk_size=65535" -initrd ~/rootfs.ext4

常见问题

initramfs与initrd区别

Linux内核只认cpio格式的initramfs文件(因为unpack_to_rootfs只能解析cpio格式文件),非cpio格式的initramfs文件包将被系统抛弃,而initrd可以是cpio包也可以是传统的镜像文件,实际使用中initrd都是传统镜像文件如ext4格式。
使用initramfs,命令行参数将不需要"root="命令。
如下,kernel会首先尝试解析initramfs,然后尝试initrd(ramdisk)。

[ 0.548161] Trying to unpack rootfs image as initramfs...
[ 0.550507] rootfs image is not initramfs (invalid magic at start of compressed archive); looks like an initrd

ramdisk大小

如果RAM disk size这个大小和你做的ramdisk不匹配,则启动时仍然会出现 kernel panic内核恐慌,提示ramdisk格式不正确,挂载不上ramdisk。也可通过设置启动参数修改ramdisk大小。“ramdisk_size=65536”

 Device Drivers
  ----> [*] Block devices
   ----> <*> RAM block device support
    ----> (65535) Default RAM disk size (kbytes)

挂载失败打印:

RAMDISK: ext2 filesystem found at block 0
RAMDISK: image too big! (32768KiB/4096KiB)
VFS: Cannot open root device "(null)" or unknown-block(0,0): error -6

串口打印, 不能打开tty文件

文件系统添加dev目录

sudo mkdir dev

找不到rcS文件

can't run '/etc/init.d/rcS': No such file or directory,
/etc/init.d/rcS allows you to run additional programs at boot time,
在文件系统中新建rcs解决此问题

sudo mkdir -p etc/init.d/
cd etc/init.d/
sudo chmod 777 rcS

kernel启动参数

不知怎的,最新版本的Linux没有这个文档了,看看之前版本的吧

https://elixir.bootlin.com/linux/v2.6.37.5/source/Documentation/kernel-parameters.txt

常用的主要有

  • initrd [BOOT] Specify the location of the initial ramdisk
  • loglevel
  • ramdisk_size

gdb调试

  1. arm不能使用普通的gdb,要用gdb-multiarch

sudo apt install gdb-multiarch

  1. 确保编译的内核包含调试信息
Kernel hacking
 ----> Compile-time checks and compiler options
  ----> [*] compile the kernel with debug info
  ----> [*] Provide GDB scripts for kernel debugging
  1. qemu命令需要添加以下选项
qemu-system-aarch64  -S -s

-S:表示qemu虚拟机会冻结CPU,直到远程的gdb输入相应控制命令
-s:表示在1234端口接受gdb的调试连接

  1. 在另一终端输入命令

gdb-multiarch --tui
(gdb) file vmlinux //加载kernel符号表
(gdb) target remote localhost:1234 //通过1234端口连接qemu平台
(gdb) b start_kernel //在内核start_kernel设置断点
(gdb) c //continue 运行

Linux kernel使用了大量的static函数,配合-O2编译选项,这些static函数被自动inline,这样可以达到优异的速度优化效果,但是也让gdb的时候带来一些小困扰。

这个问题似乎没有更好的解决办法,因为-O2是不能关闭的,因此一般都是在函数名前加上这样一个定义来临时规避这个问题:

__attribute__((optimize("O0")))

gdb调试,在start_kernel断点停不住

关闭kaslr

qemu-system-aarch64 -append nokaslr

启动打印时间戳

Kernel hacking
 ----> printk and dmesg options
 ----> [*] Show timing information on printk

earlyprintk

因为printk依赖于console,而console驱动此时还未加载,这个阶段printk的内存都被存储到log_buf中,待console驱动加载后才会一次性地打印出来。在console被初始化之前,earlyprintk将printk的所有内容直接输出到串口,console初始化完毕后,由console驱动接管printk的内容。打开earlyprintk有以下两种方式:
1、打开config 宏
2、增加启动参数:bootargs还要增加earlyprintk

buildroot

可借助buildroot,生成kernel、rootfs等

riscv linux启动

qemu启动命令和arm64一样,只不过需要编译出riscv的linux和busybox镜像。
https://risc-v-getting-started-guide.readthedocs.io/en/latest/linux-qemu.html

~/workspace/qemu/qemu-7.0.0-rc0/build/qemu-system-riscv64 \
  -M virt \
  -smp 1 \
  -m 1024 \
  -nographic \
  -kernel ~/workspace/buildroot/buildroot-2022.02.3/output/images/Image \
  -initrd ~/workspace/buildroot/buildroot-2022.02.3/output/images/rootfs.cpio \
  -append "root=/dev/ram0 ramdisk_size=65535 nokaslr" 
posted @ 2021-06-20 18:39  zephyr~  阅读(8429)  评论(0编辑  收藏  举报