計算機組成原理Ⅱ課程設計 PA2.2&2.3


計算機組成原理Ⅱ課程設計 PA2.2&2.3

目錄

思考題

  1. 什么是 API

    API(Application Programming Interface,應用程序接口)是一些預先定義的函數,或指軟件系統不同組成部分銜接的約定。用來提供應用程序與開發人員基於某軟件或硬件得以訪問的一組例程,而又無需訪問原碼,或理解內部工作機制的細節。

  2. AM 屬於硬件還是軟件?

    AM屬於軟件,是對底層的抽象。

    AM 和操作系統一樣。AM是一個抽象計算機模型,通過一組API實現對計算機底層細節的抽象,為程序運行提供最基本的軟件支持,是最貼近硬件的軟件。操作系統是位於硬件與軟件之間,而AM也是位於NEMU(硬件)與軟件之間,他們的功能都是通過API實現對硬件的抽象,我認為他們是一樣的。

  3. 堆和棧在哪里?

    因為堆和棧中會頻繁的進行例如出棧入棧等的數據交換操作,若放進可執行文件中讀取速度會變慢。根據理論課上所講內容,堆和棧存在於內存中,需要動態申請。

  4. 回憶運⾏過程

    1. 根據ARCH=x86-nemu,可知我們讓 AM 項目上的程序默認編譯到 x86-nemu 的 AM 中
    2. 根據ALL=dummymake run 的命令最終會調用 nexus-am/am/arch/x86-nemu/img/run 來啟動 NEMU,運行dummy.c程序
  5. 神奇的eflags(2)

    +-----+-----+------------------------------------+
    |  SF |  OF |               實例                  |
    +-----+-----+------------------------------------+
    |  0  |  0  |               2 - 1                |
    +-----+-----+------------------------------------+
    |  0  |  1  |         0xf0000000-0x00000001      |
    +-----+-----+------------------------------------+
    |  1  |  0  |         0x80000001-0x00000001      |
    +-----+-----+------------------------------------+
    |  1  |  1  |         0x0f000000-0x00000001      |
    +-----+-----+------------------------------------+
    
  6. 這是巧合嗎?

    • 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

  7. nemu的本質

    lable1:
    	x = x - 1
    	a = a + 1
    	jne x, lable1
    	
    lable2:
    	y = y - 1
    	a = a + 1
    	jne y, lable2
    

    NEMU還需:

    • 要輸入輸出功能,沒有輸入輸出,就沒什么意義。
    • 圖形界面來進行交互。
  8. 設備是如何⼯作的?

    IO接口是連接CPU與外部設備的邏輯控制部件,其功能是協調CPU和外設之間的工作。CPU通過接口發送指令給設備,設備接受指令並執行后把結果通過接口傳回CPU。

    https://www.jianshu.com/p/d0c57c5b350e

  9. CPU 需要知道設備是如何⼯作的嗎?

    不需要。我感覺設備的接口相當於API,CPU只需要把所需要的數據或指令傳給設備,然后等待設備傳回結果即可,根本無需知道設備如何運作。

  10. 什么是驅動?

驅動,計算機軟件術語,是指驅動計算機里軟件的程序。驅動程序全稱設備驅動程序,是添加到操作系統中的特殊程序,其中包含有關硬件設備的信息。此信息能夠使計算機與相應的設備進行通信。驅動程序是硬件廠商根據操作系統編寫的配置文件,可以說沒有驅動程序,計算機中的硬件就無法工作。

