qemu构建嵌入式环境_嵌入式qemu-程序员宅基地

00. 编译qemu


下载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 。其中我们主要关注 uart 和 lcd 两个外设。因为组成cortex-A系列最小可运行系统的硬件应该就是

  • 1.CPU
  • 2.DDR/SDRAM (sd / emmc / nand or nor flash / net / ... 均不为必须,但要有一个)
  • 3.uart (控制输入)
  • 4.lcd (显示输出,其实也不是必须)

对于vexpress-a9仿真出来的其他外设(usb/wifi/net/gpio/...),可以根据需要添加(自己添加kernel驱动)

1. qemu启动kernel

1.1 安装环境依赖包

qemu提供了直接运行kernel的机制。所以这里跳过bootloader试试直接运行kernel。(我ubuntu主机中有一个for arm-cortex-a64的交叉编译工具链aarch64, 有一个for arm-cortex-m的交叉编译工具链, 可以在docker中安装一个 for arm-cortex-a32 的交叉编译工具链 arm-linux-guneabi-gcc)

sudo docker run -ti -v /:/mnt:rw ubuntu /bin/bash
apt-get update
apt-get install vim bc build-essential gcc-arm-linux-gnueabi libncurses5-dev bison flex

安装完成之后, arm-linux-gnueabi-gcc -v, 以后编译我们就用docker。

1.2 下载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顶层目录,

1.3 编译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

1.4 运行qemu启动kernel

#-M 使用qemu仿真 vexpress-a9 machine,
#-m 指定qemu虚拟机内存 512M
#-kernel 指定 qemu使用的kernel image
#-dtb 指定 qemu boot kernel 时使用的设备树
#-nographic 不使用图形化界面(串口输出)
#-append "xx"  指定kernel启动参数(串口输出参数)
qemu-system-arm \
    -M vexpress-a9 \
    -m 512M \
    -kernel arch/arm/boot/zImage \
    -dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
    -nographic \
    -append "console=ttyAMA0"

正常情况应该进入kernel打印 Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0),表示没有文件系统可以挂载,退出qemu就 ps -a 并 kill xx(或者  ps -A | grep qemu-system-arm | awk '{print $1}' | xargs sudo kill)。下面在直接启动kernel的基础上制作一个rootfs镜像。


2. 制作rootfs

rootfs可以放在 net(网络接口,eg.nfs/tftp), nand/nor flash, sd/emmc(mmc接口), hard disk 中, 通常包含以下内容

  • 1.命令(shell) + 库(各种用途的库)
  • 2.字符设备等设备文件
  • 3.配置脚本

busybox / buildroot 都是制作rootfs的工具.(制作完成我们再搞一个磁盘映像 or 拷贝到物理设备即可)。

2.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生成的内容了

busybox生成了上面所说的 "1.命令(shell)+库" 的基本版。

2.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/

2.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

qemu启动kernel - rootfs

#-append指定的kernel启动参数里,root=/dev/mmcblk0 告诉kernel,rootfs文件系统映像在 /dev/mmcblk0
#  sd/emmc设备都是mmc接口,mmc接口的第一个设备就是mmcblk0
#  rw表示文件系统挂载的时候以可读写方式挂载
#-sd 指示qemu的硬件连接状态,连接了一个sd卡(mmc接口),sd卡中的映像内容是 vexpress.img
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

-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)。

启动lcd版本的qemu,除了需要去掉 -nographic 外, 还需要将 console=ttyAMA0 改为 console=tty0,因为标准终端已经重定向到lcd了(设备则由/dev/ttyAMA0变为/dev/tty0了)

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 \
    -append "root=/dev/mmcblk0 rw console=tty0" \
    -sd vexpress.img

3. qemu启动uboot-kernel-rootfs

真实的嵌入式环境,一般都是bootloader引导kernel,kernel挂载rootfs。所以这里用qemu仿真这个完整的过程。

下载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

make uboot (同样放在docker中进行)

vim config.mk,  #ARCH := arm
vim Makefile,   #CROSS_COMPILE ?= arm-linux-gnueabi-
make vexpress_ca9x4_defconfig
make -j4

使用qemu启动uboot

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)

 

3.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个分区

3.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/)。但是更改了启动命令如下,发现起来后还是无法使用网络设备

#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 (rootwait 表示等待 mmc 设备初始化完成以后再挂载, 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

推断是qemu的配置还是没有搞好,鉴于大部分资料是都是关于使用 -net方式,所以我换为qemu v2.8.0。编译安装步骤和上面 00节 一样。最后的启动命令就是下面这个:

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

 

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

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ddddfang/article/details/112862116

智能推荐

常见安全漏洞及测试方法_sessionid 垂直权限问题-程序员宅基地

文章浏览阅读3.8k次。常见安全漏洞及测试方法垂直权限问题及测试方法垂直权限漏洞是指Web应用没有做权限控制,或仅仅在菜单上做了权限控制,导致恶意用户只要猜到了其他页面的URL,就可以访问或控制其他角色拥有的数据或页面,达到权限提升的目的。业务测试过程中,要设计此场景安全测试用例,并切实落地执行:用户A登录后,打开浏览器NetWork,查看所有XHR网络请求。用户B登录后,A的请求,进行重复操作,若可以正当操作,则表示接口存在越权问题。CR过程,要针对有update、delete、add接口做着重观察。.._sessionid 垂直权限问题

