一步步教你:如何用Qemu來模擬ARM系統

sewain發表於2020-12-29

這是道哥的第011篇原創

前言

前一段時間因為工作需要,我對ARM模擬器進行了一番調研。調研目的是:由於專案參與人員比較多,如果人手一塊ARM開發板,資源比較緊張,希望能夠用模擬器來代替。

在調研期間,花費了很多時間去查資料、測試驗證。在實際驗證的時候,發現一個現象:很多文章雖然給出了或簡單、或詳細的操作步驟,但是大部分都沒有寫清楚操作的背景、各個軟體的版本,這就導致需要把整個文章看完了、把所有的操作步驟都做了一遍,才明白作者想表達的是什麼意思,操作的目的是什麼

我覺得,任何一篇文章,首先要讓讀者知道為什麼要讀這篇文章,或者說讀了這篇文章能夠有什麼收穫

如果是操作性比較強的文章,那麼就有必要交代清楚工作平臺的背景是什麼,要達到的目的是什麼,總體步驟是怎麼樣的。只有這樣,閱讀文章的人在心中首先建立一個巨集觀的框架,在理解框架的基礎上,再去實際操作,這樣的話就更容易理解。

當然了,每個人的學習和閱讀習慣都不一樣,上面只是我個人的感受,或者說我喜歡這樣比較有條理的文章,這樣才不至於迷茫。

回到Qemu的主題上來,這篇文章主要是把調研的結果進行梳理、彙總,包括如下內容:

為什麼需要ARM模擬系統?
Qemu是什麼?
Qemu 能做什麼?或者說適合做什麼?
在 Ubuntu16.04 系統中,利用 Qemu 搭建一個ARM虛擬機器操作步驟是什麼?
編寫一個HelloWorld程式,放到虛擬機器中執行。

為什麼需要ARM模擬系統

ARM平臺的軟體開發工作,可以劃分為2類:

應用程式的開發
系統開發(核心、檔案系統、驅動程式)

應用程式的開發

我們在開發嵌入式專案的時候,一般都是先在x86平臺上把大部分的功能開發完成,然後再交叉編譯,得到在ARM平臺的可執行程式或者庫檔案。再通過scp指令或者NFS遠端掛載的方式,把這些檔案複製到ARM板子上之後執行。

一般而言,應用程式就是利用硬體產品的各種資源、外設,來完成特定的功能,比如:資料採集、控制外部裝置、網路傳輸等等。主要的特徵就是與外部的各種裝置進行互動。

系統開發(BSP)

系統開發的最終目的是:為應用程式的執行準備一個基本的執行環境,內容包括:系統載入程式bootloader,核心kernel,檔案系統rootfs,系統中所有裝置的驅動程式。在實際的專案開發中,系統開發難度更大一些,一旦開發完成,對於一塊板子來說基本上不會輕易變動,程式碼的使用生命週期更長。

以上這兩種分類,主要是從開發工作的內容角度來進行劃分的。可以看出:

應用程式開發:靈活性更大、需求變動會更多(產品經理或專案經理經常給你改需求)。
系統軟體開發:需求更穩定、很多程式碼都是官方提供或者開源的,工作內容就是進行定製、裁剪。

對於系統軟體開發來說,如果每次編譯出一個bootloader、或者kernel,都上一個ARM開發板進行驗證,的確比較麻煩。如果能有一個ARM模擬系統,直接在x86上進行模擬,工作效率就會提高很多

Qemu是什麼?

Qemu是一個開源的託管虛擬機器,通過純軟體來實現虛擬化模擬器,幾乎可以模擬任何硬體裝置。比如:Qemu可以模擬出一個ARM系統中的:CPU、記憶體、IO裝置等,然後在這個模擬層之上,可以跑一臺ARM虛擬機器,這個ARM虛擬機器認為自己在和硬體進行打交道,但實際上這些硬體都是Qemu模擬出來的

正因為Qemu是純軟體實現的,所有的指令都要經過它的轉換,所以效能非常低。所以在生產環境中,大多數的做法都是配合KVM來完成虛擬化工作,因為KVM是硬體輔助的虛擬化技術,主要負責比較繁瑣的CPU和記憶體虛擬化,而Qemu則負責I/O虛擬化,兩者合作各自發揮自身的優勢,相得益彰。這部分不是重點,就不具體深入介紹了。

Qemu的兩種模式

Qemu有兩種執行模式

  1. 使用者模式(User mode):利用動態程式碼翻譯機制來執行不同主機架構的程式碼,例如:在x86平臺上模擬執行ARM程式碼,也就是說:我們寫一條ARM指令,傳入整個模擬器中,模擬器會把整個指令翻譯成x86平臺的指令,然後在x86的CPU中執行。

  1. 系統模式(System mode):模擬整個電腦系統,利用其它VMM(Xen, KVM)來使用硬體提供的虛擬化支援,建立接近於主機效能的全功能虛擬機器。

Qemu 能做什麼?或者說適合做什麼?

因為Qemu是使用純軟體模擬的,它的強項是模擬那些不涉及到外部的具體硬體裝置的場景,比如:

想學習如何定製bootloader;
想在Arm系統中進行檔案系統的裁剪,學習檔案系統的掛載過程;
想體驗一下如何配置、裁剪linux kernel;
想學習Linux系統中的裝置樹;
...

以上這些場景中,都非常適合使用Qemu來模擬ARM系統。

在 Ubuntu16.04 系統中,利用 Qemu 搭建一個ARM虛擬機器

使用Qemu虛擬機器的幾種選擇

利用Qemu來執行ARM虛擬機器,你有2個選擇

  1. 簡單方式:直接下載別人編譯好的映像檔案(包含了核心,根檔案系統),直接執行即可。


    缺點是:別人編譯好的也許不適合你的需求,沒法定製。
  2. 複雜方式:自己下載核心程式碼、根檔案系統程式碼(例如:busybox),然後進行編譯。


    優點是:可以按照自己的實際需求,對核心、根檔案系統機型裁剪。

在第2種複雜模式中,又可以有2個選擇

2-1. 核心程式碼、根檔案系統程式碼全部自己手動編譯,最後把這些編譯結果手動組織在一個資料夾中,形成自己的根目錄;
2-2. 利用 buildroot 整個框架,只需要手動進行配置(比如:交叉編譯器在本機上的位置、輸出路徑、系統的裁剪),然後就可以一鍵編譯出一個完整的系統,可以直接燒寫到機器!

以上這幾種操作方式的選擇,可以根據自己的實際需要來選擇。如果對構建系統的整個流程已經非常熟悉了,就利用buildroot工具;如果是想更徹底的學習製作一個系統,那就手動一步一步的實際編譯、操作一遍,多練幾次,你就變成大牛了。

下面,我們就按照2-2的方式,進行實際操作一遍。所有的指令部分,我都直接貼程式碼,不用截圖,這樣方便複製。

測試平臺

我的工作電腦是Win10,通過VirtualBox安裝了Ubuntu16.04虛擬機器,64位系統

下面的操作在Ubuntu16.04虛擬機器中可以順利編譯,當然,一些基本的工具(例如:build-essential, make等基礎工具軟體這裡就不詳述了)。

安裝交叉編譯器

交叉編譯器的作用就不需要詳細解釋了,因為我們是在x86平臺上進行編譯,而執行的平臺是ARM系統,這2個平臺的指令集不一樣,所以需要交叉編譯得到ARM系統上可以執行的程式。

sudo apt-get install gcc-arm-linux-gnueabi 

驗證安裝結果

dpkg -l gcc-arm-linux-gnueabi 

顯示如下:

有些文章建議自己下載交叉編譯器,然後手動設定環境變數。我實際操作了一下,手動下載的交叉編譯工具鏈在編譯核心的時候報錯,所以還是建議直接用apt-get直接安裝

編譯核心kernel

核心kernel的作用也是不言而喻的,就相當於我們的Windows作業系統,沒有這個作業系統,硬體就是一堆廢鐵。當系統啟動的時候,會把核心載入到記憶體中,然後從核心的入口地址開始執行。

  1. 下載核心
    版本:linux-4.14.212.tar。
    在文末,我會列出所有的軟體包下載地址。

  2. 使用現成的vexpress開發板子的config檔案

make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm vexpress_defconfig 

這個操作,會把vexpress_defconfig作為配置檔案儲存為.config,下面在編譯核心時就根據這個config中的配置進行編譯。

如果需要對核心進行裁剪,執行:

make menuconfig

根據自己的實際需要,對核心進行定製。比如:可以配置網路和NFS,在系統啟動的時候就自動掛載宿主機中的某個目錄。

  1. 編譯核心
make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm

編譯得到核心檔案arch/arm/boot/zImage,Qemu啟動時需要指定使用這個映像檔案。

製作根檔案系統

核心在啟動之後、執行到最後步驟時,需要掛載根檔案系統,然後執行檔案系統中指定的執行程式,例如:/etc/rc.local。

如果沒有跟檔案系統,那麼核心在執行到最後就提示:panic...

  1. 下載busybox

    版本:busybox-1.20.2.tar.bz2。

  2. 建立rootfs根目錄

