一个虚拟机管理器(VMM)的实现

news2025/1/11 21:50:05

虚拟机管理器又名虚拟机管理程序、虚拟机监控程序、VMM。它使用虚拟化技术,将一台物理机虚拟化为多台虚拟机,每台虚拟机都可以独立一个操作系统。其背后的原理也很简单,它就是一个应用程序,模拟了硬件所提供的功能,比如 CPU、I/O、寄存器、堆栈等。也就是说它使用软件来实现了这一套东西,通常它会定义自己的一套指令集、寄存器、堆栈等。说的直白点,就是在软件层面定义一套规范,并提供这些能力。

Little Computer-3(简称 LC-3)是用于教学的汇编语言,它有着相比于 x86 更为简洁的指令集,同时包含了主流 CPU 的经典思想。LC-3 麻雀虽小,五脏俱全。它的规范不算太多,下面我们实现一个LC-3 CPU的虚拟机管理程序。

1. LC-3 体系结构

  • 内存地址空间 16 位,也就是最多可访问 0x0000~0xffff 的范围

  • 通用寄存器 8 个,16 位。编号从 000~111,R0 ~ R7

  • 三个标志寄存器,N(Negative)、P(Postive)、Z(Zero),每个 1 位

  • PC 寄存器

  • 指令长度 16 位,操作码 op 固定高 4 位

  • 采用大端字节序

所有指令如下图所示:

图片

图片

2. LC-3 CPU 定义

2.1 内存模拟

LC-3 有 2^16 个地址,每个地址包含一个 word (2 byte, 16 bit)。是大端序存储。所以用 C 表示如下。

#define MEMORY_MAX (1 << 16)uint16_t memory[MEMORY_MAX];  /* 65536 locations */

2.2 寄存器模拟

LC-3共有 10 个寄存器, 每个都是 16 位, 大部分是通用寄存器。

  • 8 个通用寄存器(R0-R7)

  • 1 个程序计数器 (PC) 寄存器

  • 1 个条件标志 (COND) 寄存器

enum
{    
    R_R0 = 0,    
    R_R1,    
    R_R2,    
    R_R3,    
    R_R4,    
    R_R5,    
    R_R6,    
    R_R7,    
    R_PC, /* program counter */    
    R_COND,    
    R_COUNT
};
uint16_t reg[R_COUNT];

2.3 指令集定义

LC-3中只有 16 条指令, 每条指令长 16 位。左侧 4 位存储操作码, 其余位用于存储参数。

enum
{
    OP_BR = 0, /* 0000 branch */
    OP_ADD,    /* 0001 add  */
    OP_LD,     /* 0010 load */
    OP_ST,     /* 0011 store */
    OP_JSR,    /* 0100 jump register */
    OP_AND,    /* 0101 bitwise and */
    OP_LDR,    /* 0110 load register */
    OP_STR,    /* 0111 store register */
    OP_RTI,    /* 1000 unused */
    OP_NOT,    /* 1001 bitwise not */
    OP_LDI,    /* 1010 load indirect */
    OP_STI,    /* 1011 store indirect */
    OP_JMP,    /* 1100 jump */
    OP_RES,    /* 1101 reserved (unused) */
    OP_LEA,    /* 1110 load effective address */
    OP_TRAP    /* 1111 execute trap */
};

R_COND 寄存器存储条件标志, 提供最近执行结果的信息。这样程序就可以执行逻辑/循环语句, 如 if (x > 0) { ... }。

enum
{
    FL_POS = 1 << 0, /* P: positive (greater than zero) */
    FL_ZRO = 1 << 1, /* Z: zero */
    FL_NEG = 1 << 2, /* N: negative (smaller than zero) */
};

2.4  中断

LC-3 中类似 x86 int 的 TRAP,实现输入输出等功能。

enum
{
    TRAP_GETC  = 0x20,
    TRAP_OUT   = 0x21,
    TRAP_PUTS  = 0x22,
    TRAP_IN    = 0x23,
    TRAP_PUTSP = 0x24,
    TRAP_HALT  = 0x25 
};

LC-3 有两个内存映射寄存器需要实现,它们是键盘状态寄存器 (KBSR)和键盘数据寄存器 (KBDR)。 键盘状态寄存器(KBSR)指示是否有按键被按下,键盘数据寄存器(KBDR)则识别被按下的按键。

​​​​​​

enum{    MR_KBSR = 0xFE00, /* keyboard status */    MR_KBDR = 0xFE02  /* keyboard data */}

3. LC-3 指令实现

