(2023)从零开始用qemu搭建虚拟arm环境

news2024/12/22 23:34:23

用qemu搭建虚拟arm环境

  • 引言
    • 安装版本
  • 1. VMware + ubuntu20.04 + qemu安装
  • 2.安装交叉编译工具
  • 3.编译内核kernel
  • 4.u-boot编译
  • 5.制作根文件系统
    • 第一步:下载、编译和安装busybox
    • 第二步:形成根目录结构
    • 第三步:制作根文件系统镜像
  • 测试HelloWorld应用程序
  • 如何关闭qemu虚拟机
  • 补充:
    • **关于”make: arm-linux-gnueabihf-gcc: Command not found“问题**
    • 关于qemu启动ARM虚拟机运行指令解析
  • 参考文献:

引言

Qemu是什么?
Qemu是一个开源的托管虚拟机,通过纯软件来实现虚拟化模拟器,几乎可以模拟任何硬件设备。比如:Qemu可以模拟出一个ARM系统中的:CPU、内存、IO设备等,然后在这个模拟层之上,可以跑一台ARM虚拟机,这个ARM虚拟机认为自己在和硬件进行打交道,但实际上这些硬件都是Qemu模拟出来的。
在这里插入图片描述
正因为Qemu是纯软件实现的,所有的指令都要经过它的转换,所以性能非常低。所以在生产环境中,大多数的做法都是配合KVM来完成虚拟化工作,因为KVM是硬件辅助的虚拟化技术,主要负责比较繁琐的CPU和内存虚拟化,而Qemu则负责I/O虚拟化,两者合作各自发挥自身的优势,相得益彰。这部分不是重点,就不具体深入介绍了。

Qemu的两种模式
1.用户模式(User mode):利用动态代码翻译机制来执行不同主机架构的代码,例如:在x86平台上模拟执行ARM代码,也就是说:我们写一条ARM指令,传入整个模拟器中,模拟器会把整个指令翻译成x86平台的指令,然后在x86的CPU中执行。

2.系统模式(System mode):模拟整个电脑系统,利用其它VMM(Xen, KVM)来使用硬件提供的虚拟化支持,创建接近于主机性能的全功能虚拟机。
在这里插入图片描述
使用Qemu虚拟机的几种选择
利用Qemu来运行ARM虚拟机,你有2个选择:

简单方式:直接下载别人编译好的映像文件(包含了内核,根文件系统),直接执行即可。
缺点是:别人编译好的也许不适合你的需求,没法定制。

复杂方式:自己下载内核代码、根文件系统代码(例如:busybox),然后进行编译。
优点是:可以按照自己的实际需求,对内核、根文件系统机型裁剪。

在第2种复杂模式中,又可以有2个选择:
2-1. 内核代码、根文件系统代码全部自己手动编译,最后把这些编译结果手动组织在一个文件夹中,形成自己的根目录;
2-2. 利用 buildroot 整个框架,只需要手动进行配置(比如:交叉编译器在本机上的位置、输出路径、系统的裁剪),然后就可以一键编译出一个完整的系统,可以直接烧写到机器!

安装版本

VMware Workstation Pro:16.1.1
ubuntu:20.04 64位
qemu:8.1.50

搭建虚拟arm开发板:
busybox:1.36.0
kernel:5.10
u-boot:2020.10

1. VMware + ubuntu20.04 + qemu安装

参考这篇文章:【VMware + ubuntu20.04 + qemu安装】

下面将正式开始搭建虚拟的arm开发板环境

2.安装交叉编译工具

交叉编译器的作用就不需要详细解释了,因为我们是在x86平台上进行编译,而运行的平台是ARM系统,这2个平台的指令集不一样,所以需要交叉编译得到ARM系统上可以执行的程序。

sudo apt-get install gcc-arm-linux-gnueabi

验证安装结果

dpkg -l gcc-arm-linux-gnueabi

显示如下:
在这里插入图片描述

3.编译内核kernel

下载内核kernel压缩包

wget https://mirror.bjtu.edu.cn/kernel/linux/kernel/v5.x/linux-5.10.tar.xz

