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

相关文章

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

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

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

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

用Java打造简易计算器的实现步骤

《用Java打造简易计算器的实现步骤》:本文主要介绍如何设计和实现一个简单的Java命令行计算器程序,该程序能够执行基本的数学运算(加、减、乘、除),文中通过代码介绍的非常详细,需要的朋友可以参考... 目录目标:一、项目概述与功能规划二、代码实现步骤三、测试与优化四、总结与收获总结目标:简单计算器,设计

使用PyQt5编写一个简单的取色器

《使用PyQt5编写一个简单的取色器》:本文主要介绍PyQt5搭建的一个取色器,一共写了两款应用,一款使用快捷键捕获鼠标附近图像的RGB和16进制颜色编码,一款跟随鼠标刷新图像的RGB和16... 目录取色器1取色器2PyQt5搭建的一个取色器,一共写了两款应用,一款使用快捷键捕获鼠标附近图像的RGB和16

如何用Python绘制简易动态圣诞树

《如何用Python绘制简易动态圣诞树》这篇文章主要给大家介绍了关于如何用Python绘制简易动态圣诞树,文中讲解了如何通过编写代码来实现特定的效果,包括代码的编写技巧和效果的展示,需要的朋友可以参考... 目录代码:效果:总结 代码:import randomimport timefrom math

通过C#和RTSPClient实现简易音视频解码功能

《通过C#和RTSPClient实现简易音视频解码功能》在多媒体应用中,实时传输协议(RTSP)用于流媒体服务,特别是音视频监控系统,通过C#和RTSPClient库,可以轻松实现简易的音视... 目录前言正文关键特性解决方案实现步骤示例代码总结最后前言在多媒体应用中,实时传输协议(RTSP)用于流媒体服

使用Java编写一个文件批量重命名工具

《使用Java编写一个文件批量重命名工具》这篇文章主要为大家详细介绍了如何使用Java编写一个文件批量重命名工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录背景处理1. 文件夹检查与遍历2. 批量重命名3. 输出配置代码片段完整代码背景在开发移动应用时,UI设计通常会提供不

如何编写Linux PCIe设备驱动器 之二

如何编写Linux PCIe设备驱动器 之二 功能(capability)集功能(capability)APIs通过pci_bus_read_config完成功能存取功能APIs参数pos常量值PCI功能结构 PCI功能IDMSI功能电源功率管理功能 功能(capability)集 功能(capability)APIs int pcie_capability_read_wo

【JavaScript】LeetCode:16-20

文章目录 16 无重复字符的最长字串17 找到字符串中所有字母异位词18 和为K的子数组19 滑动窗口最大值20 最小覆盖字串 16 无重复字符的最长字串 滑动窗口 + 哈希表这里用哈希集合Set()实现。左指针i,右指针j,从头遍历数组,若j指针指向的元素不在set中,则加入该元素,否则更新结果res,删除集合中i指针指向的元素,进入下一轮循环。 /*** @param

HotSpot虚拟机的经典垃圾收集器

读《深入理解Java虚拟机》第三版笔记。 关系 Serial、ParNew、Parallel Scavenge、Parallel Old、Serial Old(MSC)、Concurrent Mark Sweep (CMS)、Garbage First(G1)收集器。 如图: 1、Serial 和 Serial Old 收集器 2、ParNew 收集器 3、Parallel Sc