目录
- 前言
- 1 安装并配置qemu
- 1.1 安装
- 1.2 配置qemu的网络
- 2 搭建TFTP环境
- 3 搭建NFS环境
- 4 编译u-boot
- 5 编译linux kernel
- 6 使用busybox制作根文件系统
- 6.1 制作过程
- 6.2 测试
- 7 在qemu上利用u-boot启动kernel
- 8 对开发环境的一些完善工作
- 参考文献
前言
因为疫情的原因,迟迟没有开学,而我的开发板还在学校,为了不影响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版本下,查看相应模块
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?