Visionpro实现多目标测量_dim mylabels as arraylist在visionpro中的含义-程序员宅基地

文章浏览阅读2k次。VisionPro实现多目标测量其效果如图所示:VB代码如下:Imports SystemImports System.CollectionsImports Cognex.VisionProImports Cognex.VisionPro3DImports Cognex.VisionPro.ToolGroupImports Cognex.VisionPro.BlobImports Cognex.VisionPro.CaliperPublic Class UserScript_dim mylabels as arraylist在visionpro中的含义

dev多行注释_5.Python注释(多行注释和单行注释)用法详解-程序员宅基地

文章浏览阅读139次。Python 中使用井号(‘#’)作为单行注释的符号,语法格式为:# 注释内容也就是说,从符号‘#’处开始,直到换行处结束,此部分内容都作为注释的内容,当程序执行时,这部分内容会被忽略。单行注释放置的位置,既可以是要注释代码的前一行,例如:#这是一行简单的注释print ("Hello World!")也可以是注释代码的右侧,例如:print ("Hello World!") #这是一行简单的注释..._dev多行注释符号

Hibernate 面试中最常考察的知识点整合-程序员宅基地

文章浏览阅读48次。为什么80%的码农都做不了架构师?>>> ...

40.e2studio的初次使用(烧范例)_e2studio教程-程序员宅基地

文章浏览阅读7k次。一,初次使用的一些配置首次使用,先点help-瑞萨工具链,看看工具都在不在。列出来了就表示添加了。(前面的空白方格真是误导人)安装基础软件包特殊应用软件包也加了其中三个,除了一个叫Guiliani的不知哪国语言的SDK。偶然发现按浏览器的后退键同时快速下拉,可以看到这个标签页的前面所有网页。软件包层级如下:这是解压后的软件包二,导入一个项目瞅瞅按..._e2studio教程

Lambda-Serverless应用的本地开发_lambda 本地开发-程序员宅基地

文章浏览阅读1k次。Serverless应用的Local开发示例云上开发本地开发构成图必要包的安装配置 yml 文件使插件有效安装 DynamoDB Local无结果解决DynamoDB Local 表内容添加添加DynamoDB Local 的表定义到 yml 配置文件DynamoDB Local 启动出现 Error解决DynamoDB Local 启动local api-gateway 启动启动确认资源删除..._lambda 本地开发

随便推点

[AutoSar]AutoSar 系统上电启动过程 (主要参与模块 EcuM,BswM,ComM)_ecum上电流程为初始化驱动模块-程序员宅基地

文章浏览阅读4.5k次,点赞8次,收藏49次。AutoSar 系统上电启动过程StartUP:实现无需OS支持的底层硬件驱动初始化,初始化部分为init Block0和init Block1即StartUP1,总结为EcuM初始化;需要OS支持和不需要使用NVM的BSW模块初始化,初始化部分为init Block2,需要OS支持和需要使用NVM的BSW模块初始化,初始化部分为init Block3,即StartUP2,总结为BswM初始化。RUN:完成所有的BSW模块初始化,可以执行SWC程序;SWC可以向ECUM模块请求Run request_ecum上电流程为初始化驱动模块

用keybd_event & mouse_event & setcursorpos 摸拟键盘输入以及鼠标的移动。_setcursorpos 模拟-程序员宅基地

文章浏览阅读2.9k次。今天在实现wince的复制 粘贴功能时想到利用系统自带的这些功能,模拟键盘功能。现在VC下用VC测试当我用keybd_event(VK_CONTROL, 0, 0, 0);keybd_event('a', 0, 0, 0);keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0);k_setcursorpos 模拟

RESTful API 设计最佳实践_restfulapi的设计-程序员宅基地

文章浏览阅读238次。分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&a_restfulapi的设计

STC89C52驱动W25Q32测试笔记-程序员宅基地

文章浏览阅读2.7k次,点赞6次,收藏27次。STC89C52驱动W25Q32_w25q32

js_02 windows对象及函数_js 如何自定义window方法-程序员宅基地

文章浏览阅读1.2k次。文章目录1.函数2.函数的分类2.1 系统函数2.2 自定义函数3.windows 对象3.1常用属性3.2常用方法3.3常用事件4.Date 对象4.1Date对象的方法4.2Data对象的使用1.函数函数的含义:类似于Java中的方法,是完成特定任务的代码语句块使用更简单:不用定义属于某个类,直接使用2.函数的分类2.1 系统函数parseInt (“字符串”)将字符串转换为整型数字如: parseInt (“86”)将字符串“86”转换为整型值86parseFloat(“字符串_js 如何自定义window方法

python循环删除_python 循环删除不死马php-程序员宅基地

文章浏览阅读178次。想要python循环删除不出问题 ,倒序删除即可正序删除x = [1, 2, 3, 4]for i in x: if i == 2 or i == 3: x.remove(i)print x执行结果倒序删除x = [1, 2, 3, 4]for i in x[::-1]: if i == 2 or i == 3: x.remove(i)print x执行结果至于原因python中循环删除列表中元素时的坑!这篇文章里面写的很清_python 循环删除不死马php