驅動是操作系統與硬件之間的橋梁,操作系統有了驅動,才能調度硬件。一般應用程序在操作系統之上,利用操作系統提供的API完成相應的任務,不與硬件打交道。

  1. cpu知道嗎?

    不需要,只需要把指定地址上的值定為指定的值即可。

  2. 再次理解volatile

    O2優化下編譯

    gcc -O2 -o fun fun.c
    

    查看反匯編

    objdump -s -d fun > fun.txt
    

    添加volatile關鍵字的反匯編

    不添加volatile關鍵字的反匯編

    若不加volatile,則從反匯編中可看出少了很多指令,第11a7行會陷入死循環。

  3. hello world運⾏在哪⾥?

    不一樣。這個運行在AM層,程序設計課上學的運行在內存等硬件層上。

  4. 如何檢測很多個鍵同時被按下?

    當按下一個鍵的時候, 鍵盤將會發送該鍵的通碼; 當釋放一個鍵的時候, 鍵盤將會發送該鍵的斷碼。每當用戶敲下/釋放按鍵時, 將會把相應的鍵盤碼放入數據寄存器, 同時把狀態寄存器的標志設置為 1, 表示有按鍵事件發生. CPU 可以通過端口 I/O 訪問這些寄存器, 獲得鍵盤碼。每個按鍵的通碼斷碼都不同,因此計算機可以識別出同時按下的不同的按鍵。

  5. 編譯與鏈接Ⅰ

    • 去掉static

      沒遇報錯

    • 去掉inline

      該函數僅用static修飾,則為靜態函數,只能被該函數所在文件引用,然而該文件並沒用其他函數使用該函數,因此導致defined but not used

    • 去掉staticinline

      當多個文件包含同一個頭文件時,而頭文件中沒有加上條件編譯,就會獨立的解釋,然后生成每個文件生成獨立的標示符。在編譯器連接時,就會將工程中所有的符號整合在一起,由於,文件中有重名變量,於是就出現了重復定義的錯誤。

  6. 編譯與鏈接Ⅱ

    1. 29個

      此時dummy是靜態局部變量,可以看到有29個文件重新編譯,則有29個變量實體。

    2. 58

      由於沒初始化,兩次定義的符號都是弱符號,編譯器允許弱符號多次定義且編譯器會把這兩次當作兩個不同的符號對待。重新編譯后,仍然是那29個文件重新編譯,則又多了29個該變量實體,則一共58個。

    3. 初始化

      問題:變量重定義

      原因:變量賦初值后則成為強符號,編譯器不允許強符號多次定義,則報錯。

  7. I/O 端⼝與接⼝

    1. 地址范圍:

      1K=2^10,端口范圍0000H~0400H

      16根地址總線,尋址范圍也就是216,因為1K=210,所以尋址范圍為216/210=64K,地址范圍0000H~FFFFH

    2. 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

  8. git log截圖

實驗內容

實現剩余所有X86指令


1.add.c

  1. 實現8d(lea)

    查表發現是LEA指令,其譯碼函數是lea_M2G,執行函數lea

    填表

    /* 0x8c */  EMPTY, IDEX(lea_M2G,lea), EMPTY, EMPTY,
    

    運行成功截圖

  2. 實現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)
    

    運行成功截圖

  3. 實現ff(push)

    查看反匯編可知是push,查看i386

    填表

    make_group(gp5,    EMPTY, EMPTY, EMPTY, EMPTY,    EMPTY, EMPTY, EX(push), EMPTY)
    

    運行成功截圖

  4. 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),
    

    運行成功截圖

  5. eb(jmp)

    譯碼函數J

    執行函數jmp

    由手冊可知,操作數長度為1字節

    填表

    /* 0xe8 */	IDEXW(J,call,4), EMPTY, EMPTY, IDEXW(J,jmp,1),
    

    運行成功截圖

  6. 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))
    

    運行成功截圖

  7. 76(jbe)

    譯碼函數J

    執行函數jcc

    需要實現rtl_setcc

    查詢i386,可知

    對照要求,依次實現不同cceflags的讀取即可

      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,
    

    運行成功截圖

  8. 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,
    

    運行成功截圖

  9. 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");
     }
    
    

    運行成功截圖

  10. 0f 94(set)

    發現需要閱讀第二個表,查看94

    譯碼函數E

    執行函數setcc,前面已實現

    填表

    /* 0x94 */	IDEXW(E,setcc,1), EMPTY, EMPTY, EMPTY,
    

    運行成功截圖

  11. 0f b6(movzbl)

    譯碼函數mov_E2G

    執行函數movxz

    填表

    /* 0xb4 */	EMPTY, EMPTY, IDEXW(mov_E2G,movzx,1), IDEXW(mov_E2G,movzx,2),
    

    運行成功截圖

  12. 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

  1. 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),
    
  2. 11(adc)

    譯碼函數G2E

    執行函數adc

    填表

    /* 0x10 */	EMPTY, IDEX(G2E,adc), EMPTY, EMPTY,
    

    運行成功截圖

  3. 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),
    

    運行成功截圖

  4. 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,
    

    運行成功截圖

  5. 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),
    

    運行成功截圖

  6. 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

  1. 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

  1. 4b(dec)

    譯碼函數r

    執行函數dec,與inc相差的地方就是減1

     make_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),
    

    運行成功截圖

  2. 0f af(imul)

    譯碼函數E2G

    查看反匯編,發現是兩個操作數,所以執行函數imul2

    填表

    /* 0xac */	EMPTY, EMPTY, EMPTY, IDEX(E2G,imul2),
    

    運行成功截圖

