搭建基於qemu的linux開發環境

前言

因爲疫情的原因,遲遲沒有開學,而我的開發板還在學校,爲了不影響linux的學習計劃,決定使用qemu來模擬開發板。我的目標是:

  1. 在qemu上運行u-boot
  2. 使用u-boot通過TFTP的方式加載內核到虛擬開發板的內存
  3. 內核啓動後,通過網絡的方式掛載文件系統(NFS)

要想實現上述目標,需要配置一些環境,整個配置的過程比較細碎,一些配置方法不常用,容易忘記,因此通過一篇博客詳細記錄下來。過程中參考了一些網絡資料,會在參考文獻中列出。

1 安裝並配置qemu

1.1 安裝

ubuntu環境下,可以直接使用如下命令安裝qemu:

apt install qemu

我的ubuntu版本是18.04.2,相應的qemu版本是2.11,因此直接安裝無法安裝到qemu的最新版本,喜歡嚐鮮的同學可以去下載最新版的qemu源碼然後採用編譯安裝的方式。

1.2 配置qemu的網絡

由於我計劃在qemu上運行u-boot,然後由u-boot使用tftp的方式將kernel鏡像載入到qemu虛擬出的開發板的內存中,最後啓動內核並通過網絡的方式掛載根文件系統(NFS),因此需要qemu能夠和ubuntu網絡連通,換句話說,需要qemu裏運行的u-boot以及kernel能夠ping通ubuntu。

qemu其實也是虛擬機,qemu上運行的u-boot及kernel的關係類似於ubuntu和windows之間的關係。如果需要ubuntu和windows之間以ping通(就好像局域網中的兩臺計算機),那麼需要將虛擬機(VMWare)的網絡配置爲橋接方式。同樣的,qemu的網絡也可以配置爲橋接

將qemu的網絡配置爲橋接方式,有以下幾件事情要做:

  1. 安裝必要的工具包
    qemu在使用網絡時,需要用到brctl等命令,這些命令ubuntu安裝後可能沒有帶,需要另外安裝,運行如下命令即可:
    apt install uml-utilities bridge-utils
    
  2. 主機內核的tun/tap模塊
    qemu配置爲橋接方式需要主機內核的tun/tap模塊的支持,在我的ubuntu版本下,查看相應模塊
    ls -l /dev/net/tun
    crw-rw-rw- 1 root root 10, 200 Mar 21 23:37 /dev/net/tun
    
    發現相應的模塊已經存在,說明主機內核已經支持了。可能有一些老版本的ubuntu是沒有 支持的需要自己安裝相應的模塊並創建設備文件。
  3. 修改主機的網絡配置
    在ubuntu的/etc/network/interfaces文件中添加如下配置:
    # 2020.03.21 for qemu
    auto ens33
    auto br0
    iface br0 inet dhcp
    bridge_ports ens33
    
  4. 添加/etc/qemu-ifup和/etc/qemu-ifdown腳本
    經查看,這兩個腳本文件在我的ubuntu中已經存在了,可能某些老版本的ubuntu是需要自己創建的。如果需要自己創建的話,腳本的內容網上很容易找到,不再贅述。

完成以上步驟後,重啓ubuntu,以使配置(主要針對主機網絡配置的修改)生效。重啓後,運行ifconfig,如果出現br0則說明配置成功了,如下:

