C11编写简易16位虚拟机

2023-12-20 21:52
文章标签 16 编写 虚拟机 简易 c11

本文主要是介绍C11编写简易16位虚拟机,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

虚拟机

在计算领域,VM(虚拟机)是一个术语,指的是模拟/虚拟化计算机系统/架构的系统。

一般来说,虚拟机有两类:

  • 系统虚拟机提供真实机器的完整替代品。 它们实现了足够的功能,允许操作系统在它们上运行。 它们可以共享和管理硬件,有时多个环境可以在同一台物理机器上运行而不会互相妨碍。
  • 处理虚拟机更简单,旨在在与平台无关的环境中执行计算机程序。 JVM 是进程虚拟机的一个很好的例子。

在本文中,我们将开发一个简单的进程虚拟机,旨在在独立于平台的环境中执行简单的计算机程序。 我们的虚拟机基于 LC-3 计算机架构,并且能够解释和执行 LC3 汇编代码(的子集)。

Little Computer 3,或 LC-3,是一种计算机教育编程语言,一种汇编语言,一种低级编程语言。 它具有相对简单的指令集,但可用于编写中等复杂的汇编程序,并且是 C 编译器的可行目标。 该语言比 x86 汇编语言简单,但具有许多与更复杂语言类似的功能。 这些功能使其值得入门教学,因此它最常用于向计算机科学和计算机工程学生教授编程和计算机体系结构基础知识

为简单起见,我们特意从以下功能中剥离了 LC-3 实现:中断处理、优先级、进程、状态寄存器 (PSR)、特权模式、管理程序堆栈、用户堆栈。 我们将只虚拟化最基本的硬件,并且我们将通过陷阱与外界(stdin、stdout)进行交互。

我们受 LC-3 启发的 VM 与当今大多数通用计算机一样,基于冯·诺依曼计算机模型,它将具有三个主要组件:CPU、主存储器、输入/输出设备。

实现

我们的虚拟机功能如下:

  • 我们将程序加载到主存中
  • 在RPC寄存器中,我们保存当前需要执行的指令
  • 我们从指令中获取操作码(前 4 位),并据此解码其余参数
  • 我们执行与给定指令相关的方法
  • 我们增加 RPC 并继续下一条指令

内存

我们的机器有 W=UINT16_MAX 个字,每个字 N=16 位。从 C 的角度来看,我们的内存可以定义为:

uint16_t PC_START = 0x3000;
uint16_t mem[UINT16_MAX+1] = {0};

寄存器

我们的 VM 共有 10 个寄存器,每个寄存器 16 位:

从代码的角度来看,我们可以按如下方式实现它们:

enum regist { R0 = 0, R1, R2, R3, R4, R5, R6, R7, RPC, RCND, RCNT };
uint16_t reg[RCNT] = {0};

指令

指令就像我们向虚拟机发出的命令。

为了提取操作码本身,我们可以编写一个实用宏来应用简单的按位技巧:

#define OPC(i) ((i)>>12)

我们可以在 C 中执行的一个好技巧(从数据建模的角度来看)是将所有可能的指令(及其关联的 C 函数)保存在数组中。 索引将代表实际的操作码(毕竟,操作码是从 0 到 15 的数字),并且该值将是指向相应 C 函数的指针。

#define NOPS (16) // number of instructions
typedef void (*op_ex_f)(uint16_t instruction);
//
// ... other operations here
//
static inline void add(uint16_t i)  { /* code here */ }
static inline void and(uint16_t i)  { /* code here */ }
//
// ... other operations here
//
op_ex_f op_ex[NOPS] = { br, add, ld, st, jsr, and, ldr, str, rti, not, ldi, sti, jmp, res, lea, trap 
};

加法

逻辑位“与”

ld - 加载 RPC + 偏移量

ldi - 间接加载

ldr - 加载+偏移量

lea - 加载有效地址

not - 按位求补

st - 存储

sti - 间接存储

str - 存储+偏移量

jump - 跳转

加载和执行程序

我们只缺少两个功能:主循环和加载程序的能力。

我们虚拟机的主循环如下所示:

