1、下载编译安装
# wget https://download.qemu.org/qemu-4.2.0.tar.xz
# tar xvJf qemu-4.2.0.tar.xz
# cd qemu-4.2.0
# ./configure
# make
# make install
2、遇到的错误
ERROR: glib-2.22 gthread-2.0 is required to compile QEMU
一般使用
# sudo apt-get install libglib2.0-dev zlib1g-dev
就可以解决,但我的机器上不行,必须是libglib2.48版本以上的,所以只能网上下载编译安装了。
再安装一些必须库:
# sudo apt-get install libpixman-1-dev
# sudo apt-get install libsdl2*
3、检查是否安装正确
# qemu-system-arm -M ?
我们要使用arm工具链来编译uboot、内核、程序等,可以到arm官网下载。
当然也可以直接用命令下载:
# sudo apt install gcc-arm-linux-gnueabi
1、下载
我选择的版本是4.0.2的
# wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.0.2.tar.xz
2、编译
# make vexpress_defconfig // 配置内核,这之后还可以make menuconfig微调
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- zImage -j4
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- modules -j4 // 编译驱动模块
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- dtbs -j4 // 编译设备树
前面三个可以直接使用:
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j4
其中ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-可以去掉,但必须在Makefile修该:
arch ?= ARM, CROSS_COMPILE ?= arm-linux-gnueabi-
构建嵌入式Linux文件系统有很多工具,如buildroot,busybox、OpenWRT等,我选用的是busybox。
1、下载
# wget https://busybox.net/downloads/busybox-1.32.0.tar.bz2
也可以直接去官网选择版本下载。
2、编译
基本跟编译内核差不多
# vim Makefile, #ARCH ?= arm, CROSS_COMPILE ?= arm-linux-gnueabi-
# make defconfig
# make menuconfig #图形化配置,可以在settings->build options将其编译为static
# make -j4
# make install #然后在当前目录下 _install 就有busybox生成的内容了(基本的命令和库)
3、制作根文件系统
# mkdir rootfs
# cp -ra _install/* rootfs/
# mkdir -p rootfs/lib
# cp -ra /usr/arm-linux-gnueabi/lib/* rootfs/lib/ (里面的 *.a 其实可以删掉 只用 .so)
# cd rootfs
#全是空文件夹
# mkdir dev proc sys tmp root var mnt
# cd dev
# sudo mknod -m 666 tty1 c 4 1 #看起来是约定好的设备号,参看 ubuntu 主机中的
# sudo mknod -m 666 tty2 c 4 2
# sudo mknod -m 666 tty3 c 4 3
# sudo mknod -m 666 tty4 c 4 4
# sudo mknod -m 666 console c 5 1
# sudo mknod -m 666 null c 1 3
#还有一个etc/
4、制作根文件系统磁盘映像
# dd if=/dev/zero of=a9rootfs.ext3 bs=1M count=32
# mkfs.ext3 a9rootfs.ext3
# mount -t ext3 a9rootfs.ext3 tmpfs/ -o loop
# cp -ra ../busybox-1.32.0/rootfs/* tmpfs/
# umount tmpfs
根据自己的目录来。
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel zImage \
-dtb vexpress-v2p-ca9.dtb \
-nographic \
-append "root=/dev/mmcblk0 rw console=ttyAMA0" \
-sd vexpress.img
#-M 使用qemu仿真 vexpress-a9 machine
#-m 指定qemu虚拟机内存 512M
#-kernel 指定 qemu使用的kernel image,-dtb 指定 qemu boot kernel 时使用的设备树
# -nographic 不使用图形化界面(串口输出)。启动lcd版本的qemu,除了需要去掉 -nographic 外,
# 还需要将 console=ttyAMA0 改为console=tty0,因为标准终端已经重定向到lcd了
#(设备则由/dev/ttyAMA0变为/dev/tty0了)
# -append "xx" 指定kernel启动参数,root=/dev/mmcblk0 告诉kernel,rootfs文件系统映像
# 在 /dev/mmcblk0,以rw方式挂载。这个选项配合-sd工作,sd/emmc设备都是mmc接口,mmc接口的
# 第一个设备就是mmcblk0。-append还可以更完善,比如 -append "init=/linuxrc root=/dev/mmcblk0
# rw rootwait earlyprintk console=ttyAMA0",init=/linuxrc 告诉kernel起来后执行一下 /linuxrc。
# 启动后报告 can't run '/etc/init.d/rcS': No such file or directory,这是linux启动后执行
# 到的脚本,我们可以创建这个文件(并chmod 777),随便echo点东西即可。(etc作为kernel启动后的配置指示,
# 可以完善的更好, etc.tar.gz)。
# -sd 指示qemu的硬件连接状态,连接了一个sd卡(mmc接口),sd卡中的映像内容是 vexpress.img。没有文件系统
# kernel会打印 Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0),
# 表示没有文件系统可以挂载。配合-append里的root=xx使用。
#退出qemu就 ps -a 并 kill xx(或者 ps -A | grep qemu-system-arm | awk '{print $1}' | xargs sudo kill)
uboot 加载 kernel 可以从sd卡拿,但是sd卡已经存放了rootfs,没法和kernel image放一起,所以只能通过 tftp 从 ubuntu主机拿到。下面就是使用的这个办法。
当然也可以制作sd卡映像时制作两个分区,一个放kernel+dtb(分区格式只要uboot认识即可),另一个放rootfs(必须是linux指定的ext格式)。
既然要使用tftp,那么必须要配置QEMU 网络。qemu可以使用Tap / Tunnel模式将私有网络与ubuntu结合使用。
# apt-get install uml-utilities bridge-utils
auto eth0
auto br0
iface br0 inet dhcp
bridge_ports eth0
也可以部不修改/etc/network/interfaces,直接通过命令来实现,在主机的网卡上建立一个虚拟网桥,然后再虚拟网桥上创建虚拟的网卡,把创建好的虚拟网卡分配给qemu客户机使用
# brctl addbr br0 # 添加bridge br0
# brctl addif br0 eth0 # 将br0 与eth0 绑定
# brctl stp br0 on # 将br0设置为STP协议
# ifconfig eth0 0 # 将eth0 的IP设置为0,因为eth0已经工作在链路层,不需要IP
# dhclient br0 # 设置br0参数
# route # 查看路由表
qemu-ifup
#!/bin/sh
echo sudo tunctl -u $(id -un) -t $1
sudo tunctl -u $(id -un) -t $1
echo sudo ifconfig $1 0.0.0.0 promisc up
sudo ifconfig $1 0.0.0.0 promisc up
echo sudo brctl addif br0 $1
sudo brctl addif br0 $1
echo brctl show
brctl show
# sudo ifconfig br0 192.168.232.20
qemu-ifdown
#!/bin/sh
echo sudo brctl delif br0 $1
sudo brctl delif br0 $1
echo sudo tunctl -d $1
sudo tunctl -d $1
echo brctl show
brctl show
安装tftp工具:
apt-get install tftp-hpa tftpd-hpa xinetd
修改配置文件:/etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/home/qigaohua/work/qrs/qemuarm/"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="-l -c -s"
重启 tftp 服务:
/etc/init.d/tftpd-hpa restart
uboot 启动需要uImage ,前面使用的是zImage
make LOADADDR=0x60003000 uImage -j4
#!/bin/bash
# https://www.qemu.org/2018/05/31/nic-parameter/ 不能识别vlan选项
# -net nic,vlan=0 -net tap,vlan=0,ifname=tap0
# -netdev tap,id=n1 -device e1000,netdev=n1
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel u-boot \
-dtb vexpress-v2p-ca9.dtb \
-net nic -net tap,ifname=tap \
-nographic \
-append "root=/dev/mmcblk0 rw console=ttyAMA0"\
-sd a9rootfs.ext3
运行看看:
在uboot命令行输入以下:
setenv ipaddr 192.168.223.108 // 根据自己网络情况而定
setenv gatewayip 192.168.223.1
setenv netmask 255.255.255.0
setenv serverip 192.168.223.109
setenv bootargs 'root=/dev/mmcblk0 rw console=ttyAMA0' //如果是图形化启动,console=tty0;
saveenv
# tftp 0x60003000 uImage // 将镜像拷贝至 0x60003000,类似于 u-boot 的重定向过程
# tftp 0x60500000 vexpress-v2p-ca9.dtb // 将 dtb 文件拷贝至 0x60500000 位置。从这个头文件中可以知道,RAM 的地址是从 0x60000000 ~ 0x7fffffff
# bootm 0x60003000 - 0x60500000
上面需要手动在uboot命令行输入,可以直接在 uboot目录下的include/configs/vexpress_common.h添加:
#define CONFIG_BOOTCOMMAND \
"tftp 0x60003000 uImage;tftp 0x60500000 vexpress-v2p-ca9.dtb; \
setenv bootargs 'root=/dev/mmcblk0 console=ttyAMA0'; \
bootm 0x60003000 - 0x60500000; "
/* 配置开发板、主机IP地址 */
#define CONFIG_IPADDR 192.168.223.108
#define CONFIG_NETMASK 255.255.255.0
#define CONFIG_SERVERIP 192.168.223.109
再重新编译uboot,应该就不需要手动输入了,这个没有测试。
前面的 u-boot 仍然是从 SD 卡加载镜像文件,这一节将从 NFS 中加载。
NFS(Network File System)即网络文件系统,它允许网络中的计算机之间通过 TCP/IP 网络共享资源。在 NFS 的应用中,本地(qemu 的虚拟机) NFS 的客户端应用可以透明地读写位于远端(Ubuntu 上) NFS 服务器上的文件,就像访问本地文件一样。
如果在 Ubuntu 上有个目录比如 rootfs/ 存放了 qemu 虚拟机的根文件系统,现在使用 NFS 将它挂载到虚拟机上,那么在虚拟机上操作它的文件系统和在 Ubuntu 上操作 rootfs 等效。反之亦然。这样可以大大节省存储空间和降低操作难度。
在宿主机安装nfs:
# apt install nfs-kernel-server
修改nfs配置文件/etc/exports
/home/qigaohua/work/qrs/qemuarm/rootfs *(rw,sync,no_root_squash,no_subtree_check)
开启 NFS 服务
# /etc/init.d/nfs-kernel-server restart
注意: 编译内核需让内核支持挂载 NFS 文件系统(默认支持的),可使用make menuconfig ⇒ File systems --> Network File Systems --> 查看
运行qemu, 把上面boot.sh最后一行-sd去掉,启动后在uboot命令行输入一下命令:
# setenv ipaddr 192.168.223.108
# setenv gatewayip 192.168.223.1
# setenv netmask 255.255.255.0
# setenv serverip 192.168.223.109
# setenv bootargs 'root=/dev/nfs rw nfsroot=192.168.223.109:/home/qigaohua/work/qrs/qemuarm/rootfs init=/linuxrc ip=192.168.223.108 console=ttyAMA0' // 使用nfs文件系统
# saveenv
# tftp 0x60003000 uImage
# tftp 0x60500000 vexpress-v2p-ca9.dtb
# bootm 0x60003000 - 0x60500000
运行成功后,在本地nfs目录添加文件后,qemu arm模拟机也同样添加了。
当上面启动成功后,我们用ifconfig看看接口情况:
我发现ping baidu.com 不同,不能访问外网。下面解决该问题。
先看下路由情况:route -n
若出现 route: can’t open ‘/proc/net/route’: No such file or directory
则
mkdir proc // 如不存在该目录
mount -t proc proc proc/
如果Destination 有default 项(即 0.0.0.0),无需处理,否则需要添加路由,上图可知,我的并没有。添加路由:
# route add default gw 192.168.223.1 dev eth0
ping 下百度,发现可以了
如果你还不能ping baidu.com, 可能是域名解析问题,创建/etc/resolv.conf,添加:
根据自己的情况而定。
上面说了制作sd卡映像时制作两个分区,一个放kernel+dtb,另一个放rootfs,不需要通过tftp加载内核启动。
#制作一个含有两个分区的镜像,第一个分区放kernel+dtb,第二个分区是rootfs用于挂载
dd if=/dev/zero of=kernel-rootfs.img bs=1M count=64
sudo parted kernel-rootfs.img --script -- mklabel msdos #这三步用fdisk做也行
sudo parted kernel-rootfs.img --script -- mkpart primary fat32 2048s 40960s
sudo parted kernel-rootfs.img --script -- mkpart primary ext4 40961s -1
#fdisk kernel-rootfs.img #分两个区,2048-40960,40961-
#建立映射,然后格式化两个分区
sudo losetup -f --show kernel-rootfs.img
sudo kpartx -va /dev/loop0
sudo mkfs.vfat /dev/mapper/loop0p1
sudo mkfs.ext4 /dev/mapper/loop0p2
#mount到ubuntu主机
mkdir kernel rootfs
sudo mount /dev/mapper/loop0p1 kernel
sudo mount /dev/mapper/loop0p2 tmpfs
# 拷贝uImage、vexpress-v2p-ca9.dtb到kernel目录,拷贝rootfs/*到tmpfs目录
sudo umount kernel
sudo umount tmpfs
sudo kpartx -d /dev/loop0
sudo losetup -d /dev/loop0
rm -rf kernel tmpfs
qemu-system-arm -M vexpress-a9 -m 512M -kernel u-boot -nographic -append "root=/dev/mmcblk0 console=ttyAMA0" -sd kernel-rootfs.img
uboot起来后,在uboo命令行中输入:
fatls mmc 0:1
fatload mmc 0:1 60003000 uImage
fatload mmc 0:1 60500000 vexpress-v2p-ca9.dtb
setenv bootargs 'init=/linuxrc root=/dev/mmcblk0p2 rw rootwait earlyprintk console=ttyAMA0'
bootm 60003000 - 60500000
root=/dev/mmcblk0p2,告诉kernel根文件系统在第0个mmc接口设备的第2个分区。(rootwait 表示等待 mmc 设备初始化完成以后再挂载, earlyprintk 打开早期打印)