计算机组成原理Ⅱ课程设计 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