计算机组成原理Ⅱ课程设计 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
,一定能找到错误所在。
其他备注
助教真帅