这里我们使用 vexpress-a9 这款开发板。vexpress-a9 是 Arm 公司自己设计的一款 4 核 Cortex-A9 开发板,U-Boot、Linux Kernel 和 QEMU 对这款开发板都做了完整的支持。当然,如果想搭其它开发板,也不难,只要qemu和内核对它有成熟的支持就够了。
解压:

tar -xvf linux-5.10.tar.xz

在解压后的linux-5.10目录下,生成vexpress开发板子的config文件:

make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm vexpress_defconfig

编译32位kernel:

make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm

生成的内核镱像位于arch/arm/boot/zImage
设备树 arch/arm/boot/dts/vexpress-v2p-ca9.dtb

补充:编译64位kernel


make ARCH=arm64 defconfig CROSS_COMPILE=aarch64-linux-gnu-
 
# 如果需要调整配置选项,则使用menuconfig
make ARCH=arm64 menuconfig CROSS_COMPILE=aarch64-linux-gnu-
 
make ARCH=arm64 Image -j8 CROSS_COMPILE=aarch64-linux-gnu-

生成的内核镱像位于arch/arm/boot/Image

4.u-boot编译

拉取:

wget https://ftp.denx.de/pub/u-boot/u-boot-2020.10.tar.bz2

下载完后,可以看到 configs 目录下有针对这款开发板的配置文件。ca9x4表示cortexA9架构,4核心,vexpress_ca9x4_defconfig

ls configs/ | grep vexpress

在这里插入图片描述
编译:

make vexpress_ca9x4_defconfig
make CROSS_COMPILE=arm-linux-gnueabihf- all

最终编译生成 elf 格式的可执行文件 u-boot 和纯二进制文件u-boot.bin,其中 QEMU 可以启动的为 elf 格式的可执行文件 u-boot 。
在这里插入图片描述

5.制作根文件系统

内核在启动之后、执行到最后步骤时,需要挂载根文件系统,然后执行文件系统中指定的执行程序(初始化程序,本文未设置),例如:/etc/rc.local。

如果没有跟文件系统,那么内核在执行到最后就提示:panic…。

根文件系统放在哪里?
其实依赖于每个开发板支持的存储设备,可以放到Nor Flash上,也可以放到SD卡,甚至外部磁盘上。最关键的一点是你要清楚知道开发板有什么存储设备。

本文介绍了使用SD卡做为存储空间,文件格式为ext3格式。

第一步:下载、编译和安装busybox

下载busybox1.36.0:

 wget http://www.busybox.net/downloads/busybox-1.36.0.tar.bz2

编译,安装:

make defconfig

make CROSS_COMPILE=arm-linux-gnueabi-

make install CROSS_COMPILE=arm-linux-gnueabi-

安装完成后,会在busybox目录下生成_install目录,该目录下的程序就是单板运行所需要的命令。

第二步:形成根目录结构

先在Ubuntu主机环境下,形成目录结构,里面存放的文件和目录与单板上运行所需要的目录结构完全一样,然后再打包成镜像(在开发板看来就是SD卡),这个临时的目录结构称为根目录。

  1. 首先创建rootfs目录(根目录),根文件系统内的文件全部放到这里:
mkdir -p rootfs/{dev,etc/init.d,lib}
  1. 把busybox中的文件复制到rootfs根目录下,主要是一些基本的命令:
