動開發(fā)實戰(zhàn)環(huán)境搭建)
做了大半年 Linux 驅(qū)動開發(fā)最讓我頭疼的不是驅(qū)動本身而是手里沒有 IMX6ULL 開發(fā)板。年初想寫一個平臺驅(qū)動手邊只有一臺 PC于是我研究出一套完全靠 qemu 模擬 IMX6ULL 開發(fā)板的環(huán)境用 QEMU 跑內(nèi)核、掛 rootfs、看串口日志、寫字符設(shè)備驅(qū)動再把驅(qū)動編譯成模塊加載進去。這套方案實測下來覆蓋了真實開發(fā)板上九成的驅(qū)動開發(fā)流程內(nèi)核編譯、設(shè)備樹、模塊加載、串口控制臺、GDB 遠程調(diào)試全都能在命令行里閉環(huán)。如果你也是剛?cè)胄凶銮度胧?Linux 驅(qū)動或者公司板子還沒到位想提前把環(huán)境摸熟這篇文章會把從零到一的過程完整走一遍。我會給出能直接復(fù)制運行的命令也會解釋每條命令背后的理由。那些網(wǎng)上教程不會告訴你的坑我單獨拉了一章出來保證都是我自己踩過的。1. 為什么要在 QEMU 里搭 IMX6ULL 驅(qū)動環(huán)境1.1 實體板子的痛點用實體開發(fā)板做驅(qū)動開發(fā)首先要面對三個問題硬件成本、環(huán)境破壞、反復(fù)燒錄。IMX6ULL 這塊板子入門不貴但正點原子、野火這類全套資料加屏幕加各種模塊加起來也是一筆開銷。更麻煩的是調(diào)試驅(qū)動時經(jīng)常要把內(nèi)核、設(shè)備樹、根文件系統(tǒng)來回?zé)龑憽D 卡燒一次要幾分鐘U-Boot 不小心改壞板子直接變磚又要想辦法用 OTG 或者仿真器救回來。如果是團隊里幾個人共用一塊板子那沖突更嚴(yán)重——一個人在做內(nèi)核實驗另一個人想測自己的驅(qū)動只能排隊。QEMU 恰恰能把這三個痛點全消掉。環(huán)境是一個普通文件毀了大不了重新解壓啟動時間從按下電源鍵到進入 shell 只需要幾秒每個人都能在 PC 上獨立開一個“板子”而且完全不占實體硬件資源。1.2 QEMU 能模擬到什么程度不要把它想成一個普通的虛擬機。QEMU 在 ARM 模擬方面并不是簡單地把 CPU 指令翻譯執(zhí)行而是把 IMX6ULL 這顆 SoC 內(nèi)部的很多外設(shè)控制器也建模了。對驅(qū)動開發(fā)來說這意味著CPU 層面Cortex-A7 內(nèi)核的指令能正常跑32 位 ARM 代碼、中斷、異常、MMU、Cache 都有對應(yīng)模擬外設(shè)層面GPIO、UART、SD 控制器、定時器、看門狗這類常用外設(shè)都有寄存器模型內(nèi)核里的對應(yīng)驅(qū)動能正常 probe、正常讀寫寄存器板級層面QEMU 提供了類似真實開發(fā)板的 machine 模型內(nèi)核啟動時會加載匹配的設(shè)備樹總線枚舉、platform 設(shè)備注冊、驅(qū)動匹配這一整套流程和真實硬件完全一致。換句話說驅(qū)動本身的邏輯、注冊流程、中斷處理、文件操作接口在 QEMU 里驗證和真實板子幾乎沒區(qū)別。我在這套環(huán)境里調(diào)過的字符設(shè)備驅(qū)動移植回真實板子后幾乎沒有改動就能跑。1.3 模擬不了的邊界我必須把話說清楚QEMU 不是萬能的。IMX6ULL 上那些復(fù)雜外設(shè)比如 LCD 控制器、GPU、視頻編解碼單元、MIPI CSI 攝像頭接口QEMU 的模擬程度很淺甚至完全沒有模擬。你在這些外設(shè)上做驅(qū)動開發(fā)還是得靠實體板。另外模擬器的 GPIO 引腳并不會真的產(chǎn)生電平變化。你可以操作寄存器、可以請求中斷、可以用它驗證驅(qū)動框架的流程但不要指望能在 QEMU 里看到 LED 真的亮起來。部分版本的板級模型會把板載 LED 接到 GPIO 上能通過設(shè)備模型觀察狀態(tài)變化但這類細(xì)節(jié)每個 QEMU 版本不一樣不能當(dāng)作可靠依據(jù)。所以我的建議是基礎(chǔ)驅(qū)動、字符設(shè)備、平臺驅(qū)動、中斷請求、設(shè)備樹匹配這類“流程類”開發(fā)放心交給 QEMU涉及視頻、顯示、模擬信號這類強硬件相關(guān)的還是得老老實實用板子。2. 環(huán)境準(zhǔn)備QEMU、交叉編譯器與 rootfs2.1 QEMU 安裝與版本確認(rèn)我這里以 Ubuntu 系的發(fā)行版為例。用系統(tǒng)包管理器安裝是最快的sudo apt install qemu-system-arm裝完以后第一件事不是急著啟動而是確認(rèn)你拿到的 QEMU 機器模型。IMX6ULL 的 machine 是后來才加入 QEMU 的舊版本里沒有。我自己用的是 QEMU 8.2可以這樣查qemu-system-arm -machine help | grep -i imx6輸出里應(yīng)該能看到類似這樣的條目mcimx6ul-evk Freescale i.MX6UL Evaluation Kit (Cortex-A7) mcimx6ull-evk Freescale i.MX6ULL Evaluation Kit (Cortex-A7)如果你的系統(tǒng)包版本太老比如 Ubuntu 20.04 自帶的 QEMU 4.x很可能只有mcimx6ul-evk而沒有mcimx6ull-evk。這時候有兩種選擇一是直接用mcimx6ul-evkIMX6UL 和 IMX6ULL 在寄存器和設(shè)備樹層面高度兼容對大多數(shù)驅(qū)動開發(fā)影響不大二是自己編譯新版本 QEMU在官網(wǎng)下載源碼后按標(biāo)準(zhǔn)流程 configure、make、make install。后者不復(fù)雜但會花一些時間我建議先用系統(tǒng)包把環(huán)境跑通再回頭考慮升級。2.2 交叉工具鏈選擇IMX6ULL 是 32 位 ARM Cortex-A7 處理器所以必須用 ARM 32 位交叉編譯器。不少人會在這里犯迷糊順手裝了 aarch64 的交叉編譯器編譯出來的內(nèi)核根本跑不起來。Ubuntu 下直接安裝sudo apt install gcc-arm-linux-gnueabihf libc6-dev-armhf-cross安裝后檢查一下arm-linux-gnueabihf-gcc -v正常情況下會顯示 GCC 版本號并且 target 是 arm-linux-gnueabihf。注意這個hf后綴表示硬浮點IMX6ULL 的 Cortex-A7 支持 VFPv4 硬浮點選它是沒問題的。后面的內(nèi)核、busybox、驅(qū)動模塊全部要用這套工具鏈編譯所以先把環(huán)境變量固定好export ARCHarm export CROSS_COMPILEarm-linux-gnueabihf-2.3 用 BusyBox 做最小 rootfs內(nèi)核能啟動還不夠你得有一個能進去操作的根文件系統(tǒng)。BusyBox 是最經(jīng)典的方案它把sh、ls、cat、mount這些常用命令集合成一個二進制編譯快、體積小非常適合做嵌入式最小環(huán)境。先下載 BusyBox 源碼版本選 1.36 左右的穩(wěn)定版。然后tar xjf busybox-1.36.1.tar.bz2 cd busybox-1.36.1 make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- defconfig make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- -j$(nproc)編譯完以后把生成的 BusyBox 和命令鏈接安裝到一個臨時目錄里這個目錄就是我們 rootfs 的雛形mkdir -p ../rootfs make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- install CONFIG_PREFIX../rootfs光有 BusyBox 還不夠得手工把目錄結(jié)構(gòu)和必要設(shè)備節(jié)點搭好mkdir -p rootfs/{proc,sys,dev,etc/init.d,lib,tmp,var} sudo mknod -m 666 rootfs/dev/console c 5 1 sudo mknod -m 666 rootfs/dev/null c 1 3沒有/dev/console是個大坑后面啟動時會話直接起不來這個節(jié)點必須提前創(chuàng)建。然后寫啟動腳本。etc/inittab負(fù)責(zé)告訴 init 進程起來以后干什么::sysinit:/etc/init.d/rcS ::respawn:-/bin/sh ::restart:/sbin/initetc/init.d/rcS的內(nèi)容#!/bin/sh mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t devtmpfs devtmpfs /dev echo echo IMX6ULL QEMU rootfs OK echo 記得給 rcS 加執(zhí)行權(quán)限chmod x rootfs/etc/init.d/rcS2.4 制作 ext4 根文件系統(tǒng)鏡像QEMU 里通常把 rootfs 做成一個 ext4 鏡像然后模擬成 SD 卡接進系統(tǒng)。大小給 256MB 到 512MB一般 256MB 足夠。dd if/dev/zero ofrootfs.img bs1M count256 mkfs.ext4 rootfs.img如果你用的是新版本的 e2fsprogs1.47 及以上可以直接用-d參數(shù)把目錄內(nèi)容寫進鏡像一步到位mkfs.ext4 -d rootfs rootfs.img如果報不支持-d那就掛載后復(fù)制mkdir -p mnt sudo mount rootfs.img mnt sudo cp -a rootfs/* mnt/ sudo umount mnt這一步做完rootfs 準(zhǔn)備完畢。后面內(nèi)核里掛載的就是這個文件。3. 內(nèi)核編譯從 defconfig 到 imx6ull 設(shè)備樹3.1 內(nèi)核源碼選取做 QEMU 環(huán)境我強烈建議用主線內(nèi)核不要用開發(fā)板廠商提供的 BSP 內(nèi)核。原因有二第一廠商 BSP 里的內(nèi)核往往改了很多板級代碼還夾雜著大量針對實體板卡的 NAND 分區(qū)、LCD 初始化、WiFi 模組支持這些配置在 QEMU 里不僅沒用反而可能引發(fā)啟動異常。第二主線內(nèi)核里的 imx6ull 設(shè)備樹和 QEMU 的 machine 模型配合得最干凈你編譯完就能啟動不用做任何 hack。我用的內(nèi)核版本是 6.1.y LTS這個版本足夠穩(wěn)定而且對 imx_v6_v7_defconfig 的支持非常完善。內(nèi)核源碼直接從 kernel.org 下載tar xjf linux-6.1.x.tar.xz cd linux-6.1.x3.2 defconfig 的取舍IMX6ULL 屬于 i.MX 系列里 armv7 架構(gòu)的 SoC主線內(nèi)核里對應(yīng)的默認(rèn)配置是imx_v6_v7_defconfig。這個配置會把 i.MX 系列大部分 SoC 的支持都編進去好處是你不用糾結(jié)具體選了哪些選項壞處是編譯時間稍微長一點但完全在可接受范圍內(nèi)。配置命令make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- imx_v6_v7_defconfig接下來有一個容易被忽略的環(huán)節(jié)——檢查關(guān)鍵配置項。執(zhí)行g(shù)rep -E CONFIG_SOC_IMX6ULL|CONFIG_GPIO_IMX|CONFIG_SERIAL_IMX .config這三個配置分別對應(yīng) IMX6ULL SoC 支持、GPIO 驅(qū)動、串口驅(qū)動任何一個缺失都會讓后續(xù)工作卡住。正常 defconfig 下這三個都是開啟的。如果你想用 GDB 調(diào)試內(nèi)核記得額外開啟調(diào)試信息make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- menuconfig在 Kernel hacking → Compile-time checks and compiler options 里勾選Compile the kernel with debug info對應(yīng)配置項是CONFIG_DEBUG_INFO。有了這個后面才能對內(nèi)核下斷點調(diào)試。配置完成后開始編譯內(nèi)核鏡像make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- -j$(nproc) zImage編譯產(chǎn)物在arch/arm/boot/zImage。這一步順利的話說明基礎(chǔ)工具鏈沒問題。3.3 編譯設(shè)備樹設(shè)備樹是 IMX6ULL 驅(qū)動開發(fā)里繞不開的一環(huán)。QEMU 啟動時會把 dtb 加載到內(nèi)存內(nèi)核根據(jù)它來枚舉平臺設(shè)備、匹配驅(qū)動所以 dtb 必須和 SoC 型號一致。在內(nèi)核源碼目錄下執(zhí)行make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- dtbs完成以后找imx6ull-14x14-evk.dtbfind arch/arm/boot/dts -name imx6ull*.dtb注意路徑在不同內(nèi)核版本里有差異。6.1 及更早版本通常在arch/arm/boot/dts/imx6ull-14x14-evk.dtb6.7 之后內(nèi)核把設(shè)備樹源碼結(jié)構(gòu)調(diào)整過文件挪到了arch/arm/boot/dts/nxp/imx/imx6ull-14x14-evk.dtb。找不到就用 find 全盤搜一下這個最保險。設(shè)備樹里有一項需要注意內(nèi)存大小要和 QEMU 的-m參數(shù)匹配。imx6ull-14x14-evk.dtb默認(rèn)聲明 512MB 內(nèi)存我后面啟動時用的就是-m 512M兩者能對應(yīng)上。如果你要改內(nèi)存大小記得同步去改 dtb 或者用 QEMU 的-m參數(shù)對齊不然后果可能是內(nèi)核啟動一半直接 panic。3.4 驗證產(chǎn)物完整性編譯完以后先做一個簡單驗證避免后面啟動時手忙腳亂。核對三個文件是否齊全arch/arm/boot/zImagearch/arm/boot/dts/.../imx6ull-14x14-evk.dtb上一章做的rootfs.img這三個文件是后面 QEMU 啟動命令的全部輸入。4. QEMU 啟動 Linux 的完整命令4.1 啟動參數(shù)逐項拆解萬事俱備現(xiàn)在該上 QEMU 了。我最終使用的啟動命令長這樣qemu-system-arm \ -M mcimx6ull-evk \ -m 512M \ -kernel zImage \ -dtb imx6ull-14x14-evk.dtb \ -drive filerootfs.img,formatraw,ifsd \ -append root/dev/mmcblk0 rw rootfstypeext4 consolettymxc0,115200 panic5 \ -nographic每個參數(shù)都解釋一下知道為什么這么寫遇到問題才能自己排查-M mcimx6ull-evk指定模擬的板卡模型。如果你的 QEMU 版本沒有這個機器名就用mcimx6ul-evk。-m 512M模擬內(nèi)存大小要和 dtb 里聲明的大小匹配。-kernel zImage直接把內(nèi)核鏡像加載到內(nèi)存啟動不需要 U-Boot。這條路徑最簡單QEMU 會自行處理 zImage 的加載地址。-dtb imx6ull-14x14-evk.dtb把設(shè)備樹二進制傳給內(nèi)核。-drive filerootfs.img,formatraw,ifsd把 rootfs 鏡像模擬成 SD 卡系統(tǒng)里會出現(xiàn) mmcblk0 設(shè)備。-append內(nèi)核啟動參數(shù)。root/dev/mmcblk0指定根文件系統(tǒng)所在設(shè)備consolettymxc0,115200指定內(nèi)核串口控制臺IMX 系列的第一路 UART 在 Linux 里的設(shè)備名是 ttymxc0panic5指定內(nèi)核 panic 后 5 秒自動重啟調(diào)試時很有用。-nographic把串口輸出重定向到當(dāng)前終端不使用圖形窗口。跑嵌入式模擬基本都是這個用法。4.2 第一次啟動成功的輸出長什么樣輸入啟動命令后你會看到一串內(nèi)核啟動日志最終停在類似這樣的界面... VFS: Mounted root (ext4 filesystem) on device 179:0. devtmpfs: mounted Freeing unused kernel image (initmem) memory: 1024K Run /sbin/init as init process IMX6ULL QEMU rootfs OK / #看到這個/ #提示符恭喜你的 QEMU 環(huán)境已經(jīng)全部跑通了。進去先敲幾個命令驗證環(huán)境完整性/ # uname -a Linux (none) 6.1.xx #1 SMP ... armv7l GNU/Linux / # cat /proc/cmdline root/dev/mmcblk0 rw rootfstypeext4 consolettymxc0,115200 panic5 / # ls /sys/bus/platform/devices//sys/bus/platform/devices/下能看到大量由設(shè)備樹生成的 platform 設(shè)備這證明設(shè)備樹已經(jīng)被內(nèi)核正確解析了。到了這一步你已經(jīng)擁有了一個和真實開發(fā)板高度相似的 Linux 運行環(huán)境。4.3 網(wǎng)絡(luò)與外部訪問可選如果你要給 QEMU 加網(wǎng)絡(luò)接法比較直接qemu-system-arm \ -M mcimx6ull-evk \ -m 512M \ -kernel zImage \ -dtb imx6ull-14x14-evk.dtb \ -drive filerootfs.img,formatraw,ifsd \ -append root/dev/mmcblk0 rw rootfstypeext4 consolettymxc0,115200 panic5 \ -netdev user,ideth0 \ -device imx_fec,netdeveth0 \ -nographic進系統(tǒng)后用ifconfig -a或者ip link show看有沒有eth0。需要提醒的是QEMU 不同版本對 i.MX 網(wǎng)卡模型的名字和綁定方式有差異如果你用的版本里imx_fec設(shè)備名不對可以用qemu-system-arm -device help | grep -i imx查一下支持列表。說句實話調(diào)試驅(qū)動階段網(wǎng)絡(luò)不是必需品。文件要傳進系統(tǒng)更省事的方式是在 rootfs 鏡像制作時就拷貝進去或者用 9p 共享目錄我建議新手先把網(wǎng)絡(luò)這層放一放別讓它干擾主線。5. 驅(qū)動調(diào)試從寫代碼到斷點5.1 最小字符設(shè)備驅(qū)動環(huán)境跑通了接下來進入正題寫驅(qū)動。我用 misc 設(shè)備框架做例子它比傳統(tǒng)的 register_chrdev 寫起來簡單得多不用手動分配主設(shè)備號適合用來驗證整個工具鏈。文件叫hello.c#include linux/module.h #include linux/fs.h #include linux/miscdevice.h #include linux/uaccess.h #define DEV_NAME hello_imx6ull static ssize_t hello_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { const char *msg hello from IMX6ULL QEMU!\n; size_t len strlen(msg); if (*ppos len) return 0; if (copy_to_user(buf, msg, len)) return -EFAULT; *ppos len; return len; } static const struct file_operations hello_fops { .owner THIS_MODULE, .read hello_read, }; static struct miscdevice hello_dev { .minor MISC_DYNAMIC_MINOR, .name DEV_NAME, .fops hello_fops, }; static int __init hello_init(void) { int ret; ret misc_register(hello_dev); if (ret) return ret; printk(KERN_INFO hello_imx6ull: module loaded\n); return 0; } static void __exit hello_exit(void) { misc_deregister(hello_dev); printk(KERN_INFO hello_imx6ull: module unloaded\n); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE(GPL);配套的 Makefileobj-m : hello.o KDIR ? /path/to/linux-6.1.x all: $(MAKE) -C $(KDIR) M$(PWD) ARCHarm CROSS_COMPILEarm-linux-gnueabihf- modules clean: $(MAKE) -C $(KDIR) M$(PWD) ARCHarm CROSS_COMPILEarm-linux-gnueabihf- clean注意KDIR必須指向你編譯內(nèi)核的那個源碼目錄而且模塊必須和內(nèi)核使用同一個源碼樹、同一個交叉編譯器否則 insmod 時會報 version magic 不匹配。5.2 交叉編譯與加載編譯模塊make正常會生成hello.ko。這里看不出什么特別關(guān)鍵在后面的加載環(huán)節(jié)。把hello.ko放進 rootfs 的/root目錄里sudo cp hello.ko rootfs/root/ sudo umount mnt # 如果用掛載方式做的鏡像記得重新打包這里有個經(jīng)驗如果你用的是mkfs.ext4 -d rootfs rootfs.img這種一次性生成方式那就在生成鏡像前把 hello.ko 放進 rootfs 目錄。如果已經(jīng)是鏡像文件就掛載后復(fù)制再卸載。重新啟動 QEMU進入系統(tǒng)后加載模塊/ # insmod /root/hello.ko / # cat /proc/misc 127 hello_imx6ull / # cat /dev/hello_imx6ull hello from IMX6ULL QEMU! / # rmmod hello_imx6ull / # dmesg | tail [ xx.xxxxxx] hello_imx6ull: module loaded [ yy.yyyyyy] hello_imx6ull: module unloaded這一整套流程走通說明你已經(jīng)具備在 QEMU 里做驅(qū)動開發(fā)的基本能力了。/proc/misc能看到設(shè)備注冊成功/dev/hello_imx6ull能正常讀寫這不只是“能編譯”而是驅(qū)動從注冊到文件操作接口的全鏈路驗證。5.3 GDB 遠程調(diào)試內(nèi)核這是 QEMU 環(huán)境比實體板子強太多的地方。實體板子上你想看內(nèi)核某個函數(shù)有沒有被調(diào)用得靠 printk 一點一點打QEMU 里可以直接掛 GDB 斷點。先重新編譯內(nèi)核確認(rèn)開啟CONFIG_DEBUG_INFO然后保留編譯生成的vmlinux文件注意不是 zImage。之后啟動 QEMU 時加兩個參數(shù)qemu-system-arm \ -M mcimx6ull-evk \ -m 512M \ -kernel zImage \ -dtb imx6ull-14x14-evk.dtb \ -drive filerootfs.img,formatraw,ifsd \ -append root/dev/mmcblk0 rw rootfstypeext4 consolettymxc0,115200 panic5 \ -nographic \ -s -S-s表示在 TCP 1234 端口開放 GDB 服務(wù)-S表示暫停 CPU等待調(diào)試器連接后再開始執(zhí)行。打開另一個終端gdb-multiarch vmlinux在 GDB 里(gdb) target remote :1234 (gdb) break do_init_module (gdb) continue這時如果回到 QEMU 終端執(zhí)行insmod /root/hello.koGDB 會立刻命中do_init_module斷點——這是內(nèi)核加載任何模塊都會經(jīng)過的入口函數(shù)。你可以從這里一步步往下看模塊初始化是怎么被調(diào)用的。如果你想直接對hello_init下斷點需要先等模塊加載后讓 GDB 讀取模塊符號方法是在 insmod 前先break do_init_module斷下后再用(gdb) add-symbol-file /path/to/hello.ko 0x地址模塊加載的基地址可以從/proc/modules里讀到。這塊稍微進階但掌握了以后你調(diào)試驅(qū)動時的視野會完全不一樣。VS Code 里配好 cortex-debug 或者 Native Debug 插件也能把這套 GDB 流程圖形化適合不習(xí)慣命令行的人。6. 踩過的坑和常見問題解決6.1 啟動卡死在 “Uncompressing Linux... done, booting the kernel”這是最常見的一種翻車現(xiàn)場。內(nèi)核其實已經(jīng)解壓完成但在切換控制臺時輸出中斷了。絕大多數(shù)原因是控制臺參數(shù)不對——很多人習(xí)慣性地寫consolettyS0,115200但 IMX6ULL 的串口在 Linux 里叫ttymxc0。把啟動參數(shù)改成consolettymxc0,115200問題立刻消失。如果還不行檢查 dtb 文件名是不是imx6ull-14x14-evk.dtb以及-M指定的機器名是否支持當(dāng)前 dtb。6.2 機器名選錯導(dǎo)致的行為差異如果你的 QEMU 版本比較老沒有mcimx6ull-evk用mcimx6ul-evk啟動大概率也能跑但設(shè)備樹最好換成imx6ul-14x14-evk.dtb。IMX6UL 和 IMX6ULL 差異不大可設(shè)備樹里外設(shè)的細(xì)節(jié)還是有區(qū)別。我的經(jīng)驗是查一下 QEMU 支持哪些機器名然后讓 dtb 跟著機器名走別混搭。要是系統(tǒng)自帶的 QEMU 實在太老連mcimx6ul-evk都沒有那就直接去官網(wǎng)下載新版源碼編譯./configure --target-listarm-softmmu make -j$(nproc) sudo make install編譯新版 QEMU 本身不復(fù)雜二十分鐘以內(nèi)能搞定。6.3 insmod 報 version magic 不匹配這個報錯信息類似hello: version magic 6.1.0 ... should be 5.15.0 ...原因只有一個模塊和內(nèi)核不是同一個源碼樹編出來的。很多人為了方便模塊用板子廠商給的 SDK 編譯內(nèi)核卻自己下了主線源碼兩邊版本對不上。解決方案是模塊的KDIR必須指向你實際編譯內(nèi)核的那個目錄且CROSS_COMPILE一致。模塊和內(nèi)核用同一套配置、同一個編譯器version magic 自然就對了。6.4 rootfs 起來以后沒有 shell 交互有時候內(nèi)核能掛載 rootfs但輸出停在一行就沒有動靜或者直接 kernel panic。排查順序第一確認(rèn)/dev/console節(jié)點存在這是我在 2.3 節(jié)特意強調(diào)過的點。沒有這個節(jié)點init 進程無法打開標(biāo)準(zhǔn)輸入輸出shell 起不來。如果你初始化腳本里有mount -t devtmpfs devtmpfs /dev理論上 devtmpfs 會自動創(chuàng)建 console但保險起見手動創(chuàng)建一次沒壞處。第二確認(rèn)etc/inittab里的::respawn:-/bin/sh寫對了。BusyBox 的 init 和傳統(tǒng) SysV init 語法類似但細(xì)節(jié)不同少一個冒號都會導(dǎo)致行為異常。第三rcS腳本有沒有可執(zhí)行權(quán)限。我犯過最蠢的錯誤就是忘了chmod x結(jié)果每次啟動都靜默失敗查了半天才發(fā)現(xiàn)。6.5 鏡像反復(fù)修改太麻煩如果像我一樣頻繁修改 rootfs每次重新生成 ext4 鏡像會非常浪費時間。我的建議是做一次 NFS 根文件系統(tǒng)把 rootfs 放在主機目錄里通過網(wǎng)絡(luò)掛載QEMU 里改文件即時生效。雖然前面說網(wǎng)絡(luò)屬于進階內(nèi)容但 NFS rootfs 在開發(fā)階段的價值極大值得專門花時間配一次。命令大致長這樣guest 內(nèi)核啟動參數(shù)里把root改成 NFS 路徑配合-netdev user的 hostfwd 做端口轉(zhuǎn)發(fā)root/dev/nfs nfsroot10.0.2.2:/path/to/rootfs,vers3 rwQEMU 的 user 網(wǎng)絡(luò)模式下guest 的 10.0.2.2 就是宿主機這個地址組合是固定的。如果你頻繁迭代驅(qū)動模塊這套方案能幫你省下大量重復(fù)打包的時間。這套 QEMU 環(huán)境我到現(xiàn)在還在用哪怕后來拿到了實體開發(fā)板很多基礎(chǔ)驗證工作我還是會先在 QEMU 里跑一遍。驅(qū)動框架、設(shè)備樹匹配、模塊加載這些邏輯性問題在模擬環(huán)境里排查效率遠高于實體板子畢竟重啟快、可斷點、不怕搞壞。等你把 QEMU 這套鏈路跑熟了再回到真實板子你會發(fā)現(xiàn)自己省下來最多的就是反復(fù)燒寫和串口日志翻找的時間。如果你也打算搭一套按照上面的順序一步步來中間卡住的地方多半都能在第六章里找到答案。