QEMU搭建Linux實驗環境


嵌入式開發離不開硬件設備比如:開發板、外設等,但是如果只是想研究Linux內核,想學習Linux內核的架構/工作模式,修改一些代碼,重新編譯並燒寫到開發板中進行驗證,這樣未必有些復雜。然而qemu的使用可以避免頻繁在開發板上燒寫版本,如果進行與外設無關,僅僅是內核方面的調試,qemu模擬ARM開發環境完全可以完美地勝任。仿真能解決以下痛點:

  • 真實單板難以獲取時,可以快速上板,無需輪候
  • 源碼級的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被拆分為了多個軟件包。不同軟件包有不同的功能,比如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-6.0.0.tar.xz
tar xvJf qemu-6.0.0.tar.xz
cd qemu-6.0.0
./configure
make

gcc交叉編譯工具鏈安裝

我們要在x86_64 Ubuntu系統下編譯arm64鏡像,因此需要交叉編譯工具鏈。
sudo apt install gcc-aarch64-linux-gn

其他工具

初裝的Ubuntu缺少很多編譯工具,可不急於全部安裝以下工具,如果make menuconfig在哪出錯了,再安裝對應軟件包即可。

make: sudo apt install make
gcc: sudo apt install gcc
ncurses: sudo apt install libncurses-dev
flex: sudo apt install flex
bison: sudo apt install bison
openssl : sudo apt install libssl-dev

內核編譯

下載並解壓kernel源碼

下載最新的kernel源碼,kernel官網:https://www.kernel.org

tar -xf linux-5.12xx.tar

配置環境變量

要在x86_64的宿主機編譯arm64的鏡像,需要arm64的gcc工具。每次重新打開shell都需要配置。

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

查看環境變量是否設置成功

export

內核配置

在頂層目錄生成arm64的默認配置.config,其實是把arch/arm64/configs/defconfig復制到頂層目錄。

make defconfig

配置其他特性開關

make menuconfig

  1. 內核打開initramfs支持
    ram fs是將最小文件系統直接編譯進內核鏡像中。

General setup --->
----> [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
---->() Initramfs source file(s)

  1. 內核打開ramdisk支持
    ram disk是在內存中模擬磁盤,使用ramdisk比Initramfs靈活一些,不需要每次都去編譯內核。不過在qemu中,都支持通過-initrd指定文件鏡像。如果RAM disk size這個大小和你做的ramdisk不匹配,則啟動時仍然會出現 kernel panic內核恐慌,提示ramdisk格式不正確,掛載不上ramdisk。也可通過設置啟動參數修改ramdisk大小。“ramdisk_size=65536”

Device Drivers --->
[] Block devices --->
<
> RAM block device support
(4096) 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

  1. 核打開ext4支持
    不一定非得是ext4,這里勾選ext4僅僅是因為我們要制作的ramdisk是ext4格式

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

編譯鏡像

編譯,並且10個job運行,加快編譯速度。生成的內核鏡像在:arch/arm64/boot/Image

make -j 10

啟動裸內核

qemu相關使用

qemu有很多幫助信息。

  • qemu-system-aarch64 -h // 查看全部幫助信息
  • qemu-system-aarch64 -machine help //查看支持的machine
  • qemu-system-aarch64 -cpu help //查看machine支持的cpu類型
  • When using -nographic, press 'ctrl-a h' to get some help

啟動裸內核

執行如下命令嘗試啟動內核,如果一切順利,可看到Linux的啟動log,但是大概率會運行到根文件系統初始化時掛死。哈哈,不過到這里證明我們成功一半了。

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

-M 指定開發板
-m 指定內存大小
-kernel 指定內核文件
-dtb 指定dtb文件
-nographic 指定不需要圖形界面
-append 指定啟動參數

$ ./build/qemu-system-aarch64 -machine 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)

如果內核沒啟動,反而有如下錯誤打印,大概率是qemu版本問題。可考慮下載qemu源碼,本地編譯qemu二進制。