代码是以汇编形式编写的,而汇编是以纯文本编码的人类可读可写形式。我们使用一种称为汇编器的工具,将每一行文本转换成虚拟机可以理解的 16 位二进制指令。这种二进制形式本质上是 16 位指令的数组,被称为机器码,也是虚拟机实际运行的内容。VMM的作用就是解析并处理这些机器码。

图片

3.1 ADD 指令

两个变量相加(+)。ADD DR,SR1,SR2 或者 ADD DR,SR1,imm

case OP_ADD:
{
  // 目的寄存器 (DR)
  uint16_t r0 = (instr >> 9) & 0x7;
  // 源寄存器1 (SR1)
  uint16_t r1 = (instr >> 6) & 0x7;
  // 立即数标志
  uint16_t imm_flag = (instr >> 5) & 0x1;
  if (imm_flag == 0) {
    uint16_t r2 = instr & 0x7;
    reg[r0] = reg[r1] + reg[r2];
  } else {
    uint16_t imm5 = sign_extend(instr & 0x1F, 5);
    reg[r0] = reg[r1] + imm5;
  }
  update_flags(r0);
}

3.2 AND 指令

与运算。和 ADD 一样,有两种模式,指令格式一模一样。

case OP_AND:
{
  uint16_t r0 = (instr >> 9) & 0x7;
  uint16_t r1 = (instr >> 6) & 0x7;
  uint16_t imm_flag = (instr >> 5) & 0x1;
  if (imm_flag) {
    uint16_t imm5 = sign_extend(instr & 0x1F, 5);
    reg[r0] = reg[r1] & imm5;
  } else {
    uint16_t r2 = instr & 0x7;
    reg[r0] = reg[r1] & reg[r2];
  }
  update_flags(r0);
}

3.3 NOT 指令

取反指令。取反之后同时更新标志寄存器。

case OP_NOT:{  uint16_t r0 = (instr >> 9) & 0x7;  uint16_t r1 = (instr >> 6) & 0x7;  reg[r0] = ~reg[r1];  update_flags(r0);}

3.4 BR 指令

根据“条件标志位”进行跳转。跳转参数是相对于 PC 的偏移量。

case OP_BR:
{
  uint16_t n_flag = (instr >> 11) & 0x1;
  uint16_t z_flag = (instr >> 10) & 0x1;
  uint16_t p_flag = (instr >> 9) & 0x1;
  uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
  if ((n_flag && (reg[R_COND] & FL_NEG)) ||
    (z_flag && (reg[R_COND] & FL_ZRO)) ||
    (p_flag && (reg[R_COND] & FL_POS))) {
    reg[R_PC] += pc_offset;
  }
}

3.5 JMP 指令

跳转指令,只有一个寄存器参数,在 6~8 位。作用是更新 PC 为寄存器的值。

case OP_JMP:{  uint16_t r1 = (instr >> 6) & 0x7;  reg[R_PC] = reg[r1];}

3.6 JSR 指令

全称为 Jump to Subroutine,也就是函数调用。它有两种模式,通过指令第 12 位 flag = instr[11] 来区分。两种模式的前置操作均为保存现场:将当前 PC 值保存到 R7 中,目的是为了在函数调用完成之后恢复原 PC 值。​​​​​​​

case OP_JSR:
{
  uint16_t long_flag = (instr >> 11) & 1;
  if (long_flag) {
    reg[R_R7] = reg[R_PC];
    uint16_t long_pc_offset = sign_extend(instr & 0x7FF, 11);
    reg[R_PC] += long_pc_offset;  /* JSR 直接跳转 */
  } else {
    uint16_t tmp = reg[(instr >> 6) & 0x7];
    reg[R_R7] = reg[R_PC];
    reg[R_PC] = tmp; /* JSRR 寄存器间接跳转 */
  }
}

3.7 LD 指令

从 PC + 相对偏移得到地址后,从地址中取出值并赋值目的寄存器。​​​​​​​

case OP_LD:{  uint16_t r0 = (instr >> 9) & 0x7;  uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);  reg[r0] = mem_read(reg[R_PC] + pc_offset);  update_flags(r0);}

3.8 LDI 指令

间接加载。该指令用于将内存中某个位置的值加载到寄存器中。​​​​​​​

case OP_LDI:
{
  // 目的寄存器 (DR)
  uint16_t r0 = (instr >> 9) & 0x7;
  // PC偏移 9bit
  uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);
  // 将 pc_offset 加到 PC 上, 然后查看该内存位置获取最终地址
  reg[r0] = mem_read(mem_read(reg[R_PC] + pc_offset));
  update_flags(r0);
}

3.9 LDR 指令

将 SR1 的值与偏移量相加后的内存位置值加载到目的寄存器中。​​​​​​​

