当前位置: 首页 > news >正文

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); }
}

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

相关文章:

  • Uibot (RPA设计软件)财务会计Web应用自动化(批量开票机器人)
  • Docker启动失败,报错Is the docker daemon running? Is the docker daemon running?
  • 环境安装篇 之 安装kubevela
  • Java面试题(Spring篇)
  • python课后习题一
  • echart多折线图堆叠 y轴和实际数据不对应
  • 全量知识系统“全基因序列”程序构想及SmartChat的回复
  • Github: Github actions自动化工作原理与多workflow创建和部署
  • 掌握Go语言:利用Go语言的单向通道和select语句,提升库存管理效率(21)
  • THM学习笔记—Bounty Hacker
  • 游戏客户端面经
  • mysql基础操作
  • 亮数据代理IP轻松解决爬虫数据采集痛点
  • Python BaseModel和dataclass用法和区别
  • Day74:WEB攻防-机制验证篇重定向发送响应状态码跳过步骤验证码回传枚举
  • JS 中的深拷贝与浅拷贝
  • [数据结构]链表的实现在PHP中
  • 【407天】跃迁之路——程序员高效学习方法论探索系列(实验阶段164-2018.03.19)...
  • Android单元测试 - 几个重要问题
  • es6(二):字符串的扩展
  • exif信息对照
  • Median of Two Sorted Arrays
  • MySQL几个简单SQL的优化
  • Python_OOP
  • Selenium实战教程系列(二)---元素定位
  • Spark学习笔记之相关记录
  • vue-router 实现分析
  • 从地狱到天堂,Node 回调向 async/await 转变
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 聊聊spring cloud的LoadBalancerAutoConfiguration
  • 码农张的Bug人生 - 初来乍到
  • 前端 CSS : 5# 纯 CSS 实现24小时超市
  • 前端临床手札——文件上传
  • 入门到放弃node系列之Hello Word篇
  • 扫描识别控件Dynamic Web TWAIN v12.2发布,改进SSL证书
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 我的zsh配置, 2019最新方案
  • 学习ES6 变量的解构赋值
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • #android不同版本废弃api,新api。
  • (12)Hive调优——count distinct去重优化
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第2节(共同的基类)
  • (带教程)商业版SEO关键词按天计费系统:关键词排名优化、代理服务、手机自适应及搭建教程
  • (附源码)计算机毕业设计ssm-Java网名推荐系统
  • (附源码)计算机毕业设计ssm电影分享网站
  • (更新)A股上市公司华证ESG评级得分稳健性校验ESG得分年均值中位数(2009-2023年.12)
  • (小白学Java)Java简介和基本配置
  • (续)使用Django搭建一个完整的项目(Centos7+Nginx)
  • (原創) 博客園正式支援VHDL語法著色功能 (SOC) (VHDL)
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转) 深度模型优化性能 调参
  • ***汇编语言 实验16 编写包含多个功能子程序的中断例程
  • ***检测工具之RKHunter AIDE
  • .jks文件(JAVA KeyStore)