rom: requested regions overlap (rom bootloader. free=0x0000000041e99200, addr=0x0000000040000000)
qemu-system-aarch64: rom check and register reset failed

制作根文件系統

制作一個簡易的根文件系統,該文件系統包含的功能極其簡陋,僅為了驗證qemu啟動Linux內核后掛載跟文件系統的過程。以后會進一步完善該文件系統。

下載編譯busybox

從官網下載最新的busybox源碼,https://busybox.net/downloads/

tar -xf busybox-1.29.3.tar.bz2

busybox的編譯和Linux kernel類似都是通過kconfig管理

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make menuconfig

進入menuconfig后,配置為靜態編譯

--- Build Options
[*] Build static binary (no shared libs)

make
make install

編譯完成之后,在busybox根目錄下會有_install目錄,該目錄是編譯好的一些命令集合。

根文件系統格式

創建initrd文件系統EXT4格式

if= / of=,分別指定輸入輸出文件。/dev/zero是輸出一直為零的設備。下面命令創建內容為空的文件rootfs.ext4。

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

將文件格式轉為ext4文件系統

mkfs.ext4 rootfs.ext4

將busybox編譯生成的_install目錄下的文件全部拷貝到initrd

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

至此,簡易版根文件系統就制作完成,該根文件系統只含有最基本的功能,一些其他功能在以后的操作中會進行添加。

指定ramdisk文件,並啟動kernel

qemu-system-aarch64 -M virt -cpu cortex-a57 -m 1G -nographic -kernel Image -append "root=/dev/ram0" -initrd ~/rootfs.ext4

制作initramfs

cd busybox/_install/ rootfs
cd rootfs
find . | cpio -o -H newc > rootfs.cpio
gzip -c rootfs1.cpio > rootfs.cpio.gz
qemu-system-aarch64 -M virt -cpu cortex-a57 -m 1G -nographic -kernel Image -initrd ~/rootfs.cpio.gz -append "rdinit=/linuxrc"

initramfs與initrd區別

Linux內核只認cpio格式的initramfs文件包(因為unpack_to_rootfs只能解析cpio格式文件),非cpio格式的initramfs文件包將被系統拋棄,而initrd可以是cpio包也可以是傳統的鏡像(image)文件,實際使用中initrd都是傳統鏡像文件如ext4格式。
使用initramfs,命令行參數將不需要"initrd="和"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

常見問題

串口打印

在dev目錄下創建如下設備節點,解決找不到tty的問題,此處需要管理員權限。

mkdir -p dev
mknod dev/console c 5 1
mknod dev/null c 1 3

找不到rcS文件

can't run '/etc/init.d/rcS': No such file or directory

kernel啟動參數

不知怎的,最新版本的Linux沒有這個文檔了,看看之前版本的吧

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

常用的主要有

root=    [KNL] Root filesystem
ro      [KNL] Mount root device read-only on boot
rw     [KNL] Mount root device read-write on boot
rdinit=   [KNL] Format: <full_path>
      Run specified binary instead of /init from the ramdisk, used for early userspace startup. See initrd.
loglevel=   All Kernel Messages with a loglevel smaller than the console loglevel will be printed to the console. It can also be changed with klogd or other programs. Theloglevels are defined as follows:

ramdisk_size= [RAM] Sizes of RAM disks in kilobytes

gdb調試

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

sudo apt install gdb-multiarch

確保編譯的內核包含調試信息

Kernel hacking --->
  Compile-time checks and compiler options -- >
    []compile the kernel with debug info
    [
] Provide GDB scripts for kernel debugging

qemu命令需要添加以下選項

-S:表示qemu虛擬機會凍結CPU,直到遠程的gdb輸入相應控制命令

-s:表示在1234端口接受gdb的調試連接

在另一終端輸入命令

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")))

啟動打印時間戳

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM