不想錯過我的推送,記得右上角-查看公眾號-設為星標,摘下星星送給我!
QEMU,搞嵌入式開發的一定不陌生,最近各大群裡都討論瘋了,說它是Linux利器一點也不誇張。它是一款知名的而且開源的模擬器(官網:https://www.qemu.org/),它能在 X86 PC 上運行(其實它也可以在你的 Arm 開發板上運行,我們今天先不討論這種場景),能夠模擬 Arm、MIPS、RISC-V 等各種 CPU 和開發板,以及 網卡、音效卡、鍵盤、sdcard、emmc、usb等各種外設。你可以把它當作一塊召之即來的開發板,在上面運行 U-Boot、Linux Kernel、甚至 Ubuntu 等各種軟體和作業系統。
有時候我們想體驗一下 mainline 上最新的 U-Boot 或者 Linux Kernel,可是卻發現手邊沒有合適的板子,或者手邊的板子搭載的 U-Boot 和 Linux Kernel 版本都比較低,這時候 QEMU 可以幫你迅速實現這一願望。
我是Andy, Linux內核開發者。現從事Arm晶片上Linux 系統移植和優化工作,並向 u-boot 和 linux kernel開源社區貢獻了大量補丁。從 Cortex-M3 到 Arm64,RT-Thread 到 Linux kernel, hack 不止,其樂無窮。受達爾聞邀約為嵌入式開發愛好者分享這篇好文,更多我的文章,可以關注我的公眾號:HackforFun接下來我帶領大家一起來感受一下QEMU的強大和樂趣:手把手教你在 Ubuntu 系統中通過 QEMU 來運行基於 Arm CPU 的Linux系統吧。我們安裝的是 Arm 版本的 QEMU,如果直接在 Ubuntu 上用sudo apt install qemu-system-arm命令安裝的話,得到的 QEMU 版本比較舊,最好直接通過源碼去編譯。我在 Ubuntu 18.04 系統上發現系統默認安裝的 QEMU 在圖形模式(不帶 -nographic 參數)下無法啟動。
參考上面步驟編譯成功後,得到:qemu-system-arm和qemu-system-aarch64。前者用來模擬 32 位的 Arm cpu,比如 Arm9 /Arm11、 Cortex-A7/A9/A15 。後者用來模擬 64 位的 Arm cpu,比如 Arm Cortex A53,A57。可以用qemu-system-arm -machine help命令來查看所支持的開發板:是不是有很多熟悉的開發板都在裡面,i.MX、EXYNOS 這些知名的晶片都有包含。
這裡我們使用 vexpress-a9 這款開發板。vexpress-a9 是 Arm 公司自己設計的一款 4 核 Cortex-A9 開發板,U-Boot、Linux Kernel 和 QEMU 對這款開發板都做了完整的支持。第一步:U-Boot 代碼下載:下載完後,可以看到 configs 目錄下有針對這款開發板的配置文件:第二步:編譯
make vexpress_ca9x4_defconfigmake CROSS_COMPILE=arm-linux-gnueabihf- all最終編譯生成 elf 格式的可執行文件 u-boot 和純二進位文件u-boot.bin,其中 QEMU 可以啟動的為 elf 格式的可執行文件 u-boot。啟動一個 Arm Linux 系統,一般都要必須的三件套:Bootloader、Linux Kernel、rootfs(根文件系統)。在很久以前,製作 rootfs 是一件很麻煩的事情:交叉編譯 busybox,然後手動建立標準的 Linux 系統目錄,再把編譯 busybox 生成的各種文件和庫拷貝過來。如果還需要其他的模塊,再交叉編譯。如果交叉編譯的某個模塊依賴其他的庫,還想要辦法解決這個依賴關係。最後還要手動建立設備節點,設置對應的權限。一步一步做下去,任意一個環節都不能出錯。否則啟動的時候不知道會遇到什麼莫名其妙的問題。Buildroot 項目出現之後,如同它的 Slogan:MakingEmbedded Linux Easy,構建 rootfs 就變得輕鬆了許多,用一個群友的話說:「自從用了buildroot,我就告別了刀耕火種的野蠻生活。那種文件系統自己定製,需要任何工具都要自己下源碼交叉編譯然後被各種庫問題搞的焦頭爛額的時代一去不復返了。」
Buildroot 代碼倉庫默認只包含一個編譯框架,所以代碼量很小,下載起來很快。真正構建 rootfs需要的各種代碼包是根據你的配置選項,在編譯的時候才開始下載的。Buildroot 提供了和 U-Boot、Linux Kernel 等主流開源項目一樣的 menucoinfig 配置接口,可以通過make help來查詢所支持的各種命令:
開發者只要執行make menuconfig命令,就能通過這個熟悉的界面去選擇自己需要的各種組件,定製自己的 rootfs:首先要配置的是 Target options 選項:大部分 Arm 都是小端模式,所以選上 little endian 。這款開發板的 CPU 是 cortex-A9。我們將使用 Linaro GCC 進行編譯,Linaro 的 GCC 默認都打開了 hardfloat 的支持,所以選上 VFP extension 和 EABIhf。
第一個選項是設置最後生成的配置文件的保存路徑,buildroot 可以針對不同的板子生成特定的 defconfig 文件,默認保存在 configs 目錄下。自己修改各項配置後,執行make savedefconfig命令,就會生成新的 defconfig 文件:
下次編譯之前,可以直接執行make ca9_mini_defconfig命令來加載已有的配置。第二個選項設置 buildroot 下載的各種第三方包的存儲路徑,默認在 dl 目錄下:
因為這裡使用電腦上自己安裝的 toolchain,所以我們這裡選 External toolchain 和 Custom toolchain然後在 Toolchain path 中填寫 toolchian 在電腦上安裝的位置,如果不知道具體位置,用which命令查看:另外要注意 Toolchain prefix 這個前綴別寫錯。設置 toolchain 的版本和用來編譯這個 toolchain 的內核頭文件的內核的版本:Toolchain 的版本我們根據 Toolchain 的名字或者通過arm-linux-gnueabihf-gcc -v 命令就可以查到。編譯 Toolchain 的內核頭文件對應的內核的版本是什麼呢?我們在這個選項上敲 h 鍵,會看到下面的幫助選項:
原來這個版本可以在 toolchain 裡面的 version.h 這個文件查到:
arm-linux-gnueabihf/libc/usr/include/linux/version.h263680 對應的十六進位為 0x40600,右移 16 位,得到的版本號為 4。這就是上面 4.0.x 的由來。這裡也教給了大家一個小竅門:當我們在 make menuconfig 做配置的時候,如果遇到了看不懂的選項,直接在這個選項上敲 h 鍵,會看到一些有用的幫助信息,對這個選項做進一步解釋。因為 vexpress_a9 內核啟動的控制臺的名字叫做 ttyAMA0,所以我們還要在 :System configuration->Run a getty(login prompt) after boot選項中配置 TTY Port 為 ttyAMA0。否則文件系統掛載後無法進入控制臺。我們把編譯的 rootfs 以 initramfs 的形式和 Linux Kernel 連結在一起,為了讓根文件系統鏡像儘量小,可以對文件系統採用 lz4 壓縮,所以 Filesystem images 還要做如下配置:
到這裡一個最精簡的 buildroot 已經配置完成。如果還需要其他的命令或者工具,可以在 Target Packages 下面開啟:如果你需要的某個模塊,buildroot 裡面沒有,還可以自己添加:比如加入上面這個補丁,就可以讓 buildroot 在編譯的時候自動下載 https://github.com/rockchip-linux/io.git 並編譯。Buildroot 編譯的過程中會自動通過網絡下載需要的各種包,所以要保證網絡暢通。
git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitLinux mainline 已經有了對 vexpress_a9 這塊板子的支持:這裡面的 ca 就是 Cortex-a 的簡寫,所以 ca9 就是 Cortex-A9,ca15 就是 Cortex-A15.把前面 buildroot 編譯的 rootfs.cpio.lz4 拷貝到 linux kernel 根目錄下:
cp../buildroot/output/images/rootfs.cpio.lz4 .make ARCH=arm vexpress_defconfig執行make ARCH=arm menuconfig命令,我們修改一些基本的配置:在 General setup->Initramfs source file處填寫 rootfs.cpio.lz4, 就是我們前面從 Buildroot 拷貝過來的 rootfs,這裡面我們把它和內核編譯在一起,當然,rootfs也可以單獨作為一個文件,放在獨立的分區去加載,這種方式我們可以留在以後去嘗試。在 Kernel hacking->printk and dmesg options選項中選中第一項,這樣列印的內核 log 前面會附帶有時間戳信息,比較好看。
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j8前面說了,QEMU 可以模擬 sd 卡等外設。我們就把編譯好的固件放在一個模塊的 sdcard 上,讓 QEMU 從這張模擬的 sd 卡上啟動 Linux 系統:◆ 製作 sd 卡鏡像,並將它格式化成 fat 格式:dd if=/dev/zero of=sd.img bs=4096 count=4096mkfs.vfat sd.img◆ 把編譯好的 kernel zImage 和 dtb 文件拷貝到 sd.img 中sudo mount sd.img /mnt/ -o loop,rwsudo cp arch/arm/boot/zImage /mnt/sudo cp arch/arm/boot/dts/vexpress-v2p-ca9.dtb /mnt/sudo umount /mntsudo qemu-system-arm -M vexpress-a9 -m 512M -kernel ../uboot-imx/u-boot-nographic -sd sd.img-M 參數指定啟動的板子為 vexpress-a9。-kernel 指定 QEMU 啟動時首先執行的程序,我們這裡指定為前面編譯好的 u-boot 可以執行文件。因為我們這裡是從命令行啟動,所以加了 nographic 參數。可以看到 u-boot 已經順利啟動並進入命令,下面我們來啟動 Linux Kernel。◆ 首先通過 fatload 命令把 sd.img 裡面的 zImage 和 dtb 文件讀到開發板的內存中:
fatload mmc 0:0 0x62008000 zImagefatload mmc 0:0 0x64008000 vexpress-v2p-ca9.dtb這裡面的 0x62008000 和 0x64008000 分別對應 zImage 和 dtb 文件在內存中的加載地址,這兩個地址是怎麼來的呢:這個 textofs 定義的就是 Linux kernel zImage 執行地址對應的內存偏移地址,默認偏移為 0x8000。在 u-boot 命令行中輸入bdinfo命令,可以查到這塊開發板內存的起始地址:可以看到這塊開發板的內存其實地址為 0x60000000,所以對應內核的起始地址為:0x62008000dtb 的加載地址沒有特別的要求,一般注意和 Linux Kernel Image 避開,不要重疊即可。◆ 通過 bootz 命令啟動 Linux Kernel:bootz 0x62008000 - 0x64008000到這裡,我們就順利的在 QEMU 上把 Arm linux 運行起來了。QEMU 是一個模擬器,它和真正的開發板還是有一定的區別:它無法完全模擬真實的硬體行為,也很難模擬一個 gpio 讓你去拉高拉低或去點一個 led 燈,又或者去模擬 dram,一個 LCD 接口,讓你去接一個顯示屏….看起來它不能模擬的東西太多了,那它到底有什麼用呢?它的強項是模擬實現那些不涉及具體硬體外設的場景,比如:你想快速體驗一下最新的 u-boot 和 linux kernel,它拿過來就能跑。你想在 Arm 上運行 Ubuntu、Debian 這些時髦的 Linux 發行版,用它就行。你想研究u-boot 或者 linux kernel 的啟動流程,它也很合適。我有時候在具體的開發板上移植 Linux 內核的時候,發現某個流程跑的很異常,我又不確定正常的流程是什麼樣的時候,我就直接拿 QEMU 來跑一下做對比。你想學習 Arm 彙編,完全可以在 QEMU 上跑,配合 GDB 就能單步調試啦。你想學習 Linux 設備樹,它可以拿來做實驗….再或者你想編譯某個開源的項目在 Arm 開發板上運行,而交叉編譯比較困難,就可以考慮直接在 QEMU 上運行一個 Arm Ubuntu 來編譯,也許會簡單很多。假設你以前一直玩單片機、做硬體,也想體驗下 Arm Linux 開發,用 QEMU 試試再適合不過了。用在對的場景,QEMU 就是一款召之即來的利器!我們是妮mo,達爾聞創始人,只講技術不撩漢的小姐姐。達爾聞在線教育平臺旨在服務電子行業專業人士,提供技能培訓視頻,覆蓋各細分領域熱門話題,比如嵌入式,FPGA,人工智慧等。並針對不同人群量身定製分層級學習內容,例如常用知識點,拆解評測,電賽/智能車/考研等,歡迎關注。
官網:www.darwinlearns.com
B站:達爾聞
QQ群:786258064