mkdir -p rootfs/{dev,etc/init.d,lib} 
  1. 把busybox-1.20.2中的檔案複製到rootfs根目錄下,主要是一些基本的命令
cp busybox-1.20.2/_install/* -r rootfs/ 
  1. 把交叉編譯工具鏈中的庫檔案複製到rootfs根目錄的lib資料夾下
sudo cp -P /usr/arm-linux-gnueabi/lib/* rootfs/lib/ 
  1. 製作根檔案系統映象
    根檔案系統映象就相當於一個硬碟,就是把上面rootfs根目錄中的所有檔案複製到這個硬碟中。

(1) 生成512M大小的磁碟映象

qemu-img create -f raw disk.img 512M 

(2) 把磁碟映象格式化成ext4檔案系統

mkfs -t ext4 ./disk.img 

(3) 將rootfs根目錄中的所有檔案複製到磁碟映象中
操作步驟是:建立掛載點-掛載-複製檔案-解除安裝。

mkdir tmpfs 
sudo mount -o loop ./disk.img tmpfs/  
sudo cp -r rootfs/* tmpfs/
sudo umount tmpfs 

(4) 使用file指令檢查一下

file disk.img

利用Qemu啟動ARM虛擬機器

1.啟動虛擬機器

這個命令有點長,測試時建議直接複製、貼上。

qemu-system-arm -M vexpress-a9 -m 512M -kernel ./linux-4.14.212/arch/arm/boot/zImage -dtb  ./linux-4.14.212/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -append "root=/dev/mmcblk0 rw console=ttyAMA0" -sd disk.img

2.停止虛擬機器
在Ubuntu另一個終端視窗中,通過killall指令來停止。

killall qemu-system-arm

當然,也可以用ps指令找到qemu-system-arm的程式號,然後通過kill -9來停止虛擬機器。

測試HelloWorld應用程式

  1. 在Ubuntu任意一個目錄,編寫HelloWorld可執行程式hello.c:
#include <stdio.h> 
int main() 
{     
    printf("HelloWorld! \n");
    return 0; 
} 
  1. 交叉編譯hello.c,得到可執行程式hello:
arm-linux-gnueabi-gcc hello.c -o hello 

通過file指令,檢視一下hello程式:

file hello

  1. 通過kill命令停止虛擬機器。

  2. 把hello可執行程式複製到磁碟映象disk.img中
    操作步驟是:掛載-複製檔案-解除安裝。

sudo mount -o loop ./disk.img tmpfs/  
cp hello tmpfs/ 
sudo umount tmpfs 
  1. 執行hello程式
    再次啟動虛擬機器,此時可以在根目錄下面看到hello檔案,直接執行即可看到輸出結果。

總結

在以上的操作步驟中,我們把一個ARM系統在啟動應用程式之前,所需要的程式都手動編譯、操作了一遍。看一遍很容易就明白,親手操作一遍印象會更深刻。

這裡的操作過程有些還需要繼續深入,比如:在系統啟動之後,自動掛載宿主機(Ubuntu系統)中的某個資料夾,這樣就可以把hello等可執行程式複製到掛載目錄中,然後在ARM系統中直接執行了,而不用再執行下面在一連串的操作(停止虛擬機器-掛載磁碟映象-複製檔案-解除安裝-啟動虛擬機器)。

最後,希望這篇總結能給你帶來小小的收穫和提升!

軟體下載地址

  1. linux-4.14.212.tar.xz
    連結:https://pan.baidu.com/s/1d8RxjMkYQhPtbZgiybD8Gw
    提取碼:b6ft

  2. busybox-1.20.2.tar.bz2
    連結:https://pan.baidu.com/s/1oPeH7juEWuFR6y1Qpna_BA
    提取碼:9kh6


【原創宣告】

> 作者:道哥(公眾號: IOT物聯網小鎮)
> 知乎:道哥
> B站:道哥分享
> 掘金:道哥分享
> CSDN:道哥分享

如果覺得文章不錯,請轉發、分享給您的朋友。


我會把十多年嵌入式開發中的專案實戰經驗進行總結、分享,相信不會讓你失望的!

長按下圖二維碼關注,每篇文章都有乾貨。


轉載:歡迎轉載,但未經作者同意,必須保留此段宣告,必須在文章中給出原文連線。




推薦閱讀

[1] 原來gdb的底層除錯原理這麼簡單
[2] 生產者和消費者模式中的雙緩衝技術
[3] 深入LUA指令碼語言,讓你徹底明白除錯原理
[4] 一步步分析-如何用C實現物件導向程式設計

相關文章