| 积木首页 | 500多种网页特效 | 函数手册 | 广播电台 | 高清晰图片素材 | 服务器合租 | 万年历 | 网友最新浏览记录 |
| 程序开发 | ![]() |
网页设计 | ![]() |
搜索引擎 | ![]() |
特效代码 | ![]() |
操作系统 | ![]() |
防范病毒 | ![]() |
黑客技术 | ![]() |
图形图象 | ![]() |
电脑硬件 | ![]() |
网络技术 | ![]() |
服 务 器 | ![]() |
数 据 库 | ![]() |
网文精粹 |
)cpu技术指标
TM虚拟机中由一个只读指令存储区,数据区和8个寄存器组成,其中最后一个寄存器是PC寄存器,也就是程序计数器,用来保存下一个指令的地址,在跳转中以及读取指令的时候要用到,如下:
代码:
/* 只读指令存储区的大小 */
#define IADDR_SIZE 1024 /* increase for large programs */
/* 数据区的大小 */
#define DADDR_SIZE 1024 /* increase for large programs */
/* 寄存器的数目 */
#define NO_REGS 8
/* PC寄存器(程序计数器)的下标 */
#define PC_REG 7
在内存中分配了三个数组来实现模拟这三个数据区:
/* 只读指令存储区 */
INSTRUCTION iMem [IADDR_SIZE];
/* 数据存储区 */
int dMem [DADDR_SIZE];
/* 寄存器 */
int reg [NO_REGS];
所支持的指令集,为了简化cpu的实现,采用的是一个enum类型来存储指令的opcode,在这里opcode和汇编指令是一一对应的,而指令分为三种类型,分别是RR,RM,RA,其中的RM类型的指令需要对数据区进行读取,而另外两种只需要对寄存器进行读取就可以了,opcode的类型用一个enum OPCLASS来分别:
代码:
typedef enum {
opclRR, /* reg operands r,s,t */
opclRM, /* reg r, mem d+s */
opclRA /* reg r, int d+s */
} OPCLASS;
三种不同的opcode类型的opcode的格式是有区别的:
代码:
指令类型 指令的格式
RR r,s,t
RM r,d(s)
RA r,d(s)
说明一下,对于不同类型的汇编指令,必须与这种汇编指令的opcode格式严格对应,如RR指令的类型必须是r,s,t等等。RM和RA的指令格式稍微有一点区别,这里要根据输入的"d(s)"得到另外一个值a = d + reg[s],就是说d是偏移值,而s在这里是寄存器数组的下标,对于RM来说,得到的a是对应于数据内存区数组的下标,这个a不能小于0或者大于这个内存区的大小,否则就会报错,而对RA而言,这个a值就是存储到相应的寄存器中的值,寄存器的选择根据不同的指令而定。
下面是这个虚拟机的汇编指令对应的opcode的列表,右边的注释是每个不同的指令对应的动作,也就是要根据opcode中的数据所要进行的不同的处理:
代码:
/* 虚拟机的汇编指令对应的OPCODE */
typedef enum {
/* RR instructions */
opHALT, /* RR halt, operands are ignored */
opIN, /* RR read into reg(r); s and t are ignored */
opOUT, /* RR write from reg(r), s and t are ignored */
opADD, /* RR reg(r) = reg(s)+reg(t) */
opSUB, /* RR reg(r) = reg(s)-reg(t) */
opMUL, /* RR reg(r) = reg(s)*reg(t) */
opDIV, /* RR reg(r) = reg(s)/reg(t) */
opRRLim, /* limit of RR opcodes */
/* RM instructions */
opLD, /* RM reg(r) = mem(d+reg(s)) */
opST, /* RM mem(d+reg(s)) = reg(r) */
opRMLim, /* Limit of RM opcodes */
/* RA instructions */
opLDA, /* RA reg(r) = d+reg(s) */
opLDC, /* RA reg(r) = d ; reg(s) is ignored */
opJLT, /* RA if reg(r)<0 then reg(7) = d+reg(s) */
opJLE, /* RA if reg(r)<=0 then reg(7) = d+reg(s) */
opJGT, /* RA if reg(r)>0 then reg(7) = d+reg(s) */
opJGE, /* RA if reg(r)>=0 then reg(7) = d+reg(s) */
opJEQ, /* RA if reg(r)==0 then reg(7) = d+reg(s) */
opJNE, /* RA if reg(r)!=0 then reg(7) = d+reg(s) */
opRALim /* Limit of RA opcodes */
} OPCODE;
不论是哪一种类型的指令,都必须在指令内存区中保存相应的信息,比如说访问的寄存器的位置,内存的位置等等,这些信息保存在下面的一个结构体变量中的:
代码:
/* 存储指令的值,iop是OPCODE,剩下三个是r,s,t */
typedef struct {
int iop;
int iarg1;
int iarg2;
int iarg3;
} INSTRUCTION;
其中的iop就是指令对应的opcode,与前面的enum OPCODE中的成员一一对应,剩下的三个变量就是opcode中的r,s,t变量。指令内存区都INSTRUCTION类型的。
另外,执行指令的时候还要有不同的结果,这里同样的使用一个enum STEPRESULT类型来识别不同的结果,需要说明的是在读取内存的时候如果指针大小小于0或者大于内存的大小就会报错,另外在执行除法指令的时候还有可能出现零除错误:
代码:
/* 枚举执行结果 */
typedef enum {
srOKAY, /* 正确执行 */
srHALT, /* 停止执行 */
srIMEM_ERR, /* IMEM错误 */
srDMEM_ERR, /* DMEM错误 */
srZERODIVIDE /* 零除错误 */
} STEPRESULT;
2)cpu的工作原理
代码的量不大,只有700行不到,核心的函数有下面几个:
a)int readInstructions (void)
这个函数负责读取代码文件,是逐行处理的,对于每一行的文件首先检查指令是否符合这个虚拟机的汇编指令的格式,然后分辩这个指令的类型,再根据这个指令的类型存储不同的指令中opcode里的数据,每读取完一行代码如果没有错误的话就把指令及相应的opcode存储到指令存储区中,就是INSTRUCTION iMem [IADDR_SIZE]数组中,当读取文件结束的时候,这个数组中就都是相应的指令了,而后面的存储区存放的是HALT指令。
b)int doCommand (void)
这个函数负责读取stdin中用户输入的命令行参数来执行不同的动作,相关的动作这里不再详述,这个函数会一直执行下去,一直到用户输入q时才退出,在主函数main中是这样调用这个函数的:
代码:
do
done = ! doCommand ();
while (! done );
c)STEPRESULT stepTM (void)
这个函数是根据指令内存区中的指令逐步执行程序的函数,其中设定的一个局部变量 INSTRUCTION currentinstruction ;
负责存储当前的指令:
代码:
/* 从PC寄存器中读取下一条指令地址 */
pc = reg[PC_REG] ;
/* 如果小于零或者大于IADDR_SIZE,就是超过指令内存大小,就返回srIMEM_ERR错误 */
if ( (pc < 0) || (pc > IADDR_SIZE) )
return srIMEM_ERR ;
/* PC加一 */
reg[PC_REG] = pc + 1 ;
/* 从内存中根据PC地址值读取当前要执行的指令 */
currentinstruction = iMem[ pc ] ;
每读取完一个指令,就根据指令的类型以及指令所定义的不同的动作来执行代码,比如说:
代码:
/* 根据iop中存储的OPCODE来执行指令,每个case右边是指令的执行情况 */
switch ( currentinstruction.iop)
{
case opHALT : /* RR halt, operands are ignored */
printf("HALT: %1d,%1d,%1d\n",r,s,t);
return srHALT ;
/* break; */
case opIN : /* RR read into reg(r); s and t are ignored */
do{
printf("Enter value for IN instruction: ") ;
fflush (stdin);
fflush (stdout);
gets(in_Line);
lineLen = strlen(in_Line) ;
inCol = 0;
ok = getNum();
if ( ! ok )
printf ("Illegal value\n");
else
reg[r] = num;
} while (! ok);
break;
剩余的其它函数都是简单的辅助函数了,无非是一些跳过空格,跳过检查是否是特定字符,得到字符串以及得到数字等等之类的函数了。
3)汇编指令的格式
这个虚拟机能够识别的汇编指令有一定的格式,如下:
a)忽略空行
b)以“*”号开头的都是注释
c)任何其它行都必须以整数开头,这个整数用于指定代码的位置,任何指令后面的文字都被认为是注释而忽略掉。
至于支持的指令就是与opcode一一对应的,不再详述
4)虚拟机的使用方式
TM虚拟机只有一个文件,只需要简单的编译就可以了,下面提供一个与该虚拟机对应的汇编代码:
代码:
0: LD 6,0(0)
1: ST 0,0(0)
2: IN 0,0,0
3: ST 0,0(5)
4: LDC 0,0(0)
5: ST 0,0(6)
6: LD 0,0(5)
7: LD 1,0(6)
8: SUB 0,1,0
9: JLT 0,2(7)
10: LDC 0,0(0)
11: LDA 7,1(7)
12: LDC 0,1(0)
14: LDC 0,1(0)
15: ST 0,1(5)
16: LD 0,1(5)
17: ST 0,0(6)
18: LD 0,0(5)
19: LD 1,0(6)
20: MUL 0,1,0
21: ST 0,1(5)
22: LD 0,0(5)
23: ST 0,0(6)
24: LDC 0,1(0)
25: LD 1,0(6)
26: SUB 0,1,0
27: ST 0,0(5)
28: LD 0,0(5)
29: ST 0,0(6)
30: LDC 0,0(0)
31: LD 1,0(6)
32: SUB 0,1,0
33: JEQ 0,2(7)
34: LDC 0,0(0)
35: LDA 7,1(7)
36: LDC 0,1(0)
37: JEQ 0,-22(7)
38: LD 0,1(5)
39: OUT 0,0,0
13: JEQ 0,27(7)
40: LDA 7,0(7)
41: HALT 0,0,0
假设编译好的虚拟机的可执行文件是TM,而汇编代码文件是1.tm(这个虚拟机默认的可识别的汇编文件是*.tm),那么只需要简单的使用:
代码:
TM 1.tm
就可以了。
ok,第一个虚拟机的分析到此