case OP_LDR:
{
  uint16_t r0 = (instr >> 9) & 0x7;
  uint16_t r1 = (instr >> 6) & 0x7;
  uint16_t offset = sign_extend(instr & 0x3F, 6);
  reg[r0] = mem_read(reg[r1] + offset);
  update_flags(r0);
}

3.10 LEA 指令

将LABEL标记的地址加载到目的寄存器中。​​​​​​​

case OP_LEA:{  uint16_t r0 = (instr >> 9) & 0x7;  uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);  reg[r0] = reg[R_PC] + pc_offset;  update_flags(r0);}

3.11 ST 指令

将 SR1 中的值存储到 LABEL 指示的存储位置。​​​​​​​

case OP_ST:{  uint16_t r0 = (instr >> 9) & 0x7;  uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);  mem_write(reg[R_PC] + pc_offset, reg[r0]);}

3.12 STI 指令

将 SR1 中的值存储到 LABEL 存储位置所指示的内存中。​​​​​​​

case OP_STI:{  uint16_t r0 = (instr >> 9) & 0x7;  uint16_t pc_offset = sign_extend(instr & 0x1FF, 9);  mem_write(mem_read(reg[R_PC] + pc_offset), reg[r0]);}

3.13 STR 指令

SR1中的值存储在 SR2 和 offest6 相加后的内存位置中。​​​​​​​

case OP_STR:{  uint16_t r0 = (instr >> 9) & 0x7;  uint16_t r1 = (instr >> 6) & 0x7;  uint16_t offset = sign_extend(instr & 0x3F, 6);  mem_write(reg[r1] + offset, reg[r0]);}

3.14 Trap 中断

LC-3 提供了一些预定义的Trap,用于执行常见任务和与 I/O 设备交互。例如,用于从键盘获取输入和向控制台显示字符串。每个Trap都有一个入口(类似于操作码)。​​​​​​​

case OP_TRAP:
{
  reg[R_R7] = reg[R_PC];
  switch (instr & 0xFF)
  {
    case TRAP_GETC:
    case TRAP_OUT:
    case TRAP_PUTS:
    case TRAP_IN:
    case TRAP_PUTSP:
    case TRAP_HALT:  
  }
}

(1) Trap GETC

 从键盘读取一个输入字符并将其存储到 R0 中,而不将该字符回显到控制台。​​​​​​​

case TRAP_GETC:  reg[R_R0] = (uint16_t)getchar();update_flags(R_R0);

(2) Trap OUT

  将R0中的字符输出到控制台。​​​​​​​

case TRAP_OUT:  putc((char)reg[R_R0], stdout);fflush(stdout);

(3) Trap PUTS

  从 R0 中包含的地址开始,向控制台输出字符串。​​​​​​​

case TRAP_PUTS:
{
    // 16 bit 表示一个字符
    uint16_t* c = mem_addr() + reg[R_R0];
    while (*c) {
      putc((char)*c, stdout);
      ++c;
    }
    fflush(stdout);
}

(4)Trap IN

  从键盘上读取一个输入字符,将其存储到 R0 中,并向控制台回传该字符。​​​​​​​

case TRAP_IN:  {    char c = getchar();    putc(c, stdout);    fflush(stdout);    reg[R_R0] = (uint16_t)c;    update_flags(R_R0);}

(5) Trap PUTSP

  与 PUTS 类似,不同之处在于它输出字符串时,它先输出低 8 位,再输出    高 8 位。​​​​​​​

case TRAP_PUTSP:
{
    uint16_t* c = mem_addr() + reg[R_R0];
    while (*c) {
      char char1 = (*c) & 0xFF;
      putc(char1, stdout);
      char char2 = (*c) >> 8;
      if (char2) {
        putc(char2, stdout);
      }
      ++c;
    }
    fflush(stdout);
}

(6) Trap HALT

  结束程序。​​​​​​​

case TRAP_HALT:    fflush(stdout);    running = 0; // 跳出循环,结束程序

3.15 保留指令

RTI 和 RES 未使用,不处理即可。不过它们倒是可以用作占位指令。

4. 编译运行

完整代码见:

https://gist.github.com/hbuxiaofei/641d648b045d10a36a12e27c0da00317

(1)编译

    在linux终端执行:

$ gcc lc3.c -o lc3-vm

(2)下载编译好的二进制虚拟机程序​​​​​​​

2048:https://www.jmeiners.com/lc3-vm/supplies/2048.objRogue: https://www.jmeiners.com/lc3-vm/supplies/rogue.obj​​​​​​​

(3)使用 .obj 文件作为参数运行虚拟机

