TinyEMU源码分析之虚拟机初始化

TinyEMU源码分析之虚拟机初始化

  • 1 初始化结构参数
  • 2 配置RAM地址空间
  • 3 初始化设备
  • 4 拷贝BIOS和Kernel
  • 5 手动写入5条指令
  • 6 体验第一条指令的执行

本文属于《 TinyEMU模拟器基础系列教程》之一,欢迎查看其它文章。
本文中使用的代码,均为伪代码,删除了部分源码。

1 初始化结构参数

虚拟机的初始化,主要在virt_machine_init函数中完成。
virt_machine_init函数,如下:

static VirtMachine *riscv_machine_init(const VirtMachineParams *p)
{RISCVMachine *s;VIRTIODevice *blk_dev;VIRTIOBusDef vbus_s, *vbus = &vbus_s;// 初始化结构参数s->common.vmc = p->vmc;s->ram_size = p->ram_size;s->max_xlen = max_xlen;s->mem_map = phys_mem_map_init();s->mem_map->opaque = s;s->mem_map->flush_tlb_write_range = riscv_flush_tlb_write_range;s->cpu_state = riscv_cpu_init(s->mem_map, max_xlen);// 配置RAM地址空间/* RAM */ram_flags = 0;cpu_register_ram(s->mem_map, RAM_BASE_ADDR, p->ram_size, ram_flags);cpu_register_ram(s->mem_map, 0x00000000, LOW_RAM_SIZE, 0);cpu_register_device(s->mem_map, CLINT_BASE_ADDR, CLINT_SIZE, s,clint_read, clint_write, DEVIO_SIZE32);cpu_register_device(s->mem_map, PLIC_BASE_ADDR, PLIC_SIZE, s,plic_read, plic_write, DEVIO_SIZE32);cpu_register_device(s->mem_map, HTIF_BASE_ADDR, 16,s, htif_read, htif_write, DEVIO_SIZE32);vbus->addr = VIRTIO_BASE_ADDR;// 初始化设备/* virtio console */if (p->console) {s->common.console_dev = virtio_console_init(vbus, p->console);vbus->addr += VIRTIO_SIZE;}...if (p->input_device) {// 键盘s->keyboard_dev = virtio_input_init(vbus,VIRTIO_INPUT_TYPE_KEYBOARD);vbus->addr += VIRTIO_SIZE;// 鼠标s->mouse_dev = virtio_input_init(vbus,VIRTIO_INPUT_TYPE_TABLET);vbus->addr += VIRTIO_SIZE;}// 拷贝BIOS和Kernel;手动写入5条指令copy_bios(s, p->files[VM_FILE_BIOS].buf, p->files[VM_FILE_BIOS].len,p->files[VM_FILE_KERNEL].buf, p->files[VM_FILE_KERNEL].len,p->files[VM_FILE_INITRD].buf, p->files[VM_FILE_INITRD].len,p->cmdline);return (VirtMachine *)s;
}

首先,初始化VirtMachineClass、ram大小、max_xlen,以及内存映射初始化等。
然后,在riscv_cpu_init函数中,会完成pc赋初值和TLB初始化(赋值为-1)。

s->pc = 0x1000; 
s->cpu_state = riscv_cpu_init(s->mem_map, max_xlen);

cpu_state的类型为RISCVCPUState结构,该结构中,包含mstatus、mtvec、mscratch等CSR寄存器定义。

我们猜测,第一条指令地址,就是0x1000

初始化结构参数,其实就是把一些参数,保存到RISCVMachine对象中。

2 配置RAM地址空间

我们对本部分代码,进行分析,并结合以下常量定义。

#define LOW_RAM_SIZE   0x00010000 /* 64KB */
#define RAM_BASE_ADDR  0x80000000
#define CLINT_BASE_ADDR 0x02000000
#define CLINT_SIZE      0x000c0000
#define HTIF_BASE_ADDR 0x40008000
#define IDE_BASE_ADDR  0x40009000
#define VIRTIO_BASE_ADDR 0x40010000
#define VIRTIO_SIZE      0x1000
#define VIRTIO_IRQ       1
#define PLIC_BASE_ADDR 0x40100000
#define PLIC_SIZE      0x00400000
#define FRAMEBUFFER_BASE_ADDR 0x41000000

发现代码,构成了,如下的内存地址空间:
在这里插入图片描述
这里,主要是,确定Low Dram、CLINT、HTIF、VBUS、PLIC、High Dram的地址空间范围(申请内存),可以结合上面代码,好好看看,比较简单。

因为,在执行指令时,必须要知道具体的内存空间,是如何分布的,以便正确访问内存。

3 初始化设备

初始化console、net device、block device、filesystem、display device、input device。
不详述,自己看源码。

4 拷贝BIOS和Kernel

在copy_bios函数中,完成拷贝BIOS和Kernel,其代码如下:

static void copy_bios(RISCVMachine *s, const uint8_t *buf, int buf_len,const uint8_t *kernel_buf, int kernel_buf_len,const uint8_t *initrd_buf, int initrd_buf_len,const char *cmd_line)
{// 拷贝BIOS到0x80000000ram_ptr = get_ram_ptr(s, RAM_BASE_ADDR, TRUE);memcpy(ram_ptr, buf, buf_len);// 拷贝Kernel到0x80200000kernel_base = 0;if (kernel_buf_len > 0) {/* copy the kernel if present */if (s->max_xlen == 32)align = 4 << 20; /* 4 MB page align */elsealign = 2 << 20; /* 2 MB page align */kernel_base = (buf_len + align - 1) & ~(align - 1);memcpy(ram_ptr + kernel_base, kernel_buf, kernel_buf_len);}// 创建设备树,并写入内存地址(0x1000+8*8)处ram_ptr = get_ram_ptr(s, 0, TRUE);fdt_addr = 0x1000 + 8 * 8;riscv_build_fdt(s, ram_ptr + fdt_addr,RAM_BASE_ADDR + kernel_base, kernel_buf_len,RAM_BASE_ADDR + initrd_base, initrd_buf_len,cmd_line);// 手动写入5条指令/* jump_addr = 0x80000000 */q = (uint32_t *)(ram_ptr + 0x1000);q[0] = 0x297 + 0x80000000 - 0x1000; /* auipc t0, jump_addr */q[1] = 0x597; /* auipc a1, dtb */q[2] = 0x58593 + ((fdt_addr - 4) << 20); /* addi a1, a1, dtb */q[3] = 0xf1402573; /* csrr a0, mhartid */q[4] = 0x00028067; /* jalr zero, t0, jump_addr */
}
  • 将bios(bbl64.bin)拷贝到0x80000000地址处(物理地址),本例中bbl64.bin长度为0xd21a。
  • 将kernel(kernel-riscv64.bin)拷贝到0x80200000地址处(物理地址),本例中kernel-riscv64.bin长度为0x3d5324。

拷贝BIOS和Kernel的地址,与上图中内存地址空间,一致。

5 手动写入5条指令

手动写入的5条指令,翻译过来,就是如下:

    /* jump_addr = 0x80000000 */// 从物理地址0x1000位置处开始,手动写入5条指令的机器码q = (uint32_t *)(ram_ptr + 0x1000);// t0=0x80000000q[0] = 0x297 + 0x80000000 - 0x1000; /* auipc t0, jump_addr */// a1=PCq[1] = 0x597; /* auipc a1, dtb */// a1=a1+0x3cq[2] = 0x58593 + ((fdt_addr - 4) << 20); /* addi a1, a1, dtb */// a0=mhartidq[3] = 0xf1402573; /* csrr a0, mhartid */// PC=t0q[4] = 0x00028067; /* jalr zero, t0, jump_addr */

从物理地址0x1000位置处开始,手动写入5条指令的机器码,一共20字节。
到这里,虚拟机的初始化,就完成了。

6 体验第一条指令的执行

通过目前源码的分析,可以得知,以下大致启动流程:

  • 第一条指令,从0x1000处,开始取指执行
  • 然后,以上这5条指令运行完毕,最后一条指令,设置PC=0x80000000,该地址,正是我们bbl64.bin,在内存中的基地址。
  • 也就是说,下一条指令,将跳转到bbl64.bin,执行指令。
  • 等待bbl64.bin执行完毕,再开始执行kernel-riscv64.bin。

我们可以在glue函数的,s->pc = GET_PC()位置处,打上断点,检查第一条指令的PC,的确是0x1000;并且依次取出的指令,的确为这5条指令。

