460 likes | 582 Views
汇编语言程序设计. 吴 向 军. 中山大学计算机科学系. 2003.03.20. 第 11 章 数值运算协处理器. 数值运算协处理器 ( 简称协处理器 ) 是特为与微处理器协同工作而设计的,它是用于加速处理浮点数据的处理部件。对同样的浮点计算,使用该部件进行运算所花的执行时间要比用常规指令编写的最有效代码所花的时间还要少得多。在早期的计算机系统中,该部件是可选部件,但现在一般都把协处理器直接内置在 CPU 之中。鉴于现在 Pentium 处理器内部结构的特点,该处理器能同时执行一条协处理器指令和二条整数指令。
E N D
汇编语言程序设计 吴 向 军 中山大学计算机科学系 2003.03.20
第11章 数值运算协处理器 数值运算协处理器(简称协处理器)是特为与微处理器协同工作而设计的,它是用于加速处理浮点数据的处理部件。对同样的浮点计算,使用该部件进行运算所花的执行时间要比用常规指令编写的最有效代码所花的时间还要少得多。在早期的计算机系统中,该部件是可选部件,但现在一般都把协处理器直接内置在CPU之中。鉴于现在Pentium处理器内部结构的特点,该处理器能同时执行一条协处理器指令和二条整数指令。 协处理器主要产品有:8087、80287、80387SX、80387DX和80487SX等。 协处理器可处理的数据类型有:16位、32位和64位有符号整数,18位BCD码,32位、64位和80位浮点数。 协处理器可处理的运算有:乘法、除法、加法、减法、求平方根、部分正切、部分反正切和对数等运算。
第11章 数值运算协处理器 11.1 协处理器的数据格式 11.1.1 有符号整数 有符号数在协处理器中的应用与前面章节中所描述的方法是一致的,它是各种数据类型的基础。这些整数可分为:16位(字型)、32位(短整型)和64位(长整型),其最高位为符号位。
第11章 数值运算协处理器 11.1.2 BCD码数据 • 一个BCD码数据在内存中占80位,共10个字节。其最高位字节用来表示正负号,其余9个字节,每个字节内含有二个BCD码,所以,一个BCD码数据可表示18个BCD编码。 • 关于BCD码的正负数,有如下规定: • 若最高位字节的值为0H,则表示该BCD码的值为正数 • 若最高位字节的值为80H,则表示该BCD码的值为负数 第9个字节 第1个字节 第0个字节 在汇编语言环境下,BCD码数据的定义符为:DT。如: .387 BCD1 DT 1234, -340 该说明语句决定了数据在内存中的存储形式如下: 00000000000000001234,80000000000000000340
第11章 数值运算协处理器 11.1.3 浮点数 在计算机中,浮点数一般由三部分组成:数值的符号位、阶码和有效数字(以后简称为尾数)。这种浮点数是用科学记数法来表示的,即: 浮点数=符号位.有效数字×2阶码。 Intel系列的协处理器支持3种形式的浮点数:短型浮点数(32位)、长型浮点数(64位)和临时浮点数(80位),它们分别对应单精度、双精度和扩展精度浮点数。 一、十进制数转换成浮点数的步骤 1、将十进制数转换成二进制数:整数部分用2来除,小数部分用2来乘; 2、规格化二进制数:改变阶码,使小数点前面仅有第一位有效数字; 3、计算阶码: 短型浮点数的阶码加上偏移量7FH 长型浮点数的阶码加上偏移量3FFH 扩展型浮点数的阶码加上偏移量3FFFH 4、以浮点数据格式存储。 把数值的符号位、阶码和尾数合在一起就得到了该数的浮点存储形式。
第11章 数值运算协处理器 例11.1把十进制数100.25转换成协处理器中的浮点数 解: 1、进制转换:(100.25)10=(1100100.01)2 2、规格化:(1100100.01)2=1.10010001×26=1.10010001×2110 3、计算阶码:110+01111111=10000101 4、数值的符号位为0, 阶码为:10000101,尾数为:1001 0001 0000 0000 0000 000 综合上述可得:(100.25)10的浮点形式为: 0 10000101 10010001000000000000000 几个特殊数据的存储规则: 正0: 所有的数据位都是0; 负0: 最高位为1,其它的数据位是0; 正/负无穷: 符号位为0/1,阶码位全为1,有效数字全为0; NAN: 非法的浮点数,阶码位全为1,有效数字不全为0; 其中:NAN — Not-A-Number。
第11章 数值运算协处理器 二、浮点数转换成十进制数的步骤 该步骤与前面“十进制数转换成浮点数”的步骤是互逆的,其具体步骤如下: 1、分割数字的符号、阶码和有效数字; 2、将偏移阶码减去偏移,得到真正的阶码; 3、把数字写成规格化的二进制数形式; 4、把规格化的二进制数改变成非规格化的二进制数; 5、把非规格化的二进制数转换成十进制数。 例11.2把浮点数1100000111001001000000000000转换成十进制数。 解: 1、把浮点数1100000111001001000000000000分割成三部分,可得: 符号位是1,阶码是10000011,尾数是1001001000000000000 2、还原阶码:10000011 – 01111111=100 3、该浮点数的规格化形式:1.1001001×24 (其中前面的“1.”从隐含位而来) 4、该浮点数的非规格化形式:11001.001 5、该浮点数的十进制数为-25.125 (因为符号位为1,所以,该数是负数)
第11章 数值运算协处理器 三、浮点数说明形式 在汇编语言中,可用DD、DQ和DT来分别说明单精度、双精度和扩展精度的浮点数。在MASM 6.0系统中,正浮点数前面不能书写‘+’,但MASM 6.11系统更正了这种错误,并提供了新的浮点数说明方法,即:可用REAL4、REAL8和REAL10来分别代替DD、DQ和DT。 在定义浮点数时,要使用伪指令.8087、.287或.387等。 例如: .387 data1 DD 123, -543 ;定义单精度浮点数 data2 REAL4 3.345E+3 ;定义单精度浮点数 data3 REAL8 321.545 ;定义双精度浮点数 data4 REAL10 254.555 ;定义扩展精度浮点数
第11章 数值运算协处理器 11.2 协处理器的结构 协处理器,顾名思义,是为与CPU协同工作而设计的,其主要用来提高进行数学和超越函数计算的速度。在80486DX和Pentium处理器中都内置一个与80387完全兼容的协处理器。CPU执行所有的常规指令,协处理器则执行协处理器指令,它们能同时并行地执行各自的指令。由于现在Pentium处理器内部结构的特点,该处理器能同时执行一条协处理器指令和二条整数指令。 11.2.1 协处理器的内部结构 协处理器80x87的内部结构如下图所示。它可分为二个主要部分:控制部件(CU)和数值执行部件(NEU)。
第11章 数值运算协处理器 控制部件(CU)把协处理器接到CPU的系统总线上,协处理器和CPU都监视正在执行的指令流。如果当前将要执行的指令是协处理器指令(即:ESCape指令),那么,协处理器会自动执行它,否则,该指令将交给CPU来执行。 数值执行部件(NEU)复制执行所有的协处理器指令,它有一个用8个80位的寄存器组成的堆栈,该堆栈用于以扩展精度的浮点数据格式来存放数学指令的操作数和运算结果。在协处理器指令的执行过程中,要么指定该堆栈寄存器中的数据,要么使用压栈/出栈机制来从栈顶存放或读取数据。 在NEU部件中,还有一些记录协处理器工作状态的寄存器,如:状态寄存器、控制寄存器、标记寄存器和异常指针寄存器等。有关这些寄存器的作用将在后面给予分别介绍。
第11章 数值运算协处理器 11.2.2 状态寄存器 状态寄存器是用来标识协处理器中指令执行情况的,它相当于CPU中的标志位寄存器。80x87协处理器的状态寄存器如下所示。 图11.580x87协处理器的状态寄存器示意图 请见:状态寄存器各标志位(或组合位)的含义。
第11章 数值运算协处理器 可用有关浮点指令读取状态寄存器内容,当其内容传给AX之后,一般可用下面二种方法来检测协处理器的状态。 方法1:用TEST指令来检测其相应的状态位。 例11.3检测是否有“0作除数”的错误。 FDIV DATA1 ;用协处理器中堆顶数据去除DATA1 FSTSW AX ;把状态寄存器的值传送给AX TEST AX, 4 ;测试第2位,即:检测ZE是否为1 JNZ DIV_ERR 例11.4检测是否有“非法操作数”的错误。 FSQRT ;求协处理器中堆顶数据的平方根 FSTSW AX TEST AX, 1 ;测试第0位,即:检测IE是否为1 JNZ SQRT_ERR
第11章 数值运算协处理器 方法2:用SAHF指令把AX的低字节传送给CPU的标志位寄存器,然后再用条件转移指令来完成相应的检测。 例11.5 检测内存单元的数据与协处理器堆顶数据之间的大小关系。 FCOM DATA1 ;DATA1的值与协处理器堆顶数据进行比较 FSTSW AX SAHF ;把AX的低字节存入CPU的状态寄存器 JE ST_EQUAL ;具体大小关系见表11.2中的“FCOM” JB ST_BELOW JA ST_ABOVE
第11章 数值运算协处理器 11.2.3 控制寄存器 控制寄存器主要用于浮点数精度选择的控制、四舍五入的控制和无穷大的控制等,其低6位还可用来决定是否屏蔽协处理器的异常。指令FLDCW可用来设置控制寄存器的值。控制寄存器中控制位的分布如下所示,其控制位的含义如下表所列。 图11.6控制寄存器的控制位分布示意图
第11章 数值运算协处理器 表11.3 控制寄存器中控制位的含义
第11章 数值运算协处理器 11.2.4 标记寄存器 标记寄存器用来表明协处理器堆栈中各存储单元内容的状态,也就是说,该寄存器可表明堆栈中的数据是合法的,还是非法的,是无穷,还是0或空等。该标记寄存器的结构如下图所示。 图11.7标记寄存器结构示意图 其中,TAG(i)的取值含义:00—合法,01—0,10—非法或无穷,11—空 在协处理器中,查看标记寄存器的方法是使用指令FSTENV、FSAVE或FRSTOR,它们都能使标记寄存器与其它协处理器数据一起转存。
第11章 数值运算协处理器 11.3 协处理器的指令系统 协处理器共有68条不同的指令,汇编程序在遇到协处理器指令助忆时,都会将其转换成机器语言的ESC指令,ESC指令代表了协处理器的操作码。 在协处理器指令在执行过程中,需要访问内存单元时,CPU会为其形成内存地址。协处理器在协处理器指令期间内利用数据总线来传递数据。 80287协处理器利用I/O地址00FAH~00FFH来实现其与CPU之间的数据交换,而80387~Pentium系列芯片,则是利用I/O地址800000FAH~800000FFH来实现这两者之间的数据交换。
第11章 数值运算协处理器 11.3.1 指令操作符的命名规则 协处理器指令的操作符(或助忆符)在命名设计时,遵循了下列规则: 1、在操作符后面加上字母P:表示该指令执行完后,还进行一次堆栈弹出操作。如:FADD和FADDP等; 2、在操作符后面加上字母R:表示该操作是反模式,它仅限于减法、除法指令。如:FSUB和FSUBR等; 正模式 —— 栈顶数据=栈顶数据 op 指令操作数,或OPN1=OPN1 – OPN2 反模式 —— 栈顶数据=指令操作数 op 栈顶数据,或OPN1=OPN2 – OPN1 假设:栈顶数据为10,变量data的值为1,执行下列指令将有不同的结果。 FSUB data ;指令执行后,栈顶数据为9 FSUBR data ;指令执行后,栈顶数据为-9 FSUB ST, ST(1) ;指令执行后,ST=ST-ST(1) FSUBR ST, ST(1) ;指令执行后,ST=ST(1)-ST
第11章 数值运算协处理器 3、操作符的第2个字母是I:表示内存中数据是整数。它对加、减、乘除指令都有效。 例如:FADD data——浮点数加法; FIADD data——整数加法,它表示内存单元data是一个整数,把该整数加到栈顶的浮点数上。 4、操作符的第2个字母是N:表示在指令执行之前检查非屏蔽数值性错误。如:FSAVE和FNSAVE等,前者称为等待形式(wait version),后者称为非等待形式(no-wait version)。 在使用.8087伪指令情况下,汇编程序会在等待形式的指令前面加上指令WAIT,而在非等待形式的指令前面加上空操作指令NOP。 理解了上述操作符命名规则,就能很容易地区分同类指令之间的差异。
第11章 数值运算协处理器 11.3.2 数据传送指令 为了满足协处理器和CPU之间进行数据交流的需求,就需要实现内存单元和协处理器之间进行数据传送的指令。协处理器的指令系统中有三大类数据传送指令:BCD传送指令、浮点数传送和整数传送指令。 一、BCD传送指令 1、FBLD 指令格式:FBLD MemBCD(*) 指令的功能:将内存中的BCD数据压入协处理器的堆栈中; 2、FBSTP 指令格式:FBSTP MemBCD 指令的功能:将协处理器中的BCD数据存入内存,并进行堆栈的弹出操作。 例如: .387 data1 DT123, -543 data2 DT2.5 …… FBLD data1 ;把BCD数据123压进栈 FBSTP data2 ;把当前堆顶数据弹出,并传送给BCD型的内参单元 (*) MemType是指定数据类型Type的内存单元,如:MemBCD是BCD类型的存储单元。
第11章 数值运算协处理器 二、浮点数传送指令 1、FLD 指令格式:FLD STReg(*)/MemReal 指令的功能:将浮点数据压入协处理器的堆栈中。当进行内存单元内容压栈时,系统会自动决定传送数据的精度。比如:用DD或REAL4定义的内存单元数值是单精度数等。 例如: .387 data1 DD 123, -543 data2 REAL8 -321.5 data3 REAL10 2.5 …… FLD data1 ;压一个单精度数据进栈 FLD data2 ;压一个双精度数据进栈 FLD ST(0) ;把堆栈寄存器ST(0)的值再压进栈 FLD data3 ;压一个扩展精度数据进栈 (*) STReg是协处理器堆栈寄存器ST(0)~ST(7)。
第11章 数值运算协处理器 2、FST 指令格式:FST STReg/MemReal 指令的功能:将协处理器堆栈栈顶的数据传送到目标操作数中。在进行数据传送时,系统自动根据控制寄存器中舍入控制位的设置把栈顶浮点数舍入成相应精度的数据。 3、FSTP 指令格式:FSTP STReg/MemReal 该指令的功能与FST相类似,所不同的是:指令FST执行完后,不进行堆栈的弹出操作,即:堆栈不发生变化,而指令FSTP执行完后,则需要进行堆栈的弹出操作,堆栈将发生变化。请见前面的指令操作符命名规则的说明。 4、FXCH 指令格式:FXCH [STReg] 指令的功能:将指定的寄存器中的浮点数与堆顶浮点数进行交换。如果不指定操作数,那么,默认ST和ST(1)二者之间交换数据。 例如:FXCH ST(2)——栈顶数据与堆栈寄存器ST(2)进行数据交换。
第11章 数值运算协处理器 三、整数传送指令 1、FILD 指令格式:FILD MemInt 其中:Mem是定义为整型数据类型的内存单元,但不能是用DB定义的存储单元。 2、FIST/FISTP 指令格式:FIST MemInt FISTP MemInt 其中:Mem是定义为整型数据类型的内存单元,但不能是用DB定义的存储单元。 指令的功能:将协处理器堆栈栈顶的数据传送到目标存储单元中。在进行数据传送时,系统自动根据控制寄存器中舍入控制位的设置把栈顶浮点数舍入成整型数据。 指令FIST和FISTP的区别在于堆栈操作,详细请见11.3.1中的命名规则说明。
第11章 数值运算协处理器 11.3.3 数学运算指令 在协处理器的指令系统中,有关数学运算指令有:加法指令、减法指令、乘法指令、除法指令和求平方根指令等。涉及数学运算的指令有比例运算、舍入运算、求绝对值运算和改变数值符号运算等指令。 1、加法指令 指令格式:FADD [STReg1, STReg2](*) FADD MemReal FADDP STReg, ST FIADD MemInt 指令FADD含有二个隐含操作数ST(1)和ST,其运算功能是:从堆栈中弹出这二个操作数,然后把计算的“和”压入堆栈,即:ST=ST(1)+ST。 指令“FADD MemReal”的功能:ST=ST+MemReal。 指令“FADDP STReg, ST”的功能:STReg=STReg+ST,并弹出堆栈的栈顶。 指令“FIADD MemInt”的功能:ST=ST+MemInt (*) 在此指令格式下,如果同时指定了二个堆栈寄存器,那么,其中一个寄存器必须是ST。其它指令的同类格式与此同理。
第11章 数值运算协处理器 2、减法指令 指令格式:FSUB [STReg1, STReg2] FSUB MemReal FSUBP STReg, ST FISUB MemInt FSUBR [STReg1, STReg2];后面指令是前面的反模式形式 FSUBR MemReal FSUBRP STReg,ST FISUBR MemInt 指令FSUB含有二个隐含操作数ST(1)和ST,其运算功能是:从堆栈中弹出这二个操作数,然后把计算的“差”压入堆栈,即:ST=ST(1)-ST。 指令“FSUB MemReal”的功能:ST=ST-MemReal。 指令“FSUBP STReg, ST”的功能:STReg=STReg-ST,并弹出堆栈的栈顶。 指令“FISUB MemInt”的功能:ST=ST-MemInt 反模式的四条指令的功能在此从略,请参阅11.3.1中的有关说明。
第11章 数值运算协处理器 3、乘法指令 指令格式:FMUL[STReg1, STReg2] FMULMemReal FMULP STReg, ST FIMULMemInt 4、除法指令 指令格式:FDIV[STReg1, STReg2] FDIVMemReal FDIVPSTReg, ST FIDIVMemInt FDIVR[STReg1, STReg2];后面指令是前面指令的反模式形式 FDIVRMemReal FDIVRP STReg, ST FIDIVRMemInt 5、其它数学运算指令 在协处理器中,除了完成具体的数学运算指令外,还设置了若干个与数学运算有关的运算指令。具体的运算指令及其功能描述如表11.4所列。
第11章 数值运算协处理器 例如: .387 word1 DW 20 data1 REAL8 8 data2 REAL8 -2 data3 REAL8 -12 …… FLD data1 ;本例只是显示指令的使用方法,无具体的实际功能 FLD data2 FLD data3 FDIV ST(2), ST FDIV data1 FDIVP ST(2), ST FIDIV word1
第11章 数值运算协处理器 11.3.4 比较运算指令 使用比较指令是将栈顶中的数与其它操作数进行比较,比较结果存于状态寄存器的条件编码位C3~C0处表。具体的比较运算指令及其功能描述如表11.5所列。
第11章 数值运算协处理器 11.3.5 超越函数运算指令
第11章 数值运算协处理器 11.3.6 常数操作指令 为了计算的方便,协处理器提供了几个将常用常数压栈的指令。 表11.7常数压栈指令及其常数值
第11章 数值运算协处理器 11.3.7 协处理器控制指令
第11章 数值运算协处理器 表11.9协处理器初始化的状态
第11章 数值运算协处理器 11.4 协处理器的编程举例 本节提供几个利用协处理器进行编程的例子,从这些例子可看出使用协处理器指令编程的方法和技巧。 例11.6假设圆的半径存于数组RAD中,计算出每个圆的面积,并存于数组AREA中。 例11.7已知L=0.0001,C=0.002,试计算下列公式的值,并存于单精度数F中。 例11.8已知L=4.0,F按每次递增10.0的幅度从10.0增加到1000.0,试按公式Y=2πFL,计算出100个Y值,并把它们存入数组RES中。 例11.9把内存单元DATA中存放的单精度浮点数以小数的形式显示在屏幕上。 例11.10从键盘上读入一个带小数的数字字符串,然后把它转换成单精度浮点数,并存入内存单元DATA中。
第12章 汇编语言和C语言 C/C++语言是一个被广泛使用的程序设计语言,它不仅具有良好的高级语言特征,而且还具有一些低级语言的特点,如:寄存器变量、位操作等。所以,C语言的程序与汇编语言程序之间能很平滑地衔接。 本章主要介绍汇编语言和C语言的混合编程和调用方法。虽然其它高级语言,如:Pascal、Basic等,也可与汇编语言混合使用,但出于其应用范围的考虑,不再对它们进行介绍,感兴趣的读者可参阅有关技术资料。
第12章 汇编语言和C语言 12.1 汇编指令的嵌入 为了提高C语言程序内某特殊功能段的处理效率,我们可以在其源程序中嵌入一段汇编语言程序段。这样做,虽然能达到提高了程序处理效率的目的,但它无疑以丧失源程序的可移植性为代价。所以,当想用C语言和汇编语言混合编程时,程序员需要权衡采用这种方法的利与弊。 在C语言中,嵌入汇编语言的语法如下(*): asm <opcode> <operands> <; or newline> 注意:这里的分号‘;’不是汇编语言中起注释作用的分号,而是作为语句的分隔符。 若C语言源程序中嵌入一条汇编语句,则可按下列方式来做: asm mov ax, data (*) 在此,仅以Turbo C/C++和Borland C/C++编程环境为参考。
第12章 汇编语言和C语言 若要嵌入一组汇编语句,则需要用括号‘{’和‘}’把它们括起来。 asm { movax, data1 xchgax, data2 movdata1, ax//实现整型变量data1和data2之值的交换 } 例12.1:在C语言源程序中嵌入汇编语言语句实现赋值语句A=A+B+C,其中:A、B、C都是整型变量。 解: …… asm { //实现整型变量A=A+B+C pushax movax, A addax, B addax, C movA, ax popax }
第12章 汇编语言和C语言 12.2 C语言源程序的汇编输出 在Turbo C++或Borland C++编程环境下,我们可TCC或BCC行命令把一个C语言的源程序转换成汇编语言的源程序。通过阅读汇编语言程序可以很准确地知道C语言语句的功能是如何实现的,这样,可为将来学习《编译原理》课程中的“寄存器调度”和“代码生成”等相关知识打下良好的基础。 C语言源程序转换的命令格式如下: TCC –S t1.cpp 或 BCC –S t1.cpp ;假设其文件名为t1.cpp 若命令TCC/BCC不带参数的话,则将显示其使用方法。 下面是C语言程序及其相对应的汇编语言程序,希望读者能逐行对照理解它们语句之间的转换关系,这将能进一步理解高级语言的语句功能。
第12章 汇编语言和C语言 1、C语言程序清单 #include <stdio.h> int sum(int a, int b, int c) { return (a+b+c); } void main() { int a, b, c; a = b = 12; c = 32; printf("%d", sum(a,b,c)); }
第12章 汇编语言和C语言 2、生成的汇编语言程序清单 …… ;一系列辅助说明信息 _TEXT segment byte public 'CODE' ;代码段的开始 ;int sum(int a, int b, int c) ;C语言语句 assume cs : _TEXT @sum$qiii proc near ;过程说明,对应于C语言sum过程 push bp mov bp, sp ;{ ; return(a+b+c); mov ax, word ptr [bp+4] add ax, word ptr [bp+6] add ax, word ptr [bp+8] jmp short @1@58 @1@58: ;} pop bp ;实现自定义函数sum的返回 ret @sum$qiii endp
第12章 汇编语言和C语言 ;void main() assume cs : _TEXT _main proc near ;过程说明,对应于C语言中的主函数mian() push bp mov bp, sp sub sp, 6 ;{int a, b, c; ;局部变量是用堆栈来存储的,请见第7.5.10节 ; a = b = 12; ;给局部变量赋值 mov ax, 12 ;用给堆栈单元赋值来实现对局部变量的赋值 mov word ptr [bp-4], ax mov word ptr [bp-2], ax ; c = 32; mov word ptr [bp-6], 32
第12章 汇编语言和C语言 ; printf(“%d”, sum(a,b,c));;调用系统标准函数 push word ptr [bp-6] push word ptr [bp-4] push word ptr [bp-2] call near ptr @sum$qiii;用汇编语言形式调用自定义函数sum add sp, 6 push ax mov ax, offset DGROUP : s@ push ax call near ptr _printf ;用汇编语言调用标准函数printf pop cx pop cx ;} mov sp, bp ;实现子程序main的返回 pop bp ret _main endp _TEXT ends ;代码段的结束
第12章 汇编语言和C语言 _DATA segment word public ‘DATA’;数据段的定义 s@ label byte db '%d' db 0 _DATA ends public _main ;下面说明函数的属性,请见第7.6.3节 public @sum$qiii extrn _printf : near _s@ equ s@ end
谢 谢 计算机科学系 2003年03月20日