$ lc3-vm 2048.obj

(4)2048游戏

    通过 WASD 按键进行操作

图片


参考:

【CPU Design for LC-3 instruction set 】

https://coertvonk.com/inquiries/how-cpu-work/design-30973

【The LC-3】

https://www.cs.utexas.edu/users/fussell/courses/cs310h/lectures/Lecture_10-310h.pdf

【LC-3 Assembly Lab Manual】

https://people.cs.georgetown.edu/~squier/Teaching/HardwareFundamentals/LC3-trunk/docs/LC3-AssemblyManualAndExamples.pdf

【Write your Own Virtual Machine】

https://www.jmeiners.com/lc3-vm/

【Introduction to Computing Systems】

https://highered.mheducation.com/sites/0072467509/index.html

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

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

相关文章

虹科分享 | 赋能物流机器人:CANopen通信如何发挥重要作用?

现代物流领域迅速融入了技术进步&#xff0c;特别是随着自主机器人的兴起&#xff0c;这一趋势越发明显。确保这些机器人在复杂的仓库环境中精确运行的一个关键方面是CANopen通信协议。该协议集成了各种组件&#xff08;电机、传感器、摄像头和先进的电池系统&#xff09;&…

Java基础20问(6-10)

6.Java接口和抽象类的区别&#xff1f; 不同点 1.接口在Java8之前不能写方法实现逻辑&#xff0c;Java8及以后的版本&#xff0c;可以用default关键字写方法的实现。 2.接口中方法都是public的&#xff0c;public可以省略&#xff0c;而抽象类没有这个限制。 3.接口用inter…

防止员工私自拷贝公司资料

公司资料的安全性变得越来越重要&#xff0c;然而&#xff0c;我们时常会面临一个严峻的问题&#xff1a;员工私自拷贝公司资料。这不仅可能导致数据泄露&#xff0c;还会给公司带来巨大的经济损失和声誉风险。因此&#xff0c;采取有效的措施来防止员工私自拷贝公司资料已势在…

台灯护眼灯怎么挑选?央视公认好用的护眼台灯

现在我们的生活被大大小小的屏幕包围着&#xff0c;学生群体也是如此&#xff0c;再家长他们平时学业就比较繁重&#xff0c;无疑是增加了眼睛的负担&#xff0c;这也是如今这么多儿童青少年早早戴上眼镜的原因。所以很多家长也开始重视起了孩子的视力健康问题&#xff0c;都纷…

ChatGPT 即将诞生一周年,OpenAI 将有大动作

图片来源&#xff1a;由无界AI生成 下个月就是 ChatGPT 一周年纪念日。OpenAI 正在谋划新的大动作。可以肯定地说&#xff0c;自诞生以来&#xff0c;ChatGPT 就为 OpenAI 提供了不可阻挡的增长动力。 01 营收超预期&#xff0c;OpenAI 缓了一口气 据 The Information 报道&…

Unity设置Visual Studio后依旧恢复原样

一、背景 小伙伴们在做Unity开发的时候&#xff0c;是否会遇到当设置了Visual Studio 后&#xff0c;下次打开依旧恢复原样的问题呢&#xff1f; 二、解决思路 第一步&#xff1a;首先设置这里的Vs版本 第二步:打开Browse 打开这里的Browse&#xff0c;找到Visual Studio…

哪个品牌的触控笔质量好?触控笔排行榜

想必很多学生党都想为iPad配上一款电容笔&#xff0c;然而无从下手&#xff0c;毕竟原装的Apple Pencil虽然性能很好&#xff0c;但也很贵&#xff0c;不是谁都能负担得起的。所以&#xff0c;有没有类似于Apple Pencil的平替式电容笔&#xff1f;肯定有&#xff0c;国内的平替…

WebStrom对于rpx爆红的解决方法

WebStrom对于rpx爆红的解决方法 问题场景&#xff1a;在使用WebStrom编写CSS样式代码时&#xff0c;经过快捷键格式化后代码CSS变无效了&#xff01; 问题原因&#xff1a;快捷键格式化的锅&#xff08;准确来说是WebStrom的锅&#xff09; WebStrom编译器默认是不支持 rpx 像…

Qt QDialog模式对话框传递数据给主窗口(主窗口->子窗口)

Qt工作笔记-QDialog模式对话框传递数据给主窗口_qt dialog-CSDN博客话不多说&#xff0c;上图&#xff1a;这里同样是采用了Qt的信号与槽机制。项目文件分布如下&#xff1a;代码如下&#xff1a;dialog.h#ifndef DIALOG_H#define DIALOG_H#include <QDialog>namespace U…