sudo cp busybox-1.20.2/_install/* -r rootfs/
  1. 把交叉编译工具链中的库文件复制到rootfs根目录的lib文件夹下:
sudo cp -P /usr/arm-linux-gnueabi/lib/* rootfs/lib/
  1. 创建4个tty端终设备:
sudo mknod rootfs/dev/tty1 c 4 1

sudo mknod rootfs/dev/tty2 c 4 2

sudo mknod rootfs/dev/tty3 c 4 3

sudo mknod rootfs/dev/tty4 c 4 4

第三步:制作根文件系统镜像

制作根文件系统镜像 根文件系统镜像就相当于一个硬盘,就是把上面rootfs根目录中的所有文件复制到这个硬盘中。

第一种:
生成512M大小的镜像:

dd if=/dev/zero of=a9rootfs.ext3 bs=1M count=32

格式化成ext3文件系统:

mkfs.ext3 a9rootfs.ext3

挂载,将文件拷贝到镜像中:

sudo mkdir tmpfs

sudo mount -t ext3 a9rootfs.ext3 tmpfs/ -o loop

sudo cp -r rootfs/*  tmpfs/

sudo umount tmpfs

qemu启动ARM虚拟机运行:

qemu-system-arm -M vexpress-a9 -m 512M -kernel /path/to/kernel/dir/arch/arm/boot/zImage -dtb  /path/to/kernel/dir/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -append "root=/dev/mmcblk0  console=ttyAMA0" -sd a9rootfs.ext3

“从内核启动打印,到命令行提示符出现”
在这里插入图片描述

第二种:
生成512M大小的磁盘镜像:

qemu-img create -f raw disk.img 512M

把磁盘镜像格式化成ext4文件系统:

mkfs -t ext4 ./disk.img

将rootfs根目录中的所有文件复制到磁盘镜像中 操作步骤是:创建挂载点-挂载-复制文件-卸载:

mkdir tmpfs 
sudo mount -o loop ./disk.img tmpfs/  
sudo cp -r rootfs/* tmpfs/
sudo umount tmpfs

使用file指令检查一下:

file disk.img

在这里插入图片描述
qemu启动ARM虚拟机运行:

qemu-system-arm -M vexpress-a9 -m 512M -kernel /path/to/kernel/dir/arch/arm/boot/zImage -dtb  /path/to/kernel/dir/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -append "root=/dev/mmcblk0 rw console=ttyAMA0" -sd disk.img

“从内核启动打印,到命令行提示符出现”

测试HelloWorld应用程序

在Ubuntu任意一个目录,编写HelloWorld可执行程序hello.c:

touch hello.c
vi hello.c

按i进入输入模式,复制下面代码,然后esc,shift+:,wq保存退出:

#include <stdio.h> 
int main() 
{     
    printf("HelloWorld! \n");
    return 0; 
}

交叉编译hello.c,得到arm的可执行程序hello:

arm-linux-gnueabi-gcc hello.c -o hello

通过file指令,查看一下hello程序:

file hello

在这里插入图片描述
把hello可执行程序复制到磁盘镜像disk.img中 操作步骤是:挂载-复制文件-卸载:

sudo mount -o loop ./disk.img tmpfs/  
cp hello tmpfs/ 
sudo umount tmpfs

执行hello程序 再次启动虚拟机,此时可以在根目录下面看到hello文件,直接执行即可看到输出结果。
在这里插入图片描述

总结:
在以上的操作步骤中,我们把一个ARM系统在启动应用程序之前,所需要的程序都手动编译、操作了一遍。看一遍很容易就明白,亲手操作一遍印象会更深刻。

这里的操作过程有些还需要继续深入,比如:在系统启动之后,自动挂载宿主机(Ubuntu系统)中的某个文件夹,这样就可以把hello等可执行程序复制到挂载目录中,然后在ARM系统中直接执行了,而不用再执行下面在一连串的操作(停止虚拟机-挂载磁盘镜像-复制文件-卸载-启动虚拟机)。

如何关闭qemu虚拟机

停止虚拟机 在Ubuntu另一个终端窗口中,通过killall指令来停止。

killall qemu-system-arm

或者直接 Ctrl+a 再按x,直接退出qemu环境。

补充:

关于”make: arm-linux-gnueabihf-gcc: Command not found“问题

解决方法:sudo apt-get install gcc-arm*
参考:https://blog.csdn.net/HGGshiwo/article/details/120479087

关于qemu启动ARM虚拟机运行指令解析

qemu-system-arm -M vexpress-a9 -m 512M -kernel /path/to/kernel/dir/arch/arm/boot/zImage -dtb  /path/to/kernel/dir/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic -append "root=/dev/mmcblk0  console=ttyAMA0" -sd a9rootfs.ext3

-M vexpress-a9 模拟vexpress-a9单板,你可以使用-M ?参数来获取该qemu版本支持的所有单板

-m 512M 单板运行物理内存512M

-kernel /path/to/kernel/dir/arch/arm/boot/zImage 告诉qemu单板运行内核镜像路径

-nographic 不使用图形化界面,只使用串口

-append “console=ttyAMA0” 内核启动参数,这里告诉内核vexpress单板运行,串口设备是那个tty。

因为不同单板串口驱动类型不尽相同,创建的tty设备名当然也是不相同的。那vexpress单板的tty设备名是哪个呢? 其实这个值可以从生成的.config文件CONFIG_CONSOLE宏找到。
如果搭建其它单板,需要注意内核启动参数的console=参数值,同样地,可从生成的.config文件中找到。

参考文献:

一步步教你:如何用Qemu来模拟ARM系统

从零使用qemu模拟器搭建arm运行环境

【十】搭建基于qemu的仿真环境与应用

如果想了解更多细节,包括:创建设备结点,设置初始化进程/etc/rcS,u-boot加载内核等,可参考:
【宅学部落的qemu教程】

或者其他优秀教程
Linux利器:QEMU!用它模拟开发板能替代真开发板?
qemu-system-arm功能选项整理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1508492.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Servlet API 详细讲解

Servlet API 详细讲解 文章目录 Servlet API 详细讲解1. HttpServlet2.HttpServletRequest服务器如何获取到 query string 和 body 的数据 &#xff1f;&#xff1f; 3.HttpServletResponse API就是一组类和方法的集合&#xff0c;servlet 中的 类是非常多的&#xff0c;咱们只…

Python实现图片(合并)转PDF

在日常的工作和学习过程当中,我相信很多人遇到过这样一个很普通的需求,就是将某一个图片转为PDF或者是将多个图片合并到一个PDF文件。但是,在苦苦搜寻一圈之后发现要么要下载软件,下载了还要注册,注册了还要VIP,甚至SVIP才能实现这样的需求! 今天,我带大家把这个功能打…

【学习笔记】VMware vSphere 6.7虚拟化入门

VMware vSphere 6.7虚拟化入门课程介绍 课程内容 1、VMware vSphere 6.7虚拟化入门课程介绍 2、ESXi6.7控制台设置 3、使用vSpkere Host client管理虚拟机 4、VMware EsXi基础操作 5、VMware Esxi存储管理 6、管理ESXi主机网络与虚拟机网络 7、安装配置vCenter Server Applia…

neo4j网页无法打开,启动一会儿后自动关闭,查看neo4j status显示Neo4j is not running.

目录 前情提要User limit of inotify watches reached无法访问此网站 前情提要 公司停电&#xff0c;服务器未能幸免&#xff0c;发现无法访问此网站&#xff0c;http://0.0.0.0:7474 在此之前都还好着 User limit of inotify watches reached (base) [rootlocalhost ~]# n…

利用MyRandom函数求一组指定个数的随机返回数

《VBA信息获取与处理》教程(版权10178984)是我推出第六套教程&#xff0c;目前已经是第一版修订了。这套教程定位于最高级&#xff0c;是学完初级&#xff0c;中级后的教程。这部教程给大家讲解的内容有&#xff1a;跨应用程序信息获得、随机信息的利用、电子邮件的发送、VBA互…

搭建prometheus、grafana监控平台

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

【Qt】—— 信号与槽

目录 &#xff08;一&#xff09;信号和槽概述 1.1 信号的本质 1.2 槽的本质 &#xff08;二&#xff09;信号和槽的使用 2.1 信号和槽的连接 2.2 查看内置信号和槽 2.3 通过Qt Creator⽣成信号槽代码 &#xff08;三&#xff09;自定义信号和槽 3.1 基本语法 3.2 带参…

TCP多线程模型、IO模型(select、poll、epoll)

我要成为嵌入式高手之3月11日Linux高编第十九天&#xff01;&#xff01; ———————————————————————————— TCP并发模型 一、TCP多线程模型&#xff1a; 缺点&#xff1a;创建线程会带来资源开销&#xff0c;能够现的并发量比较有限 二、IO模型&…

产品设计 - 尼尔森交互设计原则

文章目录 前言1. 状态可见原则1.1 理论介绍1.2 实践应用 2. 环境贴切原则2.1 理论介绍2.2 实践应用 3. 用户可控原则3.1 理论介绍3.2 实践应用 4. 一致性原则4.1 理论介绍4.2 实践应用 5. 易用原则5.1 理论介绍5.2 实践应用 6. 灵活高效原则6.1 理论介绍6.2 实际应用 7. 优美简…

opengl 学习(五)-----变换

变换 分类向量向量与标量运算向量取反向量加减向量相乘点乘叉乘 矩阵矩阵的加减矩阵的数乘矩阵相乘 矩阵与向量相乘单位矩阵缩放位移 旋转GLMdemo效果解析教程 分类 OpenGL C 向量 下面有一个解释的非常好的理解: 向量有一个方向(Direction)和大小(Magnitude&#xff0c;也叫…

网络编程(3/6)

使用C语言完成数据库的增删改 #include<myhead.h> int do_add(sqlite3 *ppDb) {int numb;char name[50];int salary;printf("请输入员工信息&#xff1a;工号、姓名、薪水\n");scanf("%d %s %d",&numb,name,&salary);char sql[128];char *e…

关于遗传力常见的误解

大家好&#xff0c;我是邓飞&#xff0c;今天看了一篇非常好的文章&#xff0c;介绍了遗传力相关概念和计算方法&#xff0c;里面提到了常见的误解&#xff0c;这里汇总一下。 文献链接&#xff1a;https://excellenceinbreeding.org/sites/default/files/manual/EiB-M2_Herit…

Ping工作原理

文章目录 目的ping网络协议 OSIICMP什么是ICMP作用功能报文类型查询报文类型差错报文类型ICMP 在 IPv4 和 IPv6 的封装ICMP 在 IPv4 协议中的封装ICMP 在 IPv6 协议中的封装ICMP 头部日常ping 排除步骤ping 查询报文使用code扩展目的 本文主要是梳理ping的工作原理- 揭开 ICMP…

FPGA AXI4总线操作教程

AXI&#xff08;Advanced Extensible Interface&#xff09;总线是一种高性能、低延迟的片上系统&#xff08;SoC&#xff09;接口标准&#xff0c;广泛应用于现代数字系统设计中。它允许不同的硬件组件以高效、可靠的方式进行数据传输和控制。本教程将介绍AXI总线的基本操作和…

C++面向对象..

1.面向对象的常见知识点 类、 对象、 成员变量(属性)、成员函数(方法)、 封装、继承、多态 2.类 在C中可以通过struct、class定义一个类 struct和class的区别&#xff1a; struct的默认权限是public(在C语言中struct内部是不可以定义函数的) 而class的默认权限是private(该权…

上位机图像处理和嵌入式模块部署(qmacvisual旋转和镜像)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 旋转和镜像是图像处理中经常遇到的一个情况。很多时候&#xff0c;摄像头面对物体进行拍摄&#xff0c;未必是正对着进行拍摄的&#xff0c;这个时…

谷粒商城【成神路】-【10】——缓存

目录 &#x1f9c2;1.引入缓存的优势 &#x1f953;2.哪些数据适合放入缓存 &#x1f32d;3.使用redis作为缓存组件 &#x1f37f;4.redis存在的问题 &#x1f9c8;5.添加本地锁 &#x1f95e;6.添加分布式锁 &#x1f95a;7.整合redisson作为分布式锁 &#x1f697…

JavaScript实现通过键盘弹钢琴的效果

本片文章通过触发键盘事件来触发对应的音乐&#xff0c;而且给页面添加了渐变的active类名&#xff0c;通过触发不同的鼠标事件&#xff0c;然后active类移动来实现按下钢琴键的视觉效果。 关键代码&#xff1a; <!DOCTYPE html> <html lang"en"><h…

提示并输入一个字符串,统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数要求使用C++风格字符串完成

#include <iostream> #include <array> using namespace std;int main() {cout<<"请输入一个字符串"<<endl;//array<string,100> str;string str;getline(cin,str);int daxie0,xiaoxie0,num0,space0,other0;int lenstr.size();;for(in…

Java随手记

equals和的区别 使用基本数据类型&#xff08;char&#xff0c;int&#xff0c;long等&#xff09;的时候&#xff0c;比较的是它们的值 使用引用数据类型的时候&#xff0c;比较的是地址 equals如果不重写&#xff0c;那么和 是没差别的 下面来看String的比较&#xff0c;这…