00. 编译qemu
安装依赖,主要是交叉工具链,编译uboot、kernel时用(我ubuntu主机中有一个for arm-cortex-a64的交叉编译工具链aarch64, 有一个for arm-cortex-m的交叉编译工具链, 可以在docker中安装一个 for arm-cortex-a32 的交叉编译工具链 arm-linux-guneabi-gcc)
apt-get update
apt-get install vim bc build-essential gcc-arm-linux-gnueabi libncurses5-dev bison flex
下载qemu源码(我下的是v2.8.0,原来是想下载v5.2.0,后来发现网络连接总是配置不好,v2.8.0 的 configure需要python2)
#不加 --target-list 则编译支持的所有平台, --enable-debug 允许gdb qemu
./configure --enable-kvm --enable-debug --prefix=/opt/qemu --target-list="arm-softmmu x86_64-softmmu i386-softmmu aarch64-softmmu"
make -j4
sudo make install
最后记得将 /opt/qemu/bin/ 添加到 bashrc
01. 关于qemu
qemu-system-arm 会在 x86-ubuntu(我们的真实机器)中 仿真arm
qemu-system-arm --version #打印版本
qemu-system-arm -M help #打印目前所有支持的machine
-M help 输出中有 "vexpress-a9 ARM Versatile Express for Cortex-A9" 这款arm评估板,关于开发板的资料 http://www.myir-tech.com/download.asp?nid=49 。关于 arm 其他的 ip核, https://developer.arm.com/documentation/#sort=relevancy 。组成cortex-A系列最小可运行系统的硬件
-
1.CPU
-
2.DDR/SDRAM (sd / emmc / nand or nor flash / net / ... 均不为必须,但要有一个)
-
3.uart (控制输入)
-
4.lcd (显示输出,其实也不是必须)
1. 准备uboot
1.1 下载uboot
git clone https://github.com/u-boot/u-boot.git #或者去 https://ftp.denx.de/pub/u-boot 下载 u-boot-2017.07.tar.bz2
git tag
git checkout v2020.10
1.2 make uboot
可以在make之前修改 include/configs/vexpress_common.h 中的 CONFIG_BOOTCOMMAND 指定uboot启动行为,这样就不用后面进入后每次敲命令了(如果saveenv不可用的话)。
vim config.mk, #ARCH := arm (这里还是export ARCH=arm 最保险)
vim Makefile, #CROSS_COMPILE ?= arm-linux-gnueabi-
make vexpress_ca9x4_defconfig
make -j4
2. 准备kernel
2.1 下载kernel source
#或者直接去https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/ 下载,eg. linux-4.19.168.tar.xz
git clone https://github.com/torvalds/linux.git
git tag
git checkout v4.15-rc9
在 linux/arch/arm/下, mach-vexpress 就是对应 vexpress machine, configs/vexpress_defconfig 就是其内核配置文件。回到 linux顶层目录,
2.2 编译kernel(for vexpress)
vim Makefile, #修改 arch ?= ARM, CROSS_COMPILE ?= arm-linux-gnueabi-
#或者这里 export ARCH=arm, export CROSS_COMPILE=arm-linux-gnueabi-
#或者下面make时都加上 ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
make vexpress_defconfig #配置内核,这之后还可以make menuconfig微调
make zImage -j4 #编译内核
make modules -j4 #编译内核模块
make dtbs #编译device tree,生成.dts
#使用uboot引导kernel,要编译uImage(zImage与位置无关,uImage是专门给uboot准备的,前面加上了0x40长度的header,使用uboot/tool/mkimage制作得到)
make LOADADDR=0x60003000 uImage -j4 #若提示mkimage找不到,则将 /mnt/home/fang/codes/u-boot/tools 添加到PATH
3. 制作rootfs
rootfs可以放在 net(网络接口,eg.nfs/tftp), nand/nor flash, sd/emmc(mmc接口), hard disk 中, 通常包含了 1.各种命令(shell) + 库(各种用途的库),2.字符设备等设备文件(驱动接口)3.配置脚本。busybox / buildroot 都是制作rootfs的工具.(制作完成我们再搞一个磁盘映像 or 拷贝到物理设备即可)。
3.1 下载busybox source并编译 去 https://busybox.net/downloads/ 下载 busybox源码,解压(下面几步骤在docker环境中build)
vim Makefile, #ARCH ?= arm, CROSS_COMPILE ?= arm-linux-gnueabi-
make defconfig
make menuconfig #图形化配置,可以在settings->build options将其编译为static
make -j4
make install #然后在当前目录下 _install 就有busybox生成的内容了(基本的命令和库)
3.2 制作rootfs内容
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/
3.3 制作磁盘映像并将数据拷贝到其中
dd if=/dev/zero of=vexpress.img bs=1M count=32
mkfs.ext3 vexpress.img
mount -t ext3 vexpress.img /mnt -o loop
cp -ra rootfs/* /mnt
umount /mnt
这里也附上制作多分区磁盘映像的方法
#制作一个含有两个分区的镜像,第一个分区放kernel+dtb,第二个分区是rootfs用于挂载
dd if=/dev/zero of=abc.img bs=1M count=64
sudo parted abc.img --script -- mklabel msdos #这三步用fdisk做也行
sudo parted abc.img --script -- mkpart primary fat32 2048s 40960s
sudo parted abc.img --script -- mkpart primary ext4 40961s -1
#fdisk abc.img #分两个区,2048-40960,40961-
#建立映射,然后格式化两个分区
sudo losetup -f --show abc.img
sudo kpartx -va /dev/loop0
sudo mkfs.vfat /dev/mapper/loop0p1
sudo mkfs.ext4 /dev/mapper/loop0p2
#mount到ubuntu主机
mkdir aa bb
sudo mount /dev/mapper/loop0p1 aa
sudo mount /dev/mapper/loop0p2 bb
然后拷贝rootfs到ext4分区。完成后umount并删除本地映射:
sudo umount aa
sudo umount bb
sudo kpartx -d /dev/loop0
sudo losetup -d /dev/loop0
rm -rf aa bb
4. qemu启动
4.1 qemu直接启动kernel
qemu提供了直接启动kernel的机制,可以不用第三方的bootloader。
#-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)
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel /home/fang/codes/linux/arch/arm/boot/zImage \
-dtb /home/fang/codes/linux/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-nographic \
-append "root=/dev/mmcblk0 rw console=ttyAMA0" \
-sd vexpress.img
4.2 qemu启动uboot,uboot启动kernel
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel /home/fang/codes/u-boot/u-boot \
-nographic
接下来一个重要的事情需要搞清楚, qemu启动加载uboot后, uboot如何加载内核? 内核又如何知道rootfs存放位置?
1. uboot 加载 kernel 可以通过 tftp 从 ubuntu主机拿到。或者从sd卡拿,但是sd卡已经存放了rootfs,没法和kernel image放一起,所以制作sd卡映像的时候需制作两个分区,一个放kernel+dtb(分区格式只要uboot认识即可),另一个放rootfs(必须是linux指定的ext格式)
2. 使用sd卡映像的方式操作起来简单,不需要网络。但是debug不方便。所以还是要有qemu和ubuntu主机网络通信的方法,不管是tftp kernel+dtb,还是kernel后面挂载nfs文件系统都比较方便debug。qemu可以通过tap实现与ubuntu互联
3. uboot如何加载内核的问题,可以在uboot中指定 bootcmd 环境变量。
4. 内核去哪挂载怎么挂载rootfs的问题,可以在uboot中设置bootargs(就是挂载rootfs时的参数),bootargs指示kernel起来后通过从mmcblock0p2获取rootfs,而qemu连接一个sd卡(mmc接口), 内容 xx.img 正是rootfs映像(或者nfs方式获取rootfs)
4.2.1 通过sd卡第一个分区获取kernel,第二分区获取rootfs
按照2.3节,制作含有两个分区的镜像,然后拷贝zImage uImage xx.dtb 到 fat分区,拷贝rootfs到ext4分区。uImage是专门给uboot用的make的时候要给 LOADADDR,并且uboot/tools/mkimage工具要添加到PATH。(但是看起来uboot可以使用bootz来启动zImage)。完成后启动qemu:
sudo /opt/qemu/bin/qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel ../u-boot-2017.07/u-boot \
-nographic \
-sd abc.img
uboot起来后
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 打开早期打印?)
4.2.2 通过tftp获取kernel, mmc获取rootfs
ubuntu和qemu打通网络,ubuntu需要配置好tap设备( ref: https://blog.csdn.net/aggresss/article/details/54948143),
sudo apt-get install uml-utilities #User-Mode Linux,使用它创建TAP
sudo tunctl -u root -t tap30 #在主机上创建一个网络设备
sudo ifconfig tap30 192.168.111.1 promisc up #配置网卡IP地址,并且混杂模式启用
顺便在ubuntu主机安装tftp服务,并配置
sudo apt-get install tftp-hpa tftpd-hpa xinetd
sudo vim /etc/default/tftpd-hpa ,
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/home/fang/tftpfiles"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="-l -c -s"
mkdir -p /home/fang/tftpfiles, 并 chmod 777 tftpfiles
/etc/init.d/tftpd-hpa restart
cp /home/fang/codes/linux/arch/arm/boot/dts/vexpress-v2p-ca9.dtb ~/tftpfiles/
cp /home/fang/codes/linux/arch/arm/boot/uImage ~/tftpfiles/
cp /home/fang/codes/linux/arch/arm/boot/zImage ~/tftpfiles/
qemu这边则指定好启动参数(就是虚拟的硬件配置)。参照博客的命令似乎不能识别 vlan 选项,原来是qemu v5.2.0新版本使用 -device 和 -netdev.(https://www.qemu.org/2018/05/31/nic-parameter/)。但是更改了启动命令如下,发现起来后还是无法使用网络设备。推断是qemu的配置还是没有搞好,鉴于大部分资料是都是关于使用 -net方式,所以无奈换为qemu v2.8.0。编译安装步骤和上面 00节 一样。最后的启动命令就是下面这个(需要配置好etc下的文件,不然ifconfig等命令无法使用。可以参照正点原子alpha驱动里关于busybox的章节):
sudo /opt/qemu/bin/qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel ../u-boot-2017.07/u-boot \
-nographic \
-sd vexpress2.img \
-net nic,vlan=0 -net tap,vlan=0,ifname=tap30,script=no,downscript=no
qemu启动uboot后,在uboot里设置一下环境参数
setenv ipaddr 192.168.111.2
#setenv ethaddr 00:04:9f:04:d2:35
setenv gatewayip 192.168.111.1
setenv netmask 255.255.255.0
setenv serverip 192.168.111.1
setenv bootargs 'init=/linuxrc root=/dev/mmcblk0p2 rw rootwait earlyprintk console=ttyAMA0'
saveenv
我把rootfs放在了mmc接口设备的第二个分区上了,所以root=/dev/mmcblk0p2(表示第0个mmc设备的第2个分区)。然后使用tftp命令拿kernel+dtb:
tftp 60003000 uImage
tftp 60500000 vexpress-v2p-ca9.dtb
bootm 60003000 - 60500000
进入系统后 ifconfig -a 应该可以看到多出来一个eth0,我们配置一下ip
ifconfig eth0 192.168.111.2 promisc up
ping 192.168.111.1 #ok
4.2.3 通过tftp获取kernel, nfs获取rootfs
大体上和4.2.2是一样的,不同在于
setenv bootargs 'console=ttyAMA0 root=/dev/nfs nfsroot=192.168.111.1:/home/fang/nfs_root,proto=tcp rw ip=192.168.111.2:192.168.111.1:192.168.111.1:255.255.255.0::eth0:off'
同时ubuntu主机上安装nfs服务(参考正点原子的nfs搭建):
sudo apt-get install nfs-kernel-server
sudo vim /etc/exports
#rwsync之间没有空格
/home/fang/nfs_root *(rw,sync,no_root_squash)
sudo /etc/init.d/nfs-kernel-server restart
---------------------------------------------------------------------------------------
qemuv5.2.0失败的例子
#sudo /opt/qemu/bin/qemu-system-arm \
# -M vexpress-a9 \
# -m 512M \
# -kernel /home/fang/codes/linux/arch/arm/boot/zImage \
# -dtb /home/fang/codes/linux/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
# -nographic -append "init=/linuxrc root=/dev/mmcblk0 rw rootwait earlyprintk console=ttyAMA0" \
# -sd vexpress.img \
# -device virtio-net-device,netdev=dev0,mac='00:00:00:01:00:01' \
# -netdev tap,ifname=tap30,id=dev0,script=no,downscript=no
#1.注意是sudo 了,不然貌似没法 access tap设备
#2.给kernel的参数中,添加了 init=/linuxrc, rootwait, earlyprintk
#3.我完善了etc中的文件,不然没法使用 ifconfig 等命令(看来网卡启动还需要etc中的一些配置)
#4.-device virtio-net-device, 这个名字不是随便取的,使用下面的方法查看
# qemu-system-arm -M vexpress-a9 -device help
# pci的设备好像没法使用,所以我在 net device中找了一个不使用pci bus的,就是 virtio-net-device
https://www.cnblogs.com/microxiami/p/11093276.html
https://www.qemu.org/docs/master/ https://wiki.qemu.org/Documentation https://www.cnblogs.com/bakari/p/7858029.html
https://github.com/aggresss/LKDemo 主板:https://www.arm.com/zh/products/tools/development-boards/versatile-express/motherboard-express.php 处理器子板:https://www.arm.com/zh/products/tools/development-boards/versatile-express/coretile-express.php 文档下载:http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.set.boards/index.html
# https://www.bilibili.com/video/BV1NJ411m7Ah/?spm_id_from=333.788.recommend_more_video.0
#深入uboot https://aggresss.blog.csdn.net/article/details/52753098 #基于 qemu 的 android 嵌入式全栈 https://blog.csdn.net/aggresss/category_6542756.html
<<from 51 to linux>> https://www.zhihu.com/column/c_1141746317452808192