苹果手机怎么恢复数据?推荐这款数据恢复软件!

苹果手机一直以高颜值、系统稳定&#xff0c;以及优质的用户体验而闻名&#xff0c;这也使得购买苹果手机的用户逐渐增多。在手机中我们会保存各种各样的数据&#xff0c;包括照片、视频、备忘录、聊天记录等等。但是&#xff0c;这些数据可能会因为某些原因而导致丢失。 那么…

如何修改X12端口的ControlNumber?

问题场景&#xff1a;企业对知行之桥EDI系统进行了升级或者迁移&#xff0c;由于此前通过X12端口传给客户的ControlNumber&#xff0c;已经自增到100&#xff0c;现需要在当前的知行之桥EDI系统中从101开始传送。 在EDI术语中&#xff0c;ICN# 的全称为Interchange Control Num…

飞行态势知识图谱及其问答系统的构建方法

源自&#xff1a;《指挥信息系统与技术》 作者&#xff1a;张笑文 汤闻易 单晶 李代祎 马宗民 “人工智能技术与咨询” 发布 简 介 0 引言 1 系统架构 图1 知识问答系统总体架构 2 系统功能模块设计 图2 系统结构 2.1 飞行态势知识图谱设计与构建 2.1.1 飞行态势…

IO流:字符输入流Reader的超详细用法及底层原理

字符输入流Reader的超详细用法及底层原理 一、背景二、字符输入流Reader正式出场三、IO流体系图概览四、Reader继承人&#xff1a;FileReader出场五、字符流原理解析 一、背景 当我们使用字节输入流时&#xff0c;经常会出现乱码问题&#xff0c;具体原因如下&#xff1a; 解…

SBOM实例基础元素分析

有时候&#xff0c;SBOM (软件材料清单)更多的是理论上的&#xff0c;而不是实际的。有很多关于使用 SBOM 的潜在好处的讨论&#xff0c;比如软件供应链安全和守规&#xff0c;以及流行的 SBOM 格式&#xff0c;比如 CyclonedX 和 SPDX。但是我们的一些客户(特别是那些在 SBOM …

MSQL系列(四) Mysql实战-索引分析Explain命令详解

Mysql实战-索引分析Explain命令详解 前面我们讲解了索引的存储结构&#xff0c;我们知道了BTree的索引结构&#xff0c;也了解了索引最左侧匹配原则&#xff0c;到底最左侧匹配原则在我们的项目中有什么用&#xff1f;或者说有什么影响&#xff1f;今天我们来实战操作一下&…

PyTorch 模型性能分析和优化 - 第 6 部分

玩具模型 为了方便我们的讨论&#xff0c;我们使用流行的 timm python 模块&#xff08;版本 0.9.7&#xff09;定义了一个简单的基于 Vision Transformer (ViT) 的分类模型。我们将模型的 patch_drop_rate 标志设置为 0.5&#xff0c;这会导致模型在每个训练步骤中随机丢弃一半…

中国模式识别与计算机视觉大会|多模态模型及图像安全的探索及成果

目录 前言一、多模态模型进展与探索1、GPT-4V (多模态)测试2、LLM时代文档图像处理技术趋势3、LLM时代文档图像技术机会4、MLLM时代文档图像处理技术趋势5、知名文档图像大模型OCR性能分析 二、图像安全1、篡改种类2、系统架构3、文档图像处理开放平台4、AIGC假图鉴别5、图像篡…

Linux 回环测试串口RS232 UART

测试平台&#xff1a; ubuntu 18.04 需使用root权限。 1、硬件2脚和3脚短接 2、利用stty命令去掉默认的回显参数 stty -F /dev/ttyUSB0 -echo -onlcr如不设置会无限输出&#xff0c;且看不到信息 3、输入测试指令&#xff1a; 一个终端&#xff1a; cat /dev/ttyUSB0另一…

QtService实现Qt后台服务程序其一_基本使用步骤

QtService基本使用步骤 1、QtService介绍 QtService是一个用于实现windows服务或unix守护进程的开源项目&#xff0c;本文使用QtService演示如何实现一个windows下的后台进程&#xff0c;可用于一些简单的windows服务程序中。 测试使用Qt5.9.2版本。 2、项目引入QtService …

清除el-form表单验证

当创建表单触发表单验证时&#xff0c;关闭弹窗&#xff0c;再次触发创建表单会触发表单验证&#xff0c;出现如下图所示情况&#xff1a; 在每次打开弹窗时&#xff0c;添加如下代码&#xff0c;清除表单验证 this.$nextTick( () > {this.$refs[forName].clearValidate()…