br0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.0.100  netmask 255.255.255.0  broadcast 192.168.0.255
        inet6 fe80::20c:29ff:fe31:287b  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:31:28:7b  txqueuelen 1000  (Ethernet)
        RX packets 162  bytes 102405 (102.4 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 201  bytes 19805 (19.8 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

需要注意的是,用qemu運行u-boot或kernel時,如果要使用qemu的網絡功能,需要加上參數:-net nic,vlan=0 -net tap,vlan=0,ifname=tap0

2 搭建TFTP環境

u-boot支持通過TFTP的方式加載內核,主機(ubuntu)上運行TFTP服務器,u-boot向服務器請求其參數指定的文件,主機收到請求後將TFTP傳輸目錄下的相應文件通過網絡傳輸給u-boot。這需要主機具有TFTP環境。搭建過程分一下幾個步驟:

  1. 安裝相應軟件
    apt install tftp-hpa tftpd-hpa
    
  2. 建立TFTP傳輸目錄
    mkdir tftpboot
    chmod 0777 tftpboot
    
  3. 修改TFTP的配置文件/etc/default/tftpd-hpa
    修改
    TFTP_USERNAME="tftp"
    # TFTP傳輸目錄
    TFTP_DIRECTORY=""/mnt/hgfs/share/tftpboot""
    TFTP_ADDRESS="0.0.0.0:69"
    # -c表示可以上傳文件,-s表示指定TFTP傳輸目錄,上面已經指定
    TFTP_OPTIONS="-l -c -s" 
    
  4. 重啓TFTP服務
    service tftpd-hpa restart
    

爲了確保環境搭建成功,可以做一些測試。在上文設置的TFTP傳輸目錄下創建文件test。然後進入某個目錄,假設是A目錄,運行如下命令:

#localhost 表示本機
tftp localhost

此時進入TFTP的命令行,輸入如下命令:

get test
put test
q

A目錄下出現文件test,表示TFTP服務器安裝成功。

3 搭建NFS環境

NFS是網絡文件系統的縮寫,可以用來實現在網絡中跨主機訪問文件(只能訪問目標主機開放出來的目錄,不是任何目錄)。這樣,在開發階段,直接在主機上利用開發工具開發,只要把開發好的程序拷貝到NFS共享目錄(向其它主機開放的目錄)下,開發板就可以訪問到了,非常方便。

搭建NFS環境有以下幾步:

  1. 安裝軟件
    # 安裝服務器即可(客戶端有需要再裝)
    apt install nfs-kernel-server
    
  2. 設置NFS共享目錄
    編輯/etc/exports,添加一行:
    # 這裏我設置的NFS共享目錄是/root/work/nfsroot
    # 記得創建好這個目錄
    /root/work/nfsroot *(rw,sync,no_root_squash,no_subtree_check)
    
    rw:表示權限爲可讀可寫
    sync:表示將數據同步到磁盤(保證數據的一致性)
    no_root_squash:表示來訪的root用戶保持root帳號權限
    no_subtree_check:表示不檢查父目錄權限(提高效率)
  3. 重啓NFS服務
    運行如下命令:
    /etc/init.d/nfs-kernel-server restart
    

爲了確保NFS環境搭建成功,不妨做一下測試。掛接NFS共享目錄,即執行如下命令:

mount -t nfs localhost:/root/work/nfsroot /mnt/rootfs

然後訪問/mnt/rootfs,可見/root/work/nfsroot目錄下的內容出現在/mnt/rootfs目錄下,說明環境搭建成功。

4 編譯u-boot

u-boot的源碼可以在這裏下載到,我下載的版本是u-boot-2019.10,下載之後先解壓再編譯。當然,編譯之前需要先配置,非常幸運的是這個版本的u-boot直接支持了vexpress-a9,因此直接使用相應的默認配置就可以了,省去了移植的過程:

make vexpress_ca9x4_defconfig ARCH=arm

爲了後續操做的方便,我們需要配置一下u-boot的幾個環境變量。執行:

make menuconfig ARCH=arm

打開圖形化配置界面後,將BOOTARGS配置爲:root=/dev/nfs rw nfsroot=192.168.0.100:/root/work/nfsroot init=/linuxrc ip=192.168.0.106 console=ttyAMA0;並將BOOTCOMMAND配置爲:tftp 0x60003000 uImage;tftp 0x60500000 vexpress-v2p-ca9.dtb;bootm 0x60003000 - 0x60500000;

以上環境變量是告訴u-boot給內核傳什麼參數(掛載網絡文件系統作爲根文件系統、init進程、內核IP、控制檯),以及啓動(boot)內核時u-boot執行的啓動命令(從TFTP加載內核及設備樹)。

此外,還需要設置一下u-boot的關於IP地址的環境變量,主要是服務端IP和開發板IP,這關係到開發板與服務器能否通過網絡通信。但這些環境變量尚未在u-boot的Kconfig體系(也就是圖形化配置)中,因此,不妨在源碼中直接修改。具體的,在include/configs/vexpress_common.h中添加如下宏定義:

/* 服務端IP根據主機IP確定,開發板IP與主機IP在一個網段 */
#define CONFIG_IPADDR 192.168.0.107
#define CONFIG_NETMASK 255.255.255.0
#define CONFIG_SERVERIP 192.168.0.100

然後編譯u-boot:

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

編譯完成後成功生成了u-bootu-boot.bin等文件。爲了測試配置和編譯沒有問題,運行如下命令,使用qemu運行u-boot(注意不是u-boot.bin,運行u-boot.bin會出錯):

qemu-system-arm -M vexpress-a9\
                -m 512M\
                -kernel u-boot\
                -nographic\
                -net nic,vlan=0 -net tap,vlan=0,ifname=tap0

以上命令中,需要注意的是u-boot的路徑需要根據自己的實際情況填寫,這裏是當前目錄的情況。命令執行後,u-boot成功運行,輸出瞭如下信息:


U-Boot 2019.10 (Mar 21 2020 - 22:14:46 +0800)

DRAM:  512 MiB
WARNING: Caches not enabled
Flash: 128 MiB
MMC:   MMC: 0
*** Warning - bad CRC, using default environment

In:    serial
Out:   serial
Err:   serial
Net:   smc911x-0
Hit any key to stop autoboot:  0 
=> 

此時,可以查看一下我們之前設置的IP地址是否生效:

=> print ipaddr
ipaddr=192.168.0.107

然後,在測試下第1節中對qemu的網絡配置是否真的能讓開發板和ubuntu通過網絡進行通信。測試的方法很簡單,直接ping主機(ubuntu)即可:

=> ping 192.168.0.100
smc911x: MAC 52:54:00:12:34:56
smc911x: detected LAN9118 controller
smc911x: phy initialized
smc911x: MAC 52:54:00:12:34:56
Using smc911x-0 device
smc911x: MAC 52:54:00:12:34:56
host 192.168.0.100 is alive

結果顯示,qemu網絡配置成功,u-boot可以ping通ubuntu。

5 編譯linux kernel

可以在linux內核的官網下載內核源碼,我選擇的版本是比較新的長期支持版本5.4.26。下載完源碼,並解壓,然後進入源碼的目錄。執行vexpress開發板的默認配置:

make vexpress_defconfig ARCH=arm

此外,由於我們需要掛載網絡文件系統,所以要配置內核的NFS客戶端功能。具體的,執行:

make menuconfig ARCH=arm

打開圖形化配置界面後,在File systems ⇒ Network File Systems配置項下,選中NFS客戶端相關的項。然後,保存並退出配置界面。

接下來需要編譯內核鏡像、內核模塊以及設備樹。編譯內核鏡像時需要注意,因爲我們是使用u-boot啓動內核,因此要編譯內核鏡像爲uImage,同時根據上文對u-boot環境變量的設置(根據開發板內存的實際地址),內核的加載地址需要設定爲0x60003000

製作uImage時需要mkimage工具,沒有安裝的話需要安裝:

apt install u-boot-tools

編譯內核鏡像時,執行:

make uImage LOADADDR=0x60003000 ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-

編譯內核模塊和設備樹文件時,執行:

make modules ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
make dtbs ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-

編譯得到的設備樹文件位於:arch/arm/boot/dts/vexpress-v2p-ca9.dtb。kernel的測試則放到下文,與busybox製作的根文件系統一起測試。

6 使用busybox製作根文件系統

6.1 製作過程

先到busybox的官網下載源碼,然後下載解壓。進入源碼目錄後進行配置。這裏我直接採用默認配置:

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

然後,通過圖形化的配置界面修改部分配置:

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

主要修改的是busybox的編譯方式,設置爲靜態編譯:Settings ⇒ Build static binary (no shared libs)

之後,就可以編譯安裝了:

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

busybox默認被編譯安裝在源碼根目錄的_install目錄下,進入這個目錄後發現,僅有一下幾個目錄文件:

bin  linuxrc  sbin  usr

接下來手動爲其創建幾個linux kernel要用到的目錄:

mkdir lib proc sys dev etc etc/init.d

爲了讓kernel在啓動時自動掛載一些文件系統(proc、sysfs)以及創建設備節點,我們需要rcS腳本,這個腳本在kernel啓動後會自動被kernel執行:

touch etc/init.d/rcS

然後再腳本中添加如下內容:

#!bin/sh

# 掛載proc文件系統
mount -t proc none /proc

# 掛載sysfs文件系統
mount -t sysfs none /sys

# mdev相當於嵌入式版本的udev
/sbin/mdev -s

同時不要忘了修改rcS的權限爲可執行:

chmod a+x etc/init.d/rcS

6.2 測試

下面我們需要測試製作好的文件系統有沒有問題,不妨先使用SD卡鏡像的方式測試(後面搭建好了NFS環境就直接掛載網絡文件系統了)。開始之前,爲了方便,不妨建立一個rootfs目錄,並把_install目錄下的文件拷貝到rootfs

cp -rf busybox-1.31.1/_install/* rootfs/

然後,製作SD卡鏡像:

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

顯然,rootfs.ext4.img並不是真正的SD卡設備,它只是我們在磁盤上創建的一個文件(爲qemu創建的虛擬SD卡)。接下來我們會假裝它是一張SD卡,並正兒八經的對其進行格式化、掛載等操作。先將它按照ext4文件系統格式進行格式化:

mkfs.ext4 rootfs.ext4.img

把這個鏡像文件按照ext4文件系統類型進行掛載,從而方便往裏面拷貝內容。注意,rootfs.ext4.img不是真正的SD卡,實質是一個文件,一個包含有文件系統格式的文件,也就是一個loop設備。所以掛載的時候要添加loop選項:

mount -t ext4 -o loop rootfs.ext4.img /mnt/rootfs

最後,將我們通過busybox創建的根文件系統拷貝到掛載點/mnt/rootfs上,並卸載rootfs.ext4.img:

cp -rf rootfs/* /mnt/rootfs/
umount /mnt/rootfs

製作好根文件系統鏡像之後,就可以使用qemu運行kernel了,看看能不能成功掛載根文件系統。運行如下命令(命令中各種文件的路徑要根據自己的實際情況寫):

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

很幸運,kernel啓動成功:

/ # ls
bin      etc      linuxrc  sbin     usr
dev      lib      proc     sys

設備文件也被自動創建了:

/ # ls /dev/
console          ptype            tty33            tty7
cpu_dma_latency  ptypf            tty34            tty8
full             random           tty35            tty9
gpiochip0        root             tty36            ttyAMA0
......(不再列出)

7 在qemu上利用u-boot啓動kernel

以上準備工作都完成後,我們就可以進行我們的最終目標了。回顧一下我們的目標:

  1. 在qemu上運行u-boot
  2. 使用u-boot通過TFTP的方式加載內核到虛擬開發板的內存
  3. 內核啓動後,通過網絡的方式掛載文件系統(NFS)

首先把kernel鏡像以及編譯後的設備樹文件拷貝到TFTP的傳輸目錄:

cp linux-5.4.26/arch/arm/boot/uImage /mnt/hgfs/share/tftpboot/
cp linux-5.4.26/arch/arm/boot/dts/vexpress-v2p-ca9.dtb /mnt/hgfs/share/tftpboot/

然後輸入如下命令,使用qemu運行u-boot:

qemu-system-arm -M vexpress-a9\
                -m 512M\
                -kernel u-boot\
                -nographic\
                -net nic,vlan=0 -net tap,vlan=0,ifname=tap0

u-boot運行後,可以通過TFTP加載內核,但是內核無法掛載網絡根文件系統,輸出的錯誤信息如下:

VFS: Unable to mount root fs via NFS, trying floppy.
List of all partitions:
1f00          131072 mtdblock0 
 (driver?)
1f01           32768 mtdblock1 
 (driver?)
No filesystem could mount root, tried: 
 nfs

Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(2,0)

經過一番搜索,發現是啓動參數的問題,原先的啓動參數如下:

root=/dev/nfs rw nfsroot=192.168.0.100:/root/work/nfsroot init=/linuxrc ip=192.168.0.106 console=ttyAMA0

修改如下:

rootfstype=nfs root=/dev/nfs rw nfsroot=192.168.0.100:/root/work/nfsroot,v3 init=/linuxrc ip=192.168.0.106:192.168.0.100::255.255.255.0 console=ttyAMA0

其中一部分修改是沒有效的,最關鍵的修改是v3。這個參數表明,kernel在掛載網絡根文件系統時,作爲NFS客戶端,需要按照NFS v3規定的格式向服務端運行於Ubuntu)發送相應的數據包,進而完成掛載。

爲什麼原先沒有指定版本的參數不行呢?因爲沒有指定版本的話kernel會按照NFS v2向服務端發送數據(至於是u-boot默認傳參v2,還是u-boot就傳了沒有版本的參數但kernel默認按照NFS v2,這我沒有細究)。而自Ubuntu 17.10之後NFS默認支持NFS v3NFS v4,這樣一來,kernel就無法掛載網絡根文件系統了(更多內容可以參看參考文獻[5][6])。解決這個問題可以從兩個方面出發:

  1. u-boot傳參
    修改u-boot傳遞的參數,指明NFS的版本爲v3v4(當然,kernel關於NFS v3NFS v4的配置要選上);
  2. 修改主機配置
    Ubuntu默認支持NFS v3NFS v4,但我們可以通過修改主機上NFS的配置使得對NFS v2的支持也開啓。

具體選用哪種解決方法,大家各憑喜好。修改完u-boot傳給kernel的參數後,再次使用qemu啓動u-boot,這次u-boot啓動kernel後,kernel成功掛載網絡根文件系統:

VFS: Mounted root (nfs filesystem) on device 0:14.
Freeing unused kernel memory: 1024K
Run /linuxrc as init process
random: fast init done

Please press Enter to activate this console. random: crng init done

/ # 

8 對開發環境的一些完善工作

到了這裏,最初的目標已經達成,開發環境初步建立了,不過還有一些有待完善的內容。主要是我們構建的根文件系統太過簡陋,比如缺少配置文件/etc/inittab等。

這裏簡單介紹一下kernel的啓動流程:
在這裏插入圖片描述
不難發現,當前建立的根文件系統中還沒有/etc/inittab/etc/profile等文件,雖然不至於讓kernel無法啓動,但這會導致一些應該在啓動時完成的工作沒有完成,比如一些文件系統沒有得到掛載。

那麼,怎麼製作上述文件呢?這些網上有很多資料提及,不難找到,這裏就不再贅述了。

參考文獻

[1] 用Qemu模擬vexpress-a9 — 配置 qemu 的網絡功能
[2] Ubuntu 16.04中安裝tftp
[3] ubuntu——nfs服務搭建
[4] Building a Root File System using BusyBox
[5] uboot 無法通過 nfs 啓動 Ubuntu 18.04 內的根文件目錄
[6] How can I make the nfs server support protocol version 2 in Ubuntu 17.10?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章