bool running=true;
uint16_t PC_START = 0x3000;
void start(uint16_t offset) { reg[RPC] = PC_START + offset; // The RPC is setwhile(running) {uint16_t i = mr(reg[RPC]++); // We extract instructions from the memory// location pointed by RPC           // We (auto)increment RPC       op_ex[OPC(i)](i);            // We execute each instruction}
}

现在,唯一缺少的是将程序加载到我们的虚拟机中的能力,在这方面我们将编写一个 ld_img 方法,能够将二进制文件直接加载到我们的主内存中:

void ld_img(char *fname, uint16_t offset) {// Open (binary) file containing the VM programFILE *in = fopen(fname, "rb");  if (NULL==in) {fprintf(stderr, "Cannot open file %s.\n", fname);exit(1);    }// The position from were we start copying the file// to the main memoryuint16_t *p = mem + PC_START + offset;// Load the program in memoryfread(p, sizeof(uint16_t), (UINT16_MAX-PC_START), in);// Close the file streamfclose(in);
}

该方法返回 void 并接受两个输入参数:

  • 包含我们程序的二进制文件的路径
  • 我们开始将第一条程序指令加载到主内存中的偏移量

我们虚拟机的主要方法如下所示:

int main(int argc, char **argv) {ld_img(argv[1], 0x0);start(0x0);return 0;
}

我们的第一个程序,将从键盘读取两个数字并将它们的总和打印到标准输出。

0xF026    //  1111 0000 0010 0110  TRAP tinu16      ;read an uint16_t in R0
0x1220    //  0001 0010 0010 0000  ADD R1,R0,x0     ;add contents of R0 to R1
0xF026    //  1111 0000 0010 0110  TRAP tinu16      ;read an uint16_t in R0
0x1240    //  0001 0010 0010 0000  ADD R1,R1,R0     ;add contents of R0 to R1
0x1060    //  0001 0000 0110 0000  ADD R0,R1,x0     ;add contents of R1 to R0
0xF027    //  1111 0000 0010 0111  TRAP toutu16     ;show the contents of R0 to stdout
0xF025    //  1111 0000 0010 0101  HALT             ;halt

语法对用户不友好,不是吗?我们的程序其实就是这一系列数字:0xF026 0x1220 0xF026 0x1240 0x1060 0xF027 0xF025。但如果我们仔细观察,就会发现在这些数字中我们一直在编码汇编指令。

例如,让我们看一下这个数字0xF026。其二进制表示为1111 0000 0010 0110。很容易看出1111是trap的编码,TRAPVECT是100111,对应tinu16。

或者为了更直观的表示,我们来分析 0x1220:

0x1220 ->0001   001 000 1  00000 
ADD    R1  R0     IMM5=0     

运行我们的第一个程序

#include <stdio.h>
#include <stdlib.h>uint16_t program[] = {/*mem[0x3000]=*/    0xF026,    //  1111 0000 0010 0110             TRAP trp_in_u16  ;read an uint16_t from stdin and put it in R0/*mem[0x3002]=*/    0x1220,    //  0001 0010 0010 0000             ADD R1,R0,x0     ;add contents of R0 to R1/*mem[0x3003]=*/    0xF026,    //  1111 0000 0010 0110             TRAP trp_in_u16  ;read an uint16_t from stdin and put it in R0/*mem[0x3004]=*/    0x1240,    //  0001 0010 0010 0000             ADD R1,R1,R0     ;add contents of R0 to R1/*mem[0x3006]=*/    0x1060,    //  0001 0000 0110 0000             ADD R0,R1,x0     ;add contents of R1 to R0/*mem[0x3007]=*/    0xF027,    //  1111 0000 0010 0111             TRAP trp_out_u16;show the contents of R0 to stdout/*mem[0x3006]=*/    0xF025,    //  1111 0000 0010 0101             HALT             ;halt
};int main(int argc, char** argv) {char *outf = "sum.obj";FILE *f = fopen(outf, "wb");if (NULL==f) {fprintf(stderr, "Cannot write to file %s\n", outf);}size_t writ = fwrite(program, sizeof(uint16_t), sizeof(program), f);fprintf(stdout, "Written size_t=%lu to file %s\n", writ, outf);fclose(f);return 0;
}

源代码

参阅一 - 亚图跨际
参阅二 - 亚图跨际

这篇关于C11编写简易16位虚拟机的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/517635

相关文章

Linux虚拟机不显示IP地址的解决方法(亲测有效)

《Linux虚拟机不显示IP地址的解决方法(亲测有效)》本文主要介绍了通过VMware新装的Linux系统没有IP地址的解决方法,主要步骤包括:关闭虚拟机、打开VM虚拟网络编辑器、还原VMnet8或修... 目录前言步骤0.问题情况1.关闭虚拟机2.China编程打开VM虚拟网络编辑器3.1 方法一:点击还原VM

JAVA虚拟机中 -D, -X, -XX ,-server参数使用

《JAVA虚拟机中-D,-X,-XX,-server参数使用》本文主要介绍了JAVA虚拟机中-D,-X,-XX,-server参数使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有... 目录一、-D参数二、-X参数三、-XX参数总结:在Java开发过程中,对Java虚拟机(JVM)的启动参数进

Python结合Flask框架构建一个简易的远程控制系统

《Python结合Flask框架构建一个简易的远程控制系统》这篇文章主要为大家详细介绍了如何使用Python与Flask框架构建一个简易的远程控制系统,能够远程执行操作命令(如关机、重启、锁屏等),还... 目录1.概述2.功能使用系统命令执行实时屏幕监控3. BUG修复过程1. Authorization

基于.NET编写工具类解决JSON乱码问题

《基于.NET编写工具类解决JSON乱码问题》在开发过程中,我们经常会遇到JSON数据处理的问题,尤其是在数据传输和解析过程中,很容易出现编码错误导致的乱码问题,下面我们就来编写一个.NET工具类来解... 目录问题背景核心原理工具类实现使用示例总结在开发过程中,我们经常会遇到jsON数据处理的问题,尤其是

Ubuntu固定虚拟机ip地址的方法教程

《Ubuntu固定虚拟机ip地址的方法教程》本文详细介绍了如何在Ubuntu虚拟机中固定IP地址,包括检查和编辑`/etc/apt/sources.list`文件、更新网络配置文件以及使用Networ... 1、由于虚拟机网络是桥接,所以ip地址会不停地变化,接下来我们就讲述ip如何固定 2、如果apt安

python实现简易SSL的项目实践

《python实现简易SSL的项目实践》本文主要介绍了python实现简易SSL的项目实践,包括CA.py、server.py和client.py三个模块,文中通过示例代码介绍的非常详细,对大家的学习... 目录运行环境运行前准备程序实现与流程说明运行截图代码CA.pyclient.pyserver.py参

使用PyQt实现简易文本编辑器

《使用PyQt实现简易文本编辑器》这篇文章主要为大家详细介绍了如何使用PyQt5框架构建一个简单的文本编辑器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录分析主窗口类 (MyWindow)菜单操作语法高亮 (SyntaxHighlighter)运行程序主要组件代码图示分析实现

5分钟获取deepseek api并搭建简易问答应用

《5分钟获取deepseekapi并搭建简易问答应用》本文主要介绍了5分钟获取deepseekapi并搭建简易问答应用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需... 目录1、获取api2、获取base_url和chat_model3、配置模型参数方法一:终端中临时将加

虚拟机与物理机的文件共享方式

《虚拟机与物理机的文件共享方式》文章介绍了如何在KaliLinux虚拟机中实现物理机文件夹的直接挂载,以便在虚拟机中方便地读取和使用物理机上的文件,通过设置和配置,可以实现临时挂载和永久挂载,并提供... 目录虚拟机与物理机的文件共享1 虚拟机设置2 验证Kali下分享文件夹功能是否启用3 创建挂载目录4

利用Python编写一个简单的聊天机器人

《利用Python编写一个简单的聊天机器人》这篇文章主要为大家详细介绍了如何利用Python编写一个简单的聊天机器人,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 使用 python 编写一个简单的聊天机器人可以从最基础的逻辑开始,然后逐步加入更复杂的功能。这里我们将先实现一个简单的