5.leap-year.c

  1. 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,
    

    運行成功截圖

  2. 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

  1. of bf(movsx)

    譯碼函數E2G

    執行函數movsx

    填表

    /* 0xbc */	EMPTY, EMPTY, IDEXW(E2G,movsx,1), IDEXW(E2G,movsx,2),
    

    運行成功截圖

  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))
    

    運行成功截圖

  3. 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

  1. f7(mul)

    執行函數mul

    填表

    make_group(gp3,    EMPTY, EMPTY, EX(not), EMPTY,    EX(mul), EMPTY, EMPTY, EX(idiv))
    

    運行成功截圖

10.sub-longlong.c

  1. 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

  1. 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

  1. 6a(push)

    譯碼函數push_SI

    執行函數push

    填表,查看反匯編,一個定長,一個不定長

    /* 0x68 */	IDEX(push_SI,push), EMPTY, IDEXW(push_SI,push,1), EMPTY,
    
  2. ff(call)

    執行函數call_rm,首先給is_jmp賦值1,然后給jmp_eip賦值id_dest->val,最后push eip

     make_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

  1. 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

  1. 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,
    
  2. 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;//獲取當前時間,然后減去初始時間}

運行成功

運行跑分項目


  1. dhrystone

    native

    x86-nemu

  2. 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

  3. 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

遇到的問題及解決辦法

  1. 遇到問題:cputest全部通過,但跑分程序運行不了。

    解決方案:最初我簡單的認為只要把cputest通過了,就代表所有指令都正確填寫完畢了,后來通過跑分程序意識到並不是這樣。剛開始調試,我不懂的技巧,就直接si,浪費了很多時間。后來我選擇打開nemu/include/common.h里的DEBUG,然后直接按c運行程序,等程序出錯停止后,去build/nemu-log.txt,看其最后一行,就知道大概是哪里出錯了。

  2. 遇到問題:跑分程序所有都PASSHIT GOOG POINT,但仍然出現爆棧問題,看群里好多人出現了該問題。

    解決方案:我當時很疑惑,覺得跑分程序所有都PASSHIT GOOG POINT,一定是對了,怎么還會爆棧?一定是框架有問題(·_·)

    但是,采用上述方法,我找到了是c2(ret)實現的有問題,具體解決方案上面介紹了,這里就不贅述了。這個問題找了好幾天,當時解決了特別特別興奮。

實驗心得

本次實驗最大心得就是不要懷疑框架,一定是我某條指令實現的有問題,打開DEBUGdiff-test,一定能找到錯誤所在。

其他備注

助教真帥


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM