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字节。
到这里,虚拟机的初始化,就完成了。

关于a0与a1寄存器的含义:

  • a0 = mhartid:表示硬件线程ID。
  • a1 = a1 + 0x3c:设备树内存基址为0x1040(0x1000+8*8),而a1 = q[1]指令的PC + 0x3c = 0x1004 + 0x3c = 0x1040,正好为设备树基址,因此a1表示设备树基址。
  • 因此,a0与a1,表示后续调用riscv-pk\machine\minit.c中init_first_hart函数的2个参数。

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.rhkb.cn/news/282834.html

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

相关文章

【LeetCode】--- 动态规划 集训(一)

目录 一、1137. 第 N 个泰波那契数1.1 题目解析1.2 状态转移方程1.3 解题代码 二、面试题 08.01. 三步问题2.1 题目解析2.2 状态转移方程2.3 解题代码 三、746. 使用最小花费爬楼梯3.1 题目解析3.2 状态转移方程3.3 解题代码 一、1137. 第 N 个泰波那契数 题目地址&#xff1a…

Python中的线程池与进程池:并行编程的高效选择【第145篇—并行编程】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 Python中的线程池与进程池&#xff1a;并行编程的高效选择 在Python编程中&#xff0c;实现…

[Linux]条件变量:实现线程同步(什么是条件变量、为什么需要条件变量,怎么使用条件变量(接口)、例子,代码演示(生产者消费者模式))

目录 一、条件变量 1.什么是条件变量 故事说明 2、为什么需要使用条件变量 竞态条件 3.什么是同步 饥饿问题 二、条件变量的接口 1.pthread_cond_t 2.初始化&#xff08;pthread_cond_init&#xff09; 3.销毁&#xff08;pthread_cond_destroy&#xff09; 4.等待&…

联发科MT8797迅鲲1300T规格参数_MTK5G安卓核心板方案定制

联发科MT8797&#xff08;迅鲲1300T&#xff09;平台采用Arm Cortex-A78和Cortex-A55组成的八核架构CPU&#xff0c;以及Arm Mali-G77MC9九核GPU&#xff0c;集成了AI处理器MediaTek APU&#xff0c;支持5G Sub-6GHz全频段和5G双载波聚合,支持1.08亿像素拍照和多镜头组合,以及1…

《逆水寒》“公费追星”被骂上热搜,玩家为何如此抗拒剧游联动?

游戏行业最近真是风波不断。 《逆水寒》手游因为和武侠剧《莲花楼》深入联动而遭到玩家抵制&#xff0c;网易游戏测评总监被质疑“公费追星”&#xff0c;还波及到了成毅、陈都灵等多位演员。 尤其是《莲花楼》的男主角成毅&#xff0c;遭到大量《逆水寒》手游玩家的吐槽调侃…

Java项目:70 ssm小学生课外知识学习网站+vue

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 管理员&#xff1b;首页、个人中心、医护人员管理、科室管理、病人管理、病房管理、病人信息管理、病历管理、医嘱管理、手术安排管理、药品信…

Shell脚本学习-if循环

最小化的if语句 无实际用途 if [ ] ;then echo fi 脚本解释 if 判断 [ ] 里面的条件是否成立 后面跟then&#xff0c;代表条件成立 如果在一行则使用分号隔离&#xff08;;&#xff09; 如果不在一行使用则直接在下一行驶入then即可。 如果条件成立则输出echo 后面…

GEC6818——QT开发之两个UI界面切换与表格显示DHT11数据

GEC6818——QT开发之两个UI界面切换与表格显示DHT11数据 使用环境: ubantu16 QT5.7 开发板GEC6818 实现要求&#xff1a; 利用A53按键1、按键2与温湿度传感器完成QT界面动态显示温湿度记录&#xff0c;并指定温湿度记录超过指定范围&#xff0c;进行报警&#xff08;LED&#…

企业网络基础设施物理安全面临全新挑战

企业网络基础设施的物理安全是确保业务连续性和数据完整性的关键组成部分。随着技术的发展和环境的变化&#xff0c;这些基础设施面临着新的挑战。以下是一些主要的挑战和的解决方案 一、机房、仓库、档案馆物理安全事件频发的挑战&#xff1a; 1.电力安全事件&#xff1a;市…

HTML5+CSS3+JS小实例:创意罗盘时钟

实例:创意罗盘时钟 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=…

优惠:阿里云4核16G服务器优惠价格26.52元1个月、149.00元半年

阿里云4核16G服务器优惠价格26.52元1个月、79.56元3个月、149.00元半年&#xff0c;配置为阿里云服务器ECS经济型e实例ecs.e-c1m4.xlarge&#xff0c;4核16G、按固定带宽 10Mbs、100GB ESSD Entry系统盘&#xff0c;活动链接 aliyunfuwuqi.com/go/aliyun 活动链接打开如下图&a…

网络安全实训Day5

写在前面 昨天忘更新了......讲的内容不多&#xff0c;就一个NAT。 之前记的NAT的内容&#xff1a;blog.csdn.net/Yisitelz/article/details/131840119 网络安全实训-网络工程 NAT 公网地址与私网地址 公网地址 可以在互联网上被寻址&#xff0c;由运营商统一分配全球唯一的I…

苹果手机更换国内IP地址的方法

在网络世界中&#xff0c;IP地址扮演着极为重要的角色&#xff0c;是互联网通信的基础。很多人在使用苹果手机时&#xff0c;有时候需要更换国内IP地址以获取更多网络资源或保护隐私。那么&#xff0c;是否可以更换国内ip地址&#xff1f;苹果手机更换国内ip地址的方法是怎样的…

阿兹特克帝国社会结构和政治农业和经济宗教和文化西班牙征服和遗产特诺奇提特兰(Tenochtitlán)地理位置和建筑社会和经济水上花园和农业西班牙征服巴斯克人

目录 阿兹特克帝国 社会结构和政治 农业和经济 宗教和文化 西班牙征服和遗产 特诺奇提特兰&#xff08;Tenochtitln&#xff09; 地理位置和建筑 社会和经济 水上花园和农业 西班牙征服 巴斯克人 语言 历史 文化和社会 政治和自治 1. 新南威尔士大学最新资讯 …

vue学习日记14:工程化开发脚手架Vue CLI

一、概念 二、安装 1.全局安装&查看版本 注意启动cmd输入命令 要以管理员运行哦 安装了一次就行以后不用再创建了 yarn global addvue/cli vue --version 显示了版本号即可 2.创建项目架子 创建项目的路径在哪 项目就在哪 项目名字不能用中文 vue create project-n…

Legacy|电脑Windows系统如何迁移到新安装的硬盘?系统迁移详细教程!

前言 前面讲了很多很多关于安装系统、重装系统的教程。但唯独没有讲到电脑换了新的硬盘之后&#xff0c;怎么把旧系统迁移到新的硬盘上。 今天小白就来跟各位小伙伴详细唠唠&#xff1a; 开始之前需要把系统迁移的条件准备好&#xff0c;意思就是在WinPE系统下&#xff0c;可…

子组件自定义事件$emit实现新页面弹窗关闭之后父界面刷新

文章目录 需求弹窗关闭之后父界面刷新展示最新数据 实现方案AVUE 大文本默认展开slotVUE 自定义事件实现 父界面刷新那么如何用呢? 思路核心代码1. 事件定义2. 帕斯卡命名组件且在父组件中引入以及注册3. 子组件被引用与父事件监听4.父组件回调函数 5.按钮弹窗事件 需求 弹窗…

大屏动效合集更更更之实现百分比环形

实现效果 参考链接&#xff1a; https://pslkzs.com/demo/pie/demo1.php 写在最后&#x1f352; 源码&#xff0c;关注&#x1f365;苏苏的bug&#xff0c;&#x1f361;苏苏的github&#xff0c;&#x1f36a;苏苏的码云

贪心算法(算法竞赛、蓝桥杯)--奶牛晒衣服

1、B站视频链接&#xff1a;A28 贪心算法 P1843 奶牛晒衣服_哔哩哔哩_bilibili 题目链接&#xff1a;奶牛晒衣服 - 洛谷 #include <bits/stdc.h> using namespace std; priority_queue<int> q;//用大根堆维护湿度的最大值 int n,a,b; int tim,maxn;int main(){s…

代码随想录算法训练营第day54|392.判断子序列 、 115.不同的子序列

目录 392.判断子序列 115.不同的子序列 392.判断子序列 力扣题目链接(opens new window) 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字…