計算機組成原理Ⅱ課程設計 PA2.2&2.3
目錄
思考題
-
什么是 API
API(Application Programming Interface,應用程序接口)是一些預先定義的函數,或指軟件系統不同組成部分銜接的約定。用來提供應用程序與開發人員基於某軟件或硬件得以訪問的一組例程,而又無需訪問原碼,或理解內部工作機制的細節。
-
AM 屬於硬件還是軟件?
AM屬於軟件,是對底層的抽象。
AM 和操作系統一樣。AM是一個抽象計算機模型,通過一組API實現對計算機底層細節的抽象,為程序運行提供最基本的軟件支持,是最貼近硬件的軟件。操作系統是位於硬件與軟件之間,而AM也是位於NEMU(硬件)與軟件之間,他們的功能都是通過API實現對硬件的抽象,我認為他們是一樣的。
-
堆和棧在哪里?
因為堆和棧中會頻繁的進行例如出棧入棧等的數據交換操作,若放進可執行文件中讀取速度會變慢。根據理論課上所講內容,堆和棧存在於內存中,需要動態申請。
-
回憶運⾏過程
- 根據
ARCH=x86-nemu
,可知我們讓 AM 項目上的程序默認編譯到x86-nemu
的 AM 中 - 根據
ALL=dummy
,make run
的命令最終會調用nexus-am/am/arch/x86-nemu/img/run
來啟動 NEMU,運行dummy.c
程序
- 根據
-
神奇的eflags(2)
+-----+-----+------------------------------------+ | SF | OF | 實例 | +-----+-----+------------------------------------+ | 0 | 0 | 2 - 1 | +-----+-----+------------------------------------+ | 0 | 1 | 0xf0000000-0x00000001 | +-----+-----+------------------------------------+ | 1 | 0 | 0x80000001-0x00000001 | +-----+-----+------------------------------------+ | 1 | 1 | 0x0f000000-0x00000001 | +-----+-----+------------------------------------+
-
這是巧合嗎?
-
ja
無符號整數
op1>op2
CF=0 AND ZF=0
-
jb
無符號整數
op1<op2
CF=1 AND ZF=0
-
jg
帶符號整數
op1>op2
SF=OF AND ZF=0
-
jl
帶符號整數
op1<op2
SF≠OF
-
-
nemu的本質
lable1: x = x - 1 a = a + 1 jne x, lable1 lable2: y = y - 1 a = a + 1 jne y, lable2
NEMU
還需:- 要輸入輸出功能,沒有輸入輸出,就沒什么意義。
- 圖形界面來進行交互。
-
設備是如何⼯作的?
IO接口是連接CPU與外部設備的邏輯控制部件,其功能是協調CPU和外設之間的工作。CPU通過接口發送指令給設備,設備接受指令並執行后把結果通過接口傳回CPU。
-
CPU 需要知道設備是如何⼯作的嗎?
不需要。我感覺設備的接口相當於API,CPU只需要把所需要的數據或指令傳給設備,然后等待設備傳回結果即可,根本無需知道設備如何運作。
-
什么是驅動?
驅動,計算機軟件術語,是指驅動計算機里軟件的程序。驅動程序全稱設備驅動程序,是添加到操作系統中的特殊程序,其中包含有關硬件設備的信息。此信息能夠使計算機與相應的設備進行通信。驅動程序是硬件廠商根據操作系統編寫的配置文件,可以說沒有驅動程序,計算機中的硬件就無法工作。
驅動是操作系統與硬件之間的橋梁,操作系統有了驅動,才能調度硬件。一般應用程序在操作系統之上,利用操作系統提供的API完成相應的任務,不與硬件打交道。
-
cpu知道嗎?
不需要,只需要把指定地址上的值定為指定的值即可。
-
再次理解volatile
O2優化下編譯
gcc -O2 -o fun fun.c
查看反匯編
objdump -s -d fun > fun.txt
添加
volatile
關鍵字的反匯編不添加
volatile
關鍵字的反匯編若不加
volatile
,則從反匯編中可看出少了很多指令,第11a7
行會陷入死循環。 -
hello world運⾏在哪⾥?
不一樣。這個運行在AM層,程序設計課上學的運行在內存等硬件層上。
-
如何檢測很多個鍵同時被按下?
當按下一個鍵的時候, 鍵盤將會發送該鍵的通碼; 當釋放一個鍵的時候, 鍵盤將會發送該鍵的斷碼。每當用戶敲下/釋放按鍵時, 將會把相應的鍵盤碼放入數據寄存器, 同時把狀態寄存器的標志設置為
1
, 表示有按鍵事件發生. CPU 可以通過端口 I/O 訪問這些寄存器, 獲得鍵盤碼。每個按鍵的通碼斷碼都不同,因此計算機可以識別出同時按下的不同的按鍵。 -
編譯與鏈接Ⅰ
-
去掉
static
沒遇報錯
-
去掉
inline
該函數僅用
static
修飾,則為靜態函數,只能被該函數所在文件引用,然而該文件並沒用其他函數使用該函數,因此導致defined but not used
-
去掉
static
和inline
當多個文件包含同一個頭文件時,而頭文件中沒有加上條件編譯,就會獨立的解釋,然后生成每個文件生成獨立的標示符。在編譯器連接時,就會將工程中所有的符號整合在一起,由於,文件中有重名變量,於是就出現了重復定義的錯誤。
-
-
編譯與鏈接Ⅱ
-
29個
此時
dummy
是靜態局部變量,可以看到有29個文件重新編譯,則有29個變量實體。 -
58
由於沒初始化,兩次定義的符號都是弱符號,編譯器允許弱符號多次定義且編譯器會把這兩次當作兩個不同的符號對待。重新編譯后,仍然是那29個文件重新編譯,則又多了29個該變量實體,則一共58個。
-
初始化
問題:變量重定義
原因:變量賦初值后則成為強符號,編譯器不允許強符號多次定義,則報錯。
-
-
I/O 端⼝與接⼝
-
地址范圍:
1K=2^10,端口范圍
0000H
~0400H
16根地址總線,尋址范圍也就是216,因為1K=210,所以尋址范圍為216/210=64K,地址范圍
0000H
~FFFFH
-
I/O三種控制方式:程序直接控制、終端控制、DMA控制。
例子:
首先DMA控制器初始化,然后發送“啟動DMA傳送”命令以啟動外設進行I/O操作,發送完“啟動DMA傳送”命令后, CPU轉去執行其他進程,而請求I/O的用戶進程被阻塞。在CPU執行其他進程的過程中,DMA控制器外設和主存進行數據交換。DMA控制器每完成一個數據的傳送,就將字計數器減1,並修改主存地址,當字計數器為0時,完成所有I/O操作,此時,DMA控制器將向CPU發送“DMA完成”中斷請求,CPU檢測到后調出相應的中斷服務程序執行。CPU在中斷服務程序中,解除用戶進程的阻塞狀態而使用戶進程進入就緒序列,然后中斷返回,再回到被打斷的進程繼續執行。
常見於硬盤。
課本P383~P384
-
-
git log截圖
實驗內容
實現剩余所有X86指令
1.add.c
-
實現
8d(lea)
查表發現是
LEA
指令,其譯碼函數是lea_M2G
,執行函數lea
填表
/* 0x8c */ EMPTY, IDEX(lea_M2G,lea), EMPTY, EMPTY,
運行成功截圖
-
實現
8c(and)
查看反匯編,發現是
and
指令,查i386
可知譯碼函數是
SI2E
,則需要去實現SI
,唯一要注意的是要實現位擴展,否則會報錯。static inline make_DopHelper(SI) { assert(op->width == 1 || op->width == 4); op->type = OP_TYPE_IMM; op->simm=instr_fetch(eip,op->width);//取操作數 rtl_li(&op->val,op->simm); rtl_sext(&op->val,&op->val,op->width);//位擴展 op->simm=op->val;//更新simm #ifdef DEBUG snprintf(op->str, OP_STR_SIZE, "$0x%x", op->simm); #endif }
執行函數是
and
,查i386
可知,需要實現相與並且更新標志位。make_EHelper(and) { rtl_and(&id_dest->val,&id_dest->val,&id_src->val);//目的操作數與源操作數相與 operand_write(id_dest,&id_dest->val);//寫入目的操作數 t0=0; rtl_set_CF(&t0);//設置CF位為0 rtl_set_OF(&t0);//設置OF位為0 rtl_update_ZFSF(&id_dest->val,id_dest->width);//更新ZFSF位 print_asm_template2(and); }
填表
make_group(gp1, EMPTY, EMPTY, EMPTY, EMPTY, EX(and), EX(sub), EMPTY, EMPTY)
運行成功截圖
-
實現
ff(push)
查看反匯編可知是
push
,查看i386
填表
make_group(gp5, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EX(push), EMPTY)
運行成功截圖
-
51(push)
查看
i386
,發現從50~57
都需要實現push
,所以,一次性全部添加!填表
/* 0x50 */ IDEX(r,push), IDEX(r,push), IDEX(r,push), IDEX(r,push),/* 0x54 */ IDEX(r,push), IDEX(r,push), IDEX(r,push), IDEX(r,push),
運行成功截圖
-
eb(jmp)
譯碼函數
J
執行函數
jmp
由手冊可知,操作數長度為1字節
填表
/* 0xe8 */ IDEXW(J,call,4), EMPTY, EMPTY, IDEXW(J,jmp,1),
運行成功截圖
-
83(cmp)
查看
i386
和反匯編,發現要實現cmp
閱讀
i386
,可知cmp
其實就是sub
,只是不需要賦值而已。直接使用
sub
的代碼即可make_EHelper(cmp) { rtl_sub(&t2, &id_dest->val, &id_src->val); rtl_sltu(&t3, &id_dest->val, &t2);//t3記錄是否借位,0表示借位 rtl_update_ZFSF(&t2, id_dest->width);//更新ZF,SF rtl_sltu(&t0, &id_dest->val, &t2);//與減去借位后再比 rtl_or(&t0, &t3, &t0); rtl_set_CF(&t0); rtl_xor(&t0, &id_dest->val, &id_src->val); rtl_xor(&t1, &id_dest->val, &t2); rtl_and(&t0, &t0, &t1); rtl_msb(&t0, &t0, id_dest->width); rtl_set_OF(&t0); print_asm_template2(cmp); }
填表
make_group(gp1, EMPTY, EMPTY, EX(adc), EMPTY, EX(and), EX(sub), EMPTY, EX(cmp))
運行成功截圖
-
76(jbe)
譯碼函數
J
執行函數
jcc
需要實現
rtl_setcc
查詢
i386
,可知對照要求,依次實現不同
cc
對eflags
的讀取即可switch (subcode & 0xe) { case CC_O: rtl_get_OF(dest); break; case CC_B: rtl_get_CF(dest); break; case CC_E: rtl_get_ZF(dest); break; case CC_BE: rtl_get_CF(&t0); rtl_get_ZF(&t1); rtl_or(dest, &t0, &t1); //小於等於,CF和ZF至少一個要等於1才行 break; case CC_S: rtl_get_SF(dest); break; case CC_L: rtl_get_SF(&t0); rtl_get_OF(&t1); rtl_xor(dest,&t0,&t1); break; case CC_LE: rtl_get_ZF(&t0); rtl_get_SF(&t1); rtl_get_OF(&t2); rtl_xor(&t3, &t1, &t2); rtl_or(dest, &t0, &t3); //帶符號數的小於等於,ZF=1或者SF不等於OF break; default: panic("should not reach here"); case CC_P: panic("n86 does not have PF"); }
填表
/* 0x74 */ EMPTY, EMPTY, IDEXW(J,jcc,1), EMPTY,
運行成功截圖
-
01(add)
譯碼函數
G2E
執行函數
add
,模仿adc
,除去CF
即可make_EHelper(add) { rtl_add(&t2, &id_dest->val, &id_src->val); rtl_sltu(&t3, &t2, &id_dest->val); operand_write(id_dest, &t2); rtl_update_ZFSF(&t2, id_dest->width); rtl_sltu(&t0, &t2, &id_dest->val); rtl_or(&t0, &t3, &t0); rtl_set_CF(&t0); rtl_xor(&t0, &id_dest->val, &id_src->val); rtl_not(&t0); rtl_xor(&t1, &id_dest->val, &t2); rtl_and(&t0, &t0, &t1); rtl_msb(&t0, &t0, id_dest->width); rtl_set_OF(&t0); print_asm_template2(add); }
填表
/* 0x00 */ EMPTY, IDEX(G2E,add), EMPTY, EMPTY,
運行成功截圖
-
c9(leave)
無譯碼函數
執行函數
leave
,需要先esp <-- ebp
,再ebp <-- pop()
make_EHelper(leave) { rtl_mv(&cpu.esp,&cpu.ebp);//esp <-- ebp rtl_pop(&cpu.ebp);//ebp <-- pop() print_asm("leave"); }
運行成功截圖
-
0f 94(set)
發現需要閱讀第二個表,查看
94
譯碼函數
E
執行函數
setcc
,前面已實現填表
/* 0x94 */ IDEXW(E,setcc,1), EMPTY, EMPTY, EMPTY,
運行成功截圖
-
0f b6(movzbl)
譯碼函數
mov_E2G
執行函數
movxz
填表
/* 0xb4 */ EMPTY, EMPTY, IDEXW(mov_E2G,movzx,1), IDEXW(mov_E2G,movzx,2),
運行成功截圖
-
ff(inc)
執行函數
inc
,可知其只需要模仿add
指令加一即可。它影響OF SF ZF
標志位,其余與add
相同。make_EHelper(inc) { rtlreg_t a=1; rtl_add(&t2, &id_dest->val, &a);//+1 rtl_sltu(&t3, &t2, &id_dest->val); operand_write(id_dest, &t2); //更新ZF SF rtl_update_ZFSF(&t2, id_dest->width); //更新OF rtl_xor(&t0, &id_dest->val, &a); rtl_not(&t0); rtl_xor(&t1, &id_dest->val, &t2); rtl_and(&t0, &t0, &t1); rtl_msb(&t0, &t0, id_dest->width); rtl_set_OF(&t0); print_asm_template1(inc); }
填表
make_group(gp5, EX(inc), EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EX(push), EMPTY)
運行成功截圖
2.add-longlong.c
-
0f 86(jcc)
又遇到
jcc
,直接把表全部填完/* 0x80 */ IDEX(J,jcc), IDEX(J,jcc), IDEX(J,jcc), IDEX(J,jcc), /* 0x84 */ IDEX(J,jcc), IDEX(J,jcc), IDEX(J,jcc), IDEX(J,jcc), /* 0x88 */ IDEX(J,jcc), IDEX(J,jcc), IDEX(J,jcc), IDEX(J,jcc), /* 0x8c */ IDEX(J,jcc), IDEX(J,jcc), IDEX(J,jcc), IDEX(J,jcc),
-
11(adc)
譯碼函數
G2E
執行函數
adc
填表
/* 0x10 */ EMPTY, IDEX(G2E,adc), EMPTY, EMPTY,
運行成功截圖
-
5b(pop)
在
PA2.1
遇到過,這里把表補充完整即可。/* 0x58 */ IDEX(r,pop), IDEX(r,pop), IDEX(r,pop), IDEX(r,pop), /* 0x5c */ IDEX(r,pop), IDEX(r,pop), IDEX(r,pop), IDEX(r,pop),
運行成功截圖
-
33(xor)
在
PA2.1
遇到過,這里把表補充完整即可。/* 0x30 */ IDEXW(G2E,xor,1), IDEX(G2E,xor), IDEXW(E2G,xor,1), IDEX(E2G,xor), /* 0x34 */ IDEXW(I2a,xor,1), EMPTY, EMPTY, EMPTY,
運行成功截圖
-
09(or)
譯碼函數
G2E
執行函數
or
,把目的操作數與操作數1取或即可,再把CF OF
設為0,更新SF ZF
。make_EHelper(or) { //取或 rtl_or(&id_dest->val,&id_dest->val,&id_src->val); operand_write(id_dest,&id_dest->val); t0=0; rtl_set_CF(&t0);//CF<--0 rtl_set_OF(&t0);//OF<--0 rtl_update_ZFSF(&id_dest->val,id_dest->width); print_asm_template2(or); }
填表
/* 0x08 */ IDEXW(G2E,or,1), IDEX(G2E,or), IDEXW(E2G,or,1), IDEX(E2G,or), /* 0x0c */ IDEXW(I2a,or,1), IDEX(I2a,or), EMPTY, EX(2byte_esc),
運行成功截圖
-
85(test)
譯碼函數
G2E
執行函數
test
,兩個操作數相與后存入目的操作數,再把CF OF
設為0,更新SF ZF
。重點注意:
test
只修改標志位,不把相與結果寫入目的操作數make_EHelper(test) { rtl_and(&id_dest->val,&id_dest->val,&id_src->val);//目的操作數與源操作數相與 t0=0; rtl_set_CF(&t0);//設置CF位為0 rtl_set_OF(&t0);//設置OF位為0 rtl_update_ZFSF(&id_dest->val,id_dest->width);//更新ZFSF位 print_asm_template2(test); }
填表
/* 0x84 */ IDEXW(G2E,test,1), IDEX(G2E,test), EMPTY, EMPTY,
運行成功截圖
3.bubble-sort.c
-
2b(sub)
PA2.1
實現sub
,這里只需填表即可。/* 0x28 */ IDEXW(G2E,sub,1), IDEX(G2E,sub), IDEXW(E2G,sub,1), IDEX(E2G,sub), /* 0x2c */ IDEXW(I2a,sub,1), EMPTY, EMPTY, EMPTY,
運行成功截圖
4.fact.c
-
4b(dec)
譯碼函數
r
執行函數
dec
,與inc
相差的地方就是減1make_EHelper(dec) { rtlreg_t a=1; rtl_sub(&t2, &id_dest->val, &a);//-1 rtl_sltu(&t3, &t2, &id_dest->val); operand_write(id_dest, &t2); rtl_update_ZFSF(&t2, id_dest->width); rtl_xor(&t0, &id_dest->val, &a); rtl_not(&t0); rtl_xor(&t1, &id_dest->val, &t2); rtl_and(&t0, &t0, &t1); rtl_msb(&t0, &t0, id_dest->width); rtl_set_OF(&t0); print_asm_template1(dec); }
填表
/* 0x48 */ IDEX(r,dec), IDEX(r,dec), IDEX(r,dec), IDEX(r,dec), /* 0x4c */ IDEX(r,dec), IDEX(r,dec), IDEX(r,dec), IDEX(r,dec),
運行成功截圖
-
0f af(imul)
譯碼函數
E2G
查看反匯編,發現是兩個操作數,所以執行函數
imul2
填表
/* 0xac */ EMPTY, EMPTY, EMPTY, IDEX(E2G,imul2),
運行成功截圖
5.leap-year.c
-
05(add)
譯碼函數
I2r
執行函數
add
填表
/* 0x00 */ IDEXW(G2E,add,1), IDEX(G2E,add), IDEXW(E2G,add,1), IDEX(E2G,add), /* 0x04 */ IDEXW(I2a,add,1), IDEX(I2a,add), EMPTY, EMPTY,
運行成功截圖
-
99(cltd)
查看反匯編,發現無譯碼函數
執行函數
cltd
,分為操作數是16位還是32位。當操作數為16位時,若ax<0
則給dx
賦值0xffff
否則賦值0
;操作數為32位時,若eax<0
則給edx
賦值0xffffffff
否則賦值0
;make_EHelper(cltd) { if (decoding.is_operand_size_16) { if ((int16_t)(cpu.eax&0xffff)<0) {//ax<0 cpu.edx=0xffff; } else { cpu.edx=0; } } else { if ((int32_t)(cpu.eax)<0) {//eax<0 cpu.edx=0xffffffff; } else { cpu.edx=0; } } print_asm(decoding.is_operand_size_16 ? "cwtl" : "cltd"); }
運行成功截圖
順便實現
98(cwtl)
,就是對寄存器的符號擴展。make_EHelper(cwtl) { if (decoding.is_operand_size_16) { rtl_lr_b(&t0, R_AX); rtl_sext(&t0, &t0, 1); rtl_sr_w(R_AX, &t0); } else { rtl_lr_w(&t0, R_AX); rtl_sext(&t0, &t0, 2); rtl_sr_l(R_EAX, &t0); } print_asm(decoding.is_operand_size_16 ? "cbtw" : "cwtl"); }
3. `f7(idiv)`
執行函數`idiv`
填表
```c
make_group(gp3, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EX(idiv))
運行結果截圖
6.load-store.c
-
of bf(movsx)
譯碼函數
E2G
執行函數
movsx
填表
/* 0xbc */ EMPTY, EMPTY, IDEXW(E2G,movsx,1), IDEXW(E2G,movsx,2),
運行成功截圖
-
c1(shl)
譯碼函數
I2E
執行函數
shl
make_EHelper(shl) { rtl_shl(&id_dest->val,&id_dest->val,&id_src->val);//邏輯左移 operand_write(id_dest,&id_dest->val); rtl_update_ZFSF(&id_dest->val,id_dest->width); // unnecessary to update CF and OF in NEMU print_asm_template2(shl); }
同時把
shr
sar
一起完成make_EHelper(shr) { rtl_shr(&id_dest->val,&id_dest->val,&id_src->val);//邏輯右移 operand_write(id_dest,&id_dest->val); rtl_update_ZFSF(&id_dest->val,id_dest->width); // unnecessary to update CF and OF in NEMU print_asm_template2(shr); } make_EHelper(sar) { rtl_sar(&id_dest->val,&id_dest->val,&id_src->val); operand_write(id_dest,&id_dest->val); rtl_update_ZFSF(&id_dest->val,id_dest->width); // unnecessary to update CF and OF in NEMU print_asm_template2(sar); }
填表
make_group(gp2, EMPTY, EMPTY, EMPTY, EMPTY, EX(shl), EX(shr), EMPTY, EX(sar))
運行成功截圖
-
f7(not)
查看反匯編發現是
not
譯碼函數
E
執行函數
not
,調用rtl_not
即可make_EHelper(not) { rtl_not(&id_dest->val);//取反 operand_write(id_dest,&id_dest->val); print_asm_template1(not); }
填表
make_group(gp3, EMPTY, EMPTY, EX(not), EMPTY, EMPTY, EMPTY, EMPTY, EX(idiv))
運行成功截圖
7.matrix-mul.c
直接通過
8.mov-c.c
直接通過
9.mul-longlong.c
-
f7(mul)
執行函數
mul
填表
make_group(gp3, EMPTY, EMPTY, EX(not), EMPTY, EX(mul), EMPTY, EMPTY, EX(idiv))
運行成功截圖
10.sub-longlong.c
-
1b(sub)
填表
/* 0x18 */ IDEXW(G2E,sbb,1), IDEX(G2E,sbb), IDEXW(E2G,sbb,1), IDEX(E2G,sbb), /* 0x1c */ IDEXW(I2a,sbb,1), EMPTY, EMPTY, EMPTY,
運行成功
11.fib.c
直接通過
12.if-else.c
直接通過
13.pascal.c
-
3b(cmp)
之前已經實現,直接填表即可。
/* 0x38 */ IDEXW(G2E,cmp,1), IDEX(G2E,cmp), IDEXW(E2G,cmp,1), IDEX(E2G,cmp), /* 0x3c */ IDEXW(I2a,cmp,1), IDEX(I2a,cmp), EMPTY, EMPTY,
運行成功
14.recursion
-
6a(push)
譯碼函數
push_SI
執行函數
push
填表,查看反匯編,一個定長,一個不定長
/* 0x68 */ IDEX(push_SI,push), EMPTY, IDEXW(push_SI,push,1), EMPTY,
-
ff(call)
執行函數
call_rm
,首先給is_jmp賦值1,然后給jmp_eip賦值id_dest->val,最后push eipmake_EHelper(call_rm) { decoding.is_jmp = 1; decoding.jmp_eip = id_dest->val; rtl_push(eip); print_asm("call *%s", id_dest->str); }
填表
make_group(gp5, EX(inc), EMPTY, EX(call_rm), EX(call), EMPTY, EMPTY, EX(push), EMPTY)
運行成功
15.shuixianhua.c
直接通過
16.sum.c
直接通過
17.unalign.c
直接通過
18.goldbach.c
直接通過
19.max.c
直接通過
20.movsx.c
直接通過
21.prime.c
直接通過
22.select-sort.c
直接通過
23.switch.c
-
ff(jmp)
執行函數
jmp
前面已實現,直接填表make_group(gp5, EX(inc), EMPTY, EX(call_rm), EX(call), EX(jmp), EX(jmp), EX(push), EMPTY)
運行成功
24.wanshu.c
直接通過
25.bit.c
-
22(and)
填表
/* 0x20 */ IDEXW(G2E,and,1), IDEX(G2E,and), IDEXW(E2G,and,1), IDEX(E2G,and), /* 0x24 */ IDEXW(I2a,and,1), IDEX(I2a,and), EMPTY, EMPTY,
-
0f 95(setne)
之前也實現過,直接填表即可。
/* 0x90 */ IDEXW(E,setcc,1), IDEXW(E,setcc,1), IDEXW(E,setcc,1), IDEXW(E,setcc,1), /* 0x94 */ IDEXW(E,setcc,1), IDEXW(E,setcc,1), IDEXW(E,setcc,1), IDEXW(E,setcc,1), /* 0x98 */ IDEXW(E,setcc,1), IDEXW(E,setcc,1), IDEXW(E,setcc,1), IDEXW(E,setcc,1), /* 0x9c */ IDEXW(E,setcc,1), IDEXW(E,setcc,1), IDEXW(E,setcc,1), IDEXW(E,setcc,1),
運行成功
26.min3.c
直接通過
27.quick-sort.c
直接通過
28.shift.c
直接通過
29.to-lower-case.c
直接通過
30.dummy
直接通過
31.string.c
直接通過
32.hello-str.c
直接通過
通過一鍵回歸測試
IN/OUT指令
查詢i386
手冊,發現有兩個地方需要填表,分別是e4~e7
ec~ef
in
:通過pio_read
讀取指定位置的值並寫入目的操作數即可。
make_EHelper(in) {
id_dest->val=pio_read(id_src->val,id_dest->width);
operand_write(id_dest,&id_dest->val);
print_asm_template2(in);
#ifdef DIFF_TEST
diff_test_skip_qemu();
#endif
}
out
:通過pio_write
讀取指定位置的值輸出即可。
make_EHelper(out) {
pio_write(id_dest->val,id_dest->width,id_src->val);
print_asm_template2(out);
#ifdef DIFF_TEST
diff_test_skip_qemu();
#endif
}
填表
/* 0xe4 */ IDEXW(in_I2a,in,1), IDEX(in_I2a,in), IDEXW(out_a2I,out,1), IDEX(out_a2I,out),
/* 0xec */ IDEXW(in_dx2a,in,1), IDEX(in_dx2a,in), IDEXW(out_a2dx,out,1), IDEX(out_a2dx,out),
在 nexus-am/am/arch/x86-nemu/src/trm.c
中定義宏 HAS_SERIAL
在 nexus-am/apps/hello
目錄下鍵入make run
實現時鍾設備
在nexus-am/am/arch/x86-nemu/src/ioe.c
中實現_uptime()
unsigned long _uptime() { return inl(RTC_PORT)-boot_time;//獲取當前時間,然后減去初始時間}
運行成功
運行跑分項目
-
dhrystone
native
x86-nemu
-
coremark
f7(neg)
未實現執行函數
neg
若目的操作數是0,則CF設為0否則設為1。
neg
是取相反數,計算后的結果存入id_dest
並更新ZF SF OF(其中更新OF模仿sbb即可)make_EHelper(neg) { //更新CF if(id_dest->val==0){ t0=0; rtl_set_CF(&t0); }else{ t0=1; rtl_set_CF(&t0); } //更新ZF SF id_dest->val=-id_dest->val; operand_write(id_dest,&id_dest->val); rtl_update_ZFSF(&id_dest->val, id_dest->width); //更新OF rtl_xor(&t0, &id_dest->val, &id_src->val); rtl_xor(&t1, &id_dest->val, &t2); rtl_and(&t0, &t0, &t1); rtl_msb(&t0, &t0, id_dest->width); rtl_set_OF(&t0); print_asm_template1(neg); }
填表
make_group(gp3, EMPTY, EMPTY, EX(not), EX(neg), EX(mul), EX(imul1), EX(div), EX(idiv))
跑分結果
native
x86-nemu
-
microbench
需要實現
rol
看
i386
每太看懂它想表達什么意思,我取網上又查了查,發現這個指令是循環左移,並最后修改CF
,根據這個描述,我自己嘗試寫了一下,最后通過了。思路是每次移位前都先獲取最高位,然后補在最低位。最后別忘了更新
CF
make_EHelper(rol) { //循環左移指定次數 for (t0=0;t0<id_src->val;t0++) { //先獲取最高位,即即將移出的位 rtl_shri(&t1,&id_dest->val,id_dest->width*8-1); //左移一位 rtl_shli(&t2,&id_dest->val,1); //把移出的位補在最低位 id_dest->val=t1+t2; } //設置CF rtl_set_CF(&t1); //記錄到目的操作數 operand_write(id_dest,&id_dest->val); print_asm_template2(rol); }
還需要實現
(c2)ret
這個
ret與c3(ret)
使用的執行函數不同,這令我困惑了很久很久。特別需要注意,若要是ret帶一個立即數,則需要修改
esp
的值。為此,我添加了一個新的執行函數
reti
make_EHelper(reti) { decoding.is_jmp=1;//設置跳轉 rtl_pop(&decoding.jmp_eip);//獲取跳轉位置 cpu.esp+=id_dest->val;//修改esp print_asm("ret %x", id_dest->val); }
填表
/* 0xc0 */ IDEXW(gp2_Ib2E, gp2, 1), IDEX(gp2_Ib2E, gp2), IDEXW(I,reti,2), EX(ret),
這里譯碼函數選擇
I
,寬度選擇2
,因為后面的立即數要求是16位的。跑分結果
native
x86-nemu
實現鍵盤設備
在nexus-am/am/arch/x86-nemu/src/ioe.c
中,找到_read_key()
函數
發現該函數目前只返回_KEY_NONE
。
講義中說明了 0x60
處的端口作為數據寄存器, 0x64
處的端口作為狀態寄存器. 每當用戶敲下/釋放按鍵時, 將會把相應的鍵盤碼放入數據寄存器, 同時把狀態寄存器的標志設置為 1
, 表示有按鍵事件發生. CPU 可以通過端口 I/O 訪問這些寄存器, 獲得鍵盤碼。
我們可以判斷0x64
端口處的狀態是否是1,若是,則去0x60
端口取數據。
int _read_key() {
uint32_t make_code=_KEY_NONE;
if (inb(0x64)) {//讀取0x64處的一個字節,判斷狀態
make_code=inl(0x60);//讀取0x60處的數據
}
return make_code;
}
運行成功
添加內存映射 I/O
在nemu/src/memory/memory.c
中完善paddr_read()
和paddr_write()
其中,根據講義,我們要調用is_mmio
mmio_read
mmio_write
函數,所以要引入頭文件
#include "device/mmio.h"
兩個函數思路差不多,都是先用is_mmio
判斷一個物理地址是否被映射到 I/O 空間如果是, is_mmio()
會返回映射號。 否則返回 -1
.。內存映射 I/O 的訪問需要調用 mmio_read()
或 mmio_write()
,,調用時需要提供映射號。 如果不是內存映射 I/O 的訪問, 就訪問 pmem
。
uint32_t paddr_read(paddr_t addr, int len) {
int mmio_id=is_mmio(addr);//判斷
if (mmio_id != -1)//是內存映射
return mmio_read(addr, len, mmio_id);
else
return pmem_rw(addr, uint32_t) & (~0u >> ((4 - len) << 3));
}
void paddr_write(paddr_t addr, int len, uint32_t data) {
int mmio_id=is_mmio(addr);
if (mmio_id != -1)
return mmio_write(addr, len, data, mmio_id);
else
memcpy(guest_to_host(addr), &data, len);
}
運行成功
運行打字小游戲
native
x86-nemu
遇到的問題及解決辦法
-
遇到問題:
cputest
全部通過,但跑分程序運行不了。解決方案:最初我簡單的認為只要把
cputest
通過了,就代表所有指令都正確填寫完畢了,后來通過跑分程序意識到並不是這樣。剛開始調試,我不懂的技巧,就直接si,浪費了很多時間。后來我選擇打開nemu/include/common.h
里的DEBUG
,然后直接按c
運行程序,等程序出錯停止后,去build/nemu-log.txt
,看其最后一行,就知道大概是哪里出錯了。 -
遇到問題:跑分程序所有都
PASS
並HIT GOOG POINT
,但仍然出現爆棧問題,看群里好多人出現了該問題。解決方案:我當時很疑惑,覺得跑分程序所有都
PASS
並HIT GOOG POINT
,一定是對了,怎么還會爆棧?一定是框架有問題(·_·)但是,采用上述方法,我找到了是
c2(ret)
實現的有問題,具體解決方案上面介紹了,這里就不贅述了。這個問題找了好幾天,當時解決了特別特別興奮。
實驗心得
本次實驗最大心得就是不要懷疑框架,一定是我某條指令實現的有問題,打開DEBUG
和diff-test
,一定能找到錯誤所在。
其他備注
助教真帥