static void no_inline glue(riscv_cpu_interp_x, XLEN)(RISCVCPUState *s, int n_cycles1)
{for(;;) {// 获取PCs->pc = GET_PC(); addr = s->pc;ptr = (uint8_t *)(s->tlb_code[tlb_idx].mem_addend +(uintptr_t)addr);code_ptr = ptr;//根据PC获取一条指令机器码insn = get_insn32(code_ptr); }
}

上述启动执行流程,如下图所示:
在这里插入图片描述

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

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

相关文章

vue2使用webSocket双向通讯

基于webSocket实现双向通信&#xff0c;使用webworker保持心跳。 由于浏览器的资源管理策略会暂停或限制某些资源的消耗&#xff0c;导致前端心跳包任务时效&#xff0c;后端接收不到webSocket心跳主动断开&#xff0c;因此需要使用webworker保持心跳 引入webworker npm insta…

关于安卓调用文件浏览器(一)打开并复制

背景 最近在做一个硬件产品&#xff0c;安卓应用开发。PM抽风&#xff0c;要求从app打开文件浏览器&#xff0c;跳转到指定目录&#xff0c;然后可以实现文件复制粘贴操作。 思考 从应用开发的角度看&#xff0c;从app打开系统文件浏览器并且选择文件&#xff0c;这是很常见…

.NET Framework 服务实现监控可观测性最佳实践

环境信息 系统环境&#xff1a;Windows Server开发语言&#xff1a;.NET Framework > 4.6.1APM探针包&#xff1a;ddtrace 准备工作 安装 Datakit 主机部署&#xff1a; 主机安装 - 观测云文档 打开采集 APM 采集器 Windows 主机配置 # 到如下路径&#xff0c;把ddtr…

广东省30m二级分类土地利用数据(矢量)

广东省&#xff0c;地处中国大陆最南部&#xff0c;属于东亚季风区&#xff0c;从北向南分别为中亚热带、南亚热带和热带气候&#xff0c;是中国光、热和水资源最丰富的地区之一。主要河系为珠江的西江、东江、北江和三角洲水系以及韩江水系。广东省面积为17.977万平方公里&…

vue+element 前端实现增删查改+分页,不调用后端

前端实现增删查改分页&#xff0c;不调用后端。 大概就是对数组内的数据进行增删查改分页 没调什么样式&#xff0c;不想写后端&#xff0c;当做练习 <template><div><!-- 查询 --><el-form :inline"true" :model"formQuery">&l…

高性能 MySQL 第四版(GPT 重译)(一)

前言 由 Oracle 维护的官方文档为您提供了安装、配置和与 MySQL 交互所需的知识。本书作为该文档的伴侣&#xff0c;帮助您了解如何最好地利用 MySQL 作为强大的数据平台来满足您的用例需求。 本版还扩展了合规性和安全性在操作数据库足迹中的日益重要作用。隐私法律和数据主…

使用Java版工程行业管理系统源码,提升工程项目的综合管理能力

工程项目管理涉及众多环节和角色&#xff0c;如何实现高效协同和信息共享是关键。本文将介绍一个采用先进技术框架的Java版工程项目管理系统&#xff0c;该系统支持前后端分离&#xff0c;功能全面&#xff0c;可满足不同角色的需求。从项目进度图表到施工地图&#xff0c;再到…

Java------数据结构之栈与队列(简单讲解)

本篇碎碎念&#xff1a;时隔n个月&#xff0c;继续写博客&#xff0c;假期落下的进度&#xff0c;在开学后努力追赶&#xff0c;假期不努力&#xff0c;开学徒伤悲啊&#xff0c;此时此刻真想对自己说一句&#xff0c;活该啊~~~~ 欠下的链表练习题讲解会在下次更新~~~~ 今日份励…

Ubuntu20.04 安装fcitx5输入法

序 ubuntu 20.04.3下fcitx5 需要从flatpak安装&#xff0c;&#xff08;由于qt版本&#xff0c;fcitx5-config只能安装在20.10上&#xff09;&#xff0c;中间出了各种问题&#xff0c;最后发现以下解决方案最好&#xff1a; 安装flatpak (建议使用官方ppa,版本较新) 1 2 3 …

视频素材库网站下载的地方在哪里?

视频素材库网站下载的地方在哪里&#xff1f;这是很多短视频创作者都会遇到的问题。别着急&#xff0c;今天我就来给大家介绍几个视频素材库网站下载的好去处&#xff0c;让你的视频创作更加轻松有趣&#xff01; 蛙学网&#xff1a;视频素材库网站下载的一定要选择蛙学网啦&am…

C++利用开散列哈希表封装unordered_set,unordered_map

C利用开散列哈希表封装unordered_set,unordered_map 一.前言1.开散列的哈希表完整代码 二.模板参数1.HashNode的改造2.封装unordered_set和unordered_map的第一步1.unordered_set2.unordered_map 3.HashTable 三.string的哈希函数的模板特化四.迭代器类1.operator运算符重载1.动…

【计算机考研】杭电 vs 浙工大 怎么选?

想求稳上岸的话&#xff0c;其他几所学校也可以考虑&#xff0c;以留在本地工作的角度考虑&#xff0c;这几所学校都能满足你的需求。 如果之后想谋求一份好工作&#xff0c;肯定优先杭电是比较稳的&#xff0c;当然复习的时候也得加把劲。 这个也可以酌情考虑&#xff0c;报…

C# 设置AutoScroll为true没效果的原因分析和解决办法

C#中添加tabControl 分页&#xff0c;将autoscroll设置为true发现缩小窗口没有滚动条效果。该问题出现后&#xff0c;检索发现也有很多人询问了该问题&#xff0c;但是都没有给出解决方案。 原因是内部button的属性Anchor设置为top、left、right、bottom导致的缩小界面窗口也没…

为什么Hashtable不允许插入nuIl键和null值?

1、典型回答 浅层次的来回答这个问题的答案是&#xff0c;JDK 源码不支持 Hashtable 插入 value 值为 null&#xff0c;如以下 JDK 源码所示&#xff1a; 也就是 JDK 源码规定了&#xff0c;如果你给 Hashtable 插入 value 值为 null 就会抛出空指针异常。 并且看上面的 JDK …

【Ubuntu】FTP站点搭建

配置顺序 前提条件&#xff1a;确保软件仓库可以正常使用&#xff0c;确保已正常配置IP地址 1.安装FTP服务 2.编辑FTP配置文件 3.设置开机自启 4.创建用户 5.配置用户限制名单 6.重启服务 7.查看运行状态 8.测试在同一局域网下的Windows查看文件 1.安装FTP服务 sudo apt insta…

【STM32嵌入式系统设计与开发】——8usart(串口通讯实验)

这里写目录标题 一、任务描述二、任务实施1、ActiveBeep工程文件夹创建2、函数编辑&#xff08;1&#xff09;主函数编辑&#xff08;2&#xff09;USART1初始化函数(usart1_init())&#xff08;3&#xff09;USART数据发送函数&#xff08; USART1_Send_Data&#xff08;&…

Tempo Talents | 创新专业建设方案,赋能高校4+N大数据学科人才培养

数字经济成为国家战略&#xff0c;是新一轮的经济发展引擎&#xff0c;数字人才、复合型人才成为发展的关键和核心要素。各级政府、区域开始以区域产业为导向&#xff0c;培育、聚集产业所需的数智化人才。 高校作为人才培养的重要基地&#xff0c;也发挥着不可或缺的作用。他…

Linux进程控制(一)

一、fork函数 在linux中&#xff0c;父进程通过fork函数创建子进程&#xff0c;子进程返回0&#xff0c;父进程返回子进程的pid&#xff0c;出现错误返回-1。 当运行fork函数时&#xff0c;OS会为子进程创建task_struct、mm_struct&#xff08;进程地址空间&#xff09;、页表&…

C语言实现高精度计时和高精度延时微秒级别

C语言实现高精度计时和高精度延时微秒级别 目的说明环境说明一、高精度延时(微秒级别)二、测试例程三、测试结果 目的说明 在Windows下C语言实现高精度计时功能和高精度延时微秒级别环境说明 Dev-C V5.11一、高精度延时(微秒级别) void vDelayUS(u32 usDelay) {LARGE_INTEGER…

C语言例:表达式10<<3+1的值

10的二进制 00001010 10<<3 01010000 十制左移m位&#xff0c;乘以。 0101 0000 十进制80 10<<31 81