没想到这篇文章持续了这么久,越学越深,愣是又买了一本书《计算机系统概论》,当然,也看完了,受益匪浅。
系统化的学习才是正确的学习方式,我大学就没看到过这本书,如果早点看到,可能会更好一些,当然,现在也不晚。
我主要是学习其中原理本质,不至于迷失在书中,要偏向于实践。
资源环境
LC-3 是一个16位地址空间的虚拟机。
1. 地址空间 0-UInt16.MaxValue (0xFFF)。
2. 通用寄存器8个,R0-R7
3. 三个标志寄存器,N ,P, Z
4. PC 寄存器
5. 指令集长度16位,操作码固定前四位
6. 支持函数调用
指令集
一共16条指令,可用14条。
1. BR 条件分支
2. ADD 加法
3. LD Load 加载
4. ST store 存储
5. JSR 跳转到寄存器
6. AND 与运算
7. LDR 加载寄存器
8. STR 存储寄存器
9. RTI 备用
10. NOT 取反
11. LDI 间接寻址 加载
12. STI 存储,间接寻址
13. Jump (RET) 跳转 返回
14. reserved 备用
15. LEA 加载偏移寄存器
16. TRAP 中断,系统函数调用
解析操作
首先,通过生成命令,汇编asm to bin or obj 文件,其实他们的内容是一致的。
第一行指令,是汇编指令.ORIG的起始地址,所以,要先把它提取出来。
其他命令,都按照下表的格式进行解析
格式也很简单,前四个位,是命令头,直接匹配,后面按照条件位或者寄存器位分割即可。
我这边主要是对 bin文件直接读取,省的 obj 文件,进制互转了。为了容易理解。
需要处理的命令
在上篇文章所介绍的LC3Edit 软件里,输入下边的汇编代码,这是第一步。
.ORIG x3000
LEA R0, HELLO_STR
PUTs
HALT
HELLO_STR .STRINGZ "Hello World!"
WIDTH .FILL x4000
.END
汇编的主要含义是,加载字符串 hello world! ,打印出来,然后,停止运行。
然后按照,2点击 to ASM 汇编,然后,第三步3就会提示0错误,0异常,然后,第四步就会输出相应的编译文件。
我只选择 hello.bin文件进行解析,当然,你解析其他相关文件也是一样的逻辑。只是会复杂一些。
bin文件,格式如下:
整体逻辑
环境
定义了 内存大小和寄存器,以及系统函数调用
public string[] Memory = new string[UInt16.MaxValue];
public Dictionary<Registers, UInt16> Reg = new Dictionary<Registers, UInt16>();
public Dictionary<TrapSet, Action> Traps = new Dictionary<TrapSet, Action>();/// <summary>
/// 寄存器定义
/// </summary>
public enum Registers
{R0 = 0, R1, R2, R3, R4, R5, R6, R7, R8, PC
}public UInt16 PC
{get{return Reg[Registers.PC];}set{Reg[Registers.PC] = value;}
}/// <summary>
/// 标志寄存器
/// </summary>
public enum FlagRegister
{/// <summary>/// 正数/// </summary>POS = 1,/// <summary>/// 0/// </summary>ZRO,/// <summary>/// 负数/// </summary>NEG
}public enum TrapSet
{/// <summary>/// 从键盘输入/// </summary>TRAP_GETC = 0x20,/// <summary>/// 输出字符/// </summary>TRAP_OUT = 0x21,/// <summary>/// 输出字符串/// </summary>TRAP_PUTS = 0x22,/// <summary>/// 打印输入提示,读取单个字符/// </summary>TARP_IN = 0x23,/// <summary>/// 输出字符串/// </summary>TRAP_PUTSP = 0x24,/// <summary>/// 退出程序/// </summary>TRAP_HALT = 0x25,
}
/// <summary>
/// 标志寄存器
/// </summary>
public FlagRegister COND { get; set; }//系统调用函数注册
Traps.Add(TrapSet.TRAP_GETC, Trap_GETC);
Traps.Add(TrapSet.TRAP_OUT, TRAP_OUT);
Traps.Add(TrapSet.TRAP_PUTS, TRAP_PUTS);
Traps.Add(TrapSet.TARP_IN, TRAP_IN);
Traps.Add(TrapSet.TRAP_PUTSP, TRAP_PUTSP);
Traps.Add(TrapSet.TRAP_HALT, TRAP_HALT);/// <summary>
/// 指令集
/// </summary>
public enum InstructionSet
{/// <summary>/// 条件分支/// </summary>BR = 0,/// <summary>/// 加法/// </summary>ADD = 1,/// <summary>/// load/// </summary>LD = 2,/// <summary>/// store/// </summary>ST = 3,/// <summary>/// 跳转到寄存器/// </summary>JSR = 4,/// <summary>/// 与运算/// </summary>AND = 5,/// <summary>/// 加载寄存器/// </summary>LDR = 6,/// <summary>/// 存储寄存器/// </summary>STR = 7,/// <summary>/// 备用/// </summary>RTI = 8,/// <summary>/// 取反/// </summary>NOT = 9,/// <summary>/// 间接寻址 加载/// </summary>LDI = 10,/// <summary>/// 存储 间接寻址/// </summary>STI = 11,/// <summary>/// 直接跳/// </summary>JMP = 12,/// <summary>/// reserved/// </summary>RES = 13,/// <summary>/// 加载偏移地址/// </summary>LEA = 14,/// <summary>/// 陷阱,中断,系统函数调用/// </summary>TRAP = 15
}
入口
入口比较简单,直接加载,虚拟机执行。
VM VM = new VM();
VM.LoadBin(File.ReadAllLines(@"hello\hello.bin"));
VM.Run();
Console.WriteLine("LC_3 !");
解析
解析就是把每个命令都解析到具体的命令对象上
public void LoadBin(string[] bincode)
{var ORIG_Base = Convert.ToUInt16(bincode.First(), 2);var ORIG = ORIG_Base;foreach (var item in bincode.Skip(1)){Memory[ORIG] = item;ORIG++;}PC = ORIG_Base;
}
执行
直接执行解析出来的命令集合
从内存中取数据
public void Run()
{IsRuning = true;while (IsRuning){var info = Memory[PC];var cmd = GetCommand(info);switch (cmd.InstructionSet){case InstructionSet.ADD:Add((ADDCommand)cmd);break;case InstructionSet.AND:And((ANDCommand)cmd);break;case InstructionSet.NOT:Not((NOTCommand)cmd);break;case InstructionSet.BR:BR((BRCommand)cmd);break;case InstructionSet.JMP:Jump((JMPCommand)cmd);break;case InstructionSet.JSR:Jump_Subroutine((JSRCommand)cmd);break;case InstructionSet.LD:Load((LDCommand)cmd);break;case InstructionSet.LDI:Load_Indirect((LDICommand)cmd);break;case InstructionSet.LDR:Load_Register((LDRCommand)cmd);break;case InstructionSet.LEA:Load_Effective_address((LEACommand)cmd);break;case InstructionSet.ST:Store((STCommand)cmd);break;case InstructionSet.STI:Store_indirect((STICommand)cmd);break;case InstructionSet.STR:Store_register((STRCommand)cmd);break;case InstructionSet.TRAP:Trap((TRAPCommand)cmd);break;#region 未用case InstructionSet.RTI:break;case InstructionSet.RES:break;#endregion}PC++;}Console.WriteLine("虚拟机停止了运行!");
}
指令解析,统一化
public class ANDCommand : ACommand
{public ANDCommand() : base(InstructionSet.AND){bitInfo.AddInfo(nameof(this.InstructionSet), 15, 12);bitInfo.AddInfo(nameof(this.DR), 11, 9);bitInfo.AddInfo(nameof(this.SR1), 8, 6);bitInfo.AddInfo(nameof(this.IsImmediateNumber), 5, 5, nameof(this.ImmediateNumber), nameof(this.SR2));bitInfo.AddInfo(nameof(this.ImmediateNumber), 4, 0);bitInfo.AddInfo(nameof(this.SR2), 2, 0);}/// <summary>/// 目的寄存器/// </summary>public Registers DR { get; set; }/// <summary>/// 源寄存器1/// </summary>public Registers SR1 { get; set; }public bool IsImmediateNumber { get; set; }/// <summary>/// 源寄存器2/// </summary>public Registers SR2 { get; set; }public UInt16 ImmediateNumber { get; set; }
}
初始化的时候,根据指令集的前四位操作码,直接定位到命令对象,对象,就会按照指令本身的各个位的含义去初始化各个寄存器等信息。
在解析的时候,只需要 GetCommand(info); 即可
public static ACommand GetCommand(string item)
{InstructionSet set = (InstructionSet)Convert.ToInt32(new string(item.Take(4).ToArray()), 2);ACommand aCommand = null;switch (set){case InstructionSet.BR:{aCommand = new BRCommand();aCommand.BinToCommand(item);}break;case InstructionSet.ADD:{aCommand = new ADDCommand();aCommand.BinToCommand(item);}break;case InstructionSet.LD:{aCommand = new LDCommand();aCommand.BinToCommand(item);}break;case InstructionSet.ST:{aCommand = new STCommand();aCommand.BinToCommand(item);}break;case InstructionSet.JSR:{aCommand = new JSRCommand();aCommand.BinToCommand(item);}break;case InstructionSet.AND:{aCommand = new ANDCommand();aCommand.BinToCommand(item);}break;case InstructionSet.LDR:{aCommand = new LDRCommand();aCommand.BinToCommand(item);}break;case InstructionSet.STR:{aCommand = new STRCommand();aCommand.BinToCommand(item);}break;case InstructionSet.RTI:{aCommand = new RTICommand();aCommand.BinToCommand(item);}break;case InstructionSet.NOT:{aCommand = new NOTCommand();aCommand.BinToCommand(item);}break;case InstructionSet.LDI:{aCommand = new LDICommand();aCommand.BinToCommand(item);}break;case InstructionSet.STI:{aCommand = new STICommand();aCommand.BinToCommand(item);}break;case InstructionSet.JMP:{aCommand = new JMPCommand();aCommand.BinToCommand(item);}break;case InstructionSet.RES:{aCommand = new RESCommand();aCommand.BinToCommand(item);}break;case InstructionSet.LEA:{aCommand = new LEACommand();aCommand.BinToCommand(item);}break;case InstructionSet.TRAP:{aCommand = new TRAPCommand();aCommand.BinToCommand(item);}break;}return aCommand;
}
指令函数示例
其实就是对特定逻辑操作的封装,指令基础操作,跟之前的几乎一样。
ADD
public void Add(ADDCommand command)
{if (command.IsImmediateNumber){Reg[command.DR] = (ushort)(Reg[command.SR1] + command.ImmediateNumber);}else{Reg[command.DR] = (ushort)(Reg[command.SR1] + Reg[command.SR2]);}Console.WriteLine($"{command.DR} : {Reg[command.DR]}");Update_flags(Registers.R0);
}
AND
public void And(ANDCommand command)
{if (command.IsImmediateNumber){Reg[command.DR] = (ushort)(Reg[command.SR1] + command.ImmediateNumber);}else{Reg[command.DR] = (ushort)(Reg[command.SR1] + Reg[command.SR2]);}Console.WriteLine($"{command.DR} : {Reg[command.DR]}");Update_flags(Registers.R0);
}
NOT
public void Not(NOTCommand command)
{Reg[command.DR] = (ushort)~Reg[command.SR];Update_flags(Registers.R0);
}
JUMP
public void Jump(JMPCommand command)
{PC = Reg[command.BaseR];
}
Trap
public void Trap(TRAPCommand command)
{Traps.TryGetValue((TrapSet)command.Trapverct, out var action);action?.Invoke();
}
运行结果
可喜可贺,终于搞出来了。
总结
只能说获益匪浅,很多东西,就算是很简单,你也得动手敲一遍,它就成了技能,而不仅仅留存在书和脑海的记忆中。
做完一件事情,再继续做下一件事情,这就是计算机的原理。
当然,LC-3细节还有很多没有补足,我想,对我来讲最重要的事它物理组件之间的逻辑关系,而不是其他。
代码地址
https://github.com/kesshei/VirtualMachineDemo.git
https://gitee.com/kesshei/VirtualMachineDemo.git
阅
一键三连呦!,感谢大佬的支持,您的支持就是我的动力!