接下來,就是要實現一個虛擬機了。記得編碼高質量的代碼中有一條:不要過早地優化你的代碼。因此,也本着按部就班的原則,我將從實現一個解釋器開始,逐步過渡到JIT動態編譯器,這樣的演化能夠使原理看起來更清晰。函數
解釋器的原理很簡單,就是一條指令一條指令的解釋並執行。具體流程分爲:取出指令-解碼指令-執行-返回主流程。這樣造成一個無限循環,以下圖所示:oop
這裏的主流程就是上篇定義的程序rom.bin。但rom.bin不能直接運行,須要一個解釋器來包裹它,來解釋執行。解釋器放在一個無限循環中,使得主流程無限運行不中止:優化
void loop() { for(;;) { Interpreter(&CPUREG); } }
這樣,整個虛擬機的運行能夠定義爲:編碼
memInit(); //初始化內存 ResetCPU(&CPUREG); //初始化CPU loadROM(); //加載rom.bin loop(); //執行主流程 memFree(); //釋放內存
接下來須要作的就是取出指令送入解釋器了。爲此須要定義讀寫內存的函數memGet和memSet:指針
void memSet(unsigned int, unsigned char); unsigned char memGet(unsigned int); void memSet(unsigned int addr, unsigned char data) { char Str_Err[256]; if(addr>64) { sprintf(Str_Err, "MEM: invalid mem write: 0x%8x", addr); MessageBox(NULL, Str_Err, "Warning", MB_OK); } else { RAM[addr & 0xff]=data; } } unsigned char memGet(unsigned int addr) { char Str_Err[256]; unsigned char val = 0; if(addr>64) { sprintf(Str_Err, "MEM: invalid mem read: 0x%8x", addr); MessageBox(NULL, Str_Err, "Warning", MB_OK); } else { val=RAM[addr & 0xff]; } return val; }
讀寫均爲一個字節。因爲上篇定義的CPU尋址範圍只有64字節大小,因此超過64字節就要給出錯誤提示。code
而後須要爲每個CPU指令機器碼實現一個解碼執行函數:blog
void nop(REG*); void mov(REG*); void add(REG*); void cmp(REG*); void jmp(REG*); void jcp(REG*); void nop(REG* cpuREG) { cpuREG->R_PC++; sprintf("NOP\n"); } void mov(REG* cpuREG) { memSet(cpuREG->R_PC+1, memGet(cpuREG->R_PC+2)); sprintf("MOV [0x%4x], [0x%4x]\n", cpuREG->R_PC+1, cpuREG->R_PC+2); cpuREG->R_PC+=3; } void add(REG* cpuREG) { memSet(cpuREG->R_PC+1, memGet(cpuREG->R_PC+1)+memGet(cpuREG->R_PC+2)); sprintf("ADD [0x%4x], [0x%4x]\n", cpuREG->R_PC+1, cpuREG->R_PC+2); cpuREG->R_PC+=3; } void cmp(REG* cpuREG) { if((memGet(cpuREG->R_PC+1)-memGet(cpuREG->R_PC+2)) < 0) { cpuREG->R_CMP=0; } else { cpuREG->R_CMP=1; } sprintf("CMP [0x%4x], [0x%4x]\n", cpuREG->R_PC+1, cpuREG->R_PC+2); cpuREG->R_PC+=3; } void jmp(REG* cpuREG) { sprintf("JMP [0x%4x] \n", cpuREG->R_PC+1); cpuREG->R_PC=memGet(cpuREG->R_PC+1); } void jcp(REG* cpuREG) { sprintf("JCP [0x%4x], [0x%4x]\n", cpuREG->R_PC+1, cpuREG->R_PC+2); if(cpuREG->R_CMP==0) { cpuREG->R_PC=memGet(cpuREG->R_PC+1); } else { cpuREG->R_PC=memGet(cpuREG->R_PC+2); } }
這裏最重要的是要當心處理PC寄存器。一開始CPU初始化的時候,PC寄存器是設爲0的,而自定義的rom.bin也是從0地址開始執行的。若是你虛擬的CPU不是從0地址開始執行,那麼在CPU初始化的時候就要把PC寄存器設爲相應的開始地址。另外每一條指令可能涉及的地址數不相同,那麼PC寄存器的變更也要不一樣。最後,跳轉指令也可能要根據比較寄存器的內容來改變PC寄存器。內存
作了如上的準備以後就能夠實現解釋器了。這裏用switch-case結構來決定哪條指令被執行。爲了簡單起見,用了一個函數指針來執行解碼函數:編譯器
void (*func)(REG*); //Interpreter void Interpreter(REG* cpuREG) { char Str_Err[256]; switch(memGet(cpuREG->R_PC)) { case 0: func=nop; break; case 1: func=mov; break; case 2: func=add; break; case 3: func=cmp; break; case 4: func=jmp; break; case 5: func=jcp; break; default: sprintf(Str_Err, "Unhandled Opcode (0x%4x) at [0x%4x]", memGet(cpuREG->R_PC), cpuREG->R_PC); MessageBox(NULL, Str_Err, "Warning", MB_OK); return; } func(cpuREG); }
首先從內存中取出數據,根據機器碼來決定執行解碼函數,最後執行。執行結果以下:虛擬機