目錄
前言
因爲疫情的原因,遲遲沒有開學,而我的開發板還在學校,爲了不影響linux的學習計劃,決定使用qemu來模擬開發板。我的目標是:
- 在qemu上運行u-boot
- 使用u-boot通過TFTP的方式加載內核到虛擬開發板的內存
- 內核啓動後,通過網絡的方式掛載文件系統(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的網絡配置爲橋接方式,有以下幾件事情要做:
- 安裝必要的工具包
qemu在使用網絡時,需要用到brctl
等命令,這些命令ubuntu安裝後可能沒有帶,需要另外安裝,運行如下命令即可:apt install uml-utilities bridge-utils
- 主機內核的tun/tap模塊
qemu配置爲橋接方式需要主機內核的tun/tap模塊的支持,在我的ubuntu版本下,查看相應模塊
發現相應的模塊已經存在,說明主機內核已經支持了。可能有一些老版本的ubuntu是沒有 支持的需要自己安裝相應的模塊並創建設備文件。ls -l /dev/net/tun crw-rw-rw- 1 root root 10, 200 Mar 21 23:37 /dev/net/tun
- 修改主機的網絡配置
在ubuntu的/etc/network/interfaces
文件中添加如下配置:# 2020.03.21 for qemu auto ens33 auto br0 iface br0 inet dhcp bridge_ports ens33
- 添加/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環境。搭建過程分一下幾個步驟:
- 安裝相應軟件
apt install tftp-hpa tftpd-hpa
- 建立TFTP傳輸目錄
mkdir tftpboot chmod 0777 tftpboot
- 修改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"
- 重啓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環境有以下幾步:
- 安裝軟件
# 安裝服務器即可(客戶端有需要再裝) apt install nfs-kernel-server
- 設置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
:表示不檢查父目錄權限(提高效率) - 重啓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-boot
、u-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
以上準備工作都完成後,我們就可以進行我們的最終目標了。回顧一下我們的目標:
- 在qemu上運行u-boot
- 使用u-boot通過TFTP的方式加載內核到虛擬開發板的內存
- 內核啓動後,通過網絡的方式掛載文件系統(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 v3
和NFS v4
,這樣一來,kernel就無法掛載網絡根文件系統了(更多內容可以參看參考文獻[5][6])。解決這個問題可以從兩個方面出發:
- u-boot傳參
修改u-boot傳遞的參數,指明NFS的版本爲v3
或v4
(當然,kernel關於NFS v3
和NFS v4
的配置要選上); - 修改主機配置
Ubuntu默認支持NFS v3
和NFS 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?