第11章 数学协处理器_第1页
第11章 数学协处理器_第2页
第11章 数学协处理器_第3页
第11章 数学协处理器_第4页
第11章 数学协处理器_第5页
已阅读5页,还剩18页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、567第11章 数学协处理器第11章 数学协处理器内核目录kernel/math目录中包含数学协处理器仿真处理代码文件,共包含9个C语言程序,见表11-1。本章内容与具体硬件结构关系非常密切,因此需要读者具备较深的有关Intel CPU和协处理器指令代码结构的知识。但好在这些内容与内核实现关系不大,因此跳过本章内容并不会妨碍读者对内核实现方法的完整理解。不过若能理解本章内容,那么对于实现系统级应用程序(例如汇编和反汇编等程序)和编制协处理器浮点处理程序将有很大帮助。表11-1 linux/kernel/math目录名称大小/B最后修改时间名称大小/B最后修改时间 Makefile 337719

2、91-12-31 12:26:48 ea.c 1807 1991-12-31 11:57:05 add.c 1999 1992-01-01 16:42:02 error.c 234 1991-12-28 12:42:09 compare.c 904 1992-01-01 17:15:34 get_put.c 5145 1992-01-01 01:38:13 convert.c 4348 1992-01-01 19:07:43 math_emulate.c 11540 1992-01-07 21:12:05 div.c 2099 1992-01-01 01:41:43 mul.c 1517 19

3、92-01-01 01:42:33 11.1 总体功能描述在计算机上执行计算量较大的运算通常可以使用三种方法来完成。一种是直接使用CPU普通指令执行计算。由于CPU指令是一类通用指令,因此使用这些指令进行复杂和大量的运算工作需要编制复杂的计算子程序,并且一般只有通晓数学和计算机的专业人员才能编制出这些子程序。另一种方法是为CPU配置一个数学协处理器芯片。使用协处理器芯片可以极大地简化数学处理编程难度,并且运算速度和效率也会成倍提高,但需要另外增加硬件投入。还有一种方法是在系统内核级使用仿真程序来模拟协处理器的运算功能。这种方法可能是运算速度和效率最低的一种,但与使用了协处理器一样可以方便程序员

4、编制计算程序,并且能够在对程序不加任何改动的情况下把所编程序运行在具有协处理器的机器上。在Linux 0.1x甚至Linux 0.9x内核开发初期,数学协处理器芯片80387(或其兼容芯片)价格不菲,并且一直是普通PC中的奢侈品。因此除非在科学计算量很大的场合或特别需要之处,一般PC中不会安装80387芯片。虽然现在的Intel 处理器中都内置了数学协处理器功能部件,从而现在的操作系统中已经无须包含协处理器仿真程序代码,但是因为80387仿真程序完全建立在模拟80387芯片处理结构和分析指令代码结构基础上,因此学习本章内容后读者不仅能够了解80387协处理器编程方法,而且对编写汇编和反汇编处理

5、程序也有很大帮助。如果80386 PC中没有包括80387数学协处理器芯片,那么当CPU执行到一条协处理器指令时就会引发“设备不存在”异常中断7。该异常过程的处理代码在sys_call.s第158行开始处。如果操作系统在初始化时已经设置了CPU控制寄存器CR0的EM位,那么此时就会调用math_emulate.c程序中的math_emulate()函数来用软件“解释”执行每一条协处理器指令。Linux 0.12内核中的数学协处理器仿真程序math_emulate.c完全模拟了80387芯片执行协处理器指令的方式。在处理一条协处理器指令之前,该程序会首先使用数据结构等类型在内存中建立起一个“软”

6、80387环境,包括模仿所有80387内部栈式累加器组ST、控制字寄存器CWD、状态字寄存器SWD和特征字TWD(TAG word)寄存器,然后分析引起异常的当前协处理器指令操作码,并根据具体操作码执行相应的数学模拟运算。因此在描述math_emulate.c程序的处理过程之前,有必要先介绍一下80387的内部结构和基本工作原理。11.1.1 浮点数据类型本节主要介绍协处理器使用的浮点数据类型。首先简单回顾一下整型数的几种表示方式,然后说明浮点数的几种标准表示方式以及在80387中运算时使用的临时实数表示方法。1整型数据类型对于Intel 32位CPU来讲,有三种基本无符号数据类型:字节(by

7、te)、字(word)和双字(double word),分别有8、16和32位。无符号数的表示方式很简单,字节中的每个位都代表一个二进制数,并且根据其所处位置具有不同的权值。例如一个无符号二进制数0b10001011可表示为:U = 0b10001011 = 127 + 026 + 025 + 024 + 123 + 022 + 121 + 120 = 139它对应十进制数139。其中权值最小的一位(20)通常被称为最低有效位(LSB,Least Significant Bit),而权值最大的位(27)被称为最高有效位(MSB,Most Significant Bit)。而计算机中具有负数值的

8、整型数据表示方法通常也有三种:2的补码(Twos complement)、符号数(Sign magnitude)和偏置数(biased number)表示方式。表11-2给出了这三种形式表示的一些数值。表11-2 整型数的几种表示形式十进制数2的补码表示法偏置表示法(127)符号数表示法128无法表示0b11111111无法表示1270b011111110b111111100b011111111260b011111100b111111010b0111111020b000000100b100000010b0000001010b000000010b100000000b0000000100b0000

9、00000b011111110b00000000-0无法表示无法表示0b10000000-10b111111110b011111100b10000001-20b111111100b011111010b10000010-1260b100000100b000000010b11111110-1270b100000010b000000000b11111111-1280b10000000无法表示无法表示2的补码(二进制补码)表示法是目前大多数计算机CPU使用的整数表示方法,因为CPU的无符号数的简单加法也适用于这种格式的数据运算。使用这种表示法,一个数的负数就是该数每位取反后再加1。MSB位就是该数的符

10、号位。MSB= 0表示一个正数;MSB = 1表示负数。80386 CPU具有8位(1字节)、16位(1字)和32位(双字)2的补码数据类型,分别可以表示的数据范围是:-128127、-3276832767、-21474836482146473647。另外,在80387仿真程序中使用了一种称为临时整数类型的格式,如图11-1所示。它的长度为10字节,可表示64位整型数据类型。其中低8字节最大可表示63位无符号数,而最高2字节仅使用了最高有效位来表示数值的正负。对于32位整型值则使用低4字节来表示,16位整型值则使用低2字节表示。数的偏置表示法通常用于表示浮点数格式中的指数字段值。把一个数加上指

11、定的偏置值就是该数的偏置数表示的值。从表11-1可以看出,这种表示方法的数值具有无符号数的大小顺序。因此这种表示方法易于比较数值大小。即大数值的偏置表示值总是无符号值的一个大数,而其他两种表示方式却并非如此。符号数表示法有一个位专门用于表示符号(0表示正数,1表示负数),而其他位则与无符号整数表示的数值相同。浮点数的有效数(尾数)部分使用的就是这种表示方法,而符号位代表整个浮点数的正负符号。图11-1 仿真程序支持的临时整数格式2BCD码数据类型BCD(Binary Coded Decimal)码数值是二进制编码的十进制数值,对于压缩的BCD编码,每个字节可表示两位十进制数,其中每4位表示一位

12、09的数。例如,十进制数59的压缩BCD码表示是0x01011001。对于非压缩的BCD码,每个字节只使用低4位表示1位十进制数。80387协处理器支持10字节压缩BCD码的表示和运算,可表示18位十进制数,如图11-2所示。与临时整数格式类似,其中最高字节仅使用了符号位(最高有效位)来表示数值的正负,其余位均不用。若BCD码数据是负数,则会使用最高地址处1字节的最高有效位置1来表示负值。否则最高字节所有位均是0。图11-2 80387支持的BCD码数据类型3浮点数据类型具有整数部分和小数(尾数)部分的数称为实数或浮点数。实际上整型数是小数部分为0的实数,是实数集的一个子集。由于计算机使用固定

13、长度位来表示一个数,因此并不能精确地表示所有实数。由于计算机表示实数时为了在固定长度位内能表示尽量精确的实数值,分配给表示小数部分的位个数并不是固定的,即小数点是可以“浮动”的,因此计算机表示的实数数据类型也称为浮点数。为了便于程序移植,目前计算机中都使用IEEE标准754指定的浮点数表示方式来表示实数。这种实数表示方式的一般格式如图11-3所示。它由有效数(Significant)部分、指数(Exponent)部分和符号位(Sign)组成。80387协处理器支持三种实数类型,它们每个部分使用的位数如图11-4所示。图11-3 浮点数一般格式图11-4 80387协处理器使用的实数格式其中S是

14、一个位的符号位。S=1表示是负实数;S=0表示是正实数。有效数(Significant)给出了实数数值的有效位数或尾数。当使用指数时,一个实数可以表示成多种形式。例如十进制数字10.34可以表示成1034.010-2、10.34100、1.034101或0.1034102等。为了使计算能够得到最大精度值,我们总是对实数进行规格化(Normalize)处理,即调整实数的指数值,使得二进制最高有效数值总是1,并且小数点就位于其右侧。因此,上述例子正确的规格化处理结果就是1.034101。对于二进制数来说就是1.XXXXX2N(其中X是1或0)。如果我们总是使用这种形式来表示一个实数,那么小数点左边

15、肯定是1。所以在80387的短实数(单精度)和长实数(双精度)格式中,这个“1”就没有必要明确地表示出来。因此在短实数或长实数的二进制有效数中,0x0111.010实际上就是0x1.0111.010。格式中的指数字段含有把一个数表示成规格化形式时所需要的2的幂次值。正如前面提到的,为了便于数字大小的比较,80387使用偏置数形式来存储指数值。短实数、长实数和临时实数的偏置基量分别是127、1023和16383。因此一个短实数指数值0b10000000实际表示21(0b01111111 + 0b00000001)。另外,临时实数是80387内部运算时表示数的格式。它的最高有效数1被明确地放置在位

16、63处,并且无论你给出的数是什么数据类型的(例如,整型数、短实数或BCD码数等),80387都会把它转换成临时实数格式。80387这样做的目的是为了使得精度最大化并且尽量减少运算过程中的溢出异常。显式地把1表示出来是因为80387在运算过程中确实需要该位(用于表示极小的数值)。当输入到80387中的短型或长型实数被转换成临时实数格式时,就会明确地在位63处放置一个1。4特殊实数与上面表中格式某些值无法表示的情况类似,使用实数格式表示的某些值也有其特殊含义。对于80位长度格式的临时实数,80387并没有使用其可表示的所有范围数值。表11-3是80387使用中的临时实数所能表示的所有可能的数值,其

17、中有效数一栏虚线左侧1位表示临时实数位63,即明确表示数值1的位。短实数和长实数没有此位,因此也没有表中的伪非规格化类别。下面说明其中的一些特殊值:零值、无穷值、非规格化值、伪非规格化值以及信号NaN(Not a Number)和安静NaN。表11-3 80387临时实数所能表示的数值类型和范围负号偏置型指数有效数类别0/111.11111.11安静NaNs QNaNs(Quiet NaNs)0/111.111.0/111.11110.00不确定值(Indefinite)0/111.11101.11信号NaNs SNaNs(Signalling NaNs)0/111.111.0/111.111

18、00.010/111.11100.00无穷数(Infinite)0/111.10111.11规格化数(正常数)(Normals)0/1.1.0/100.01100.000/100.00111.11伪非规格化数(Pseudo-Denormals)0/100.001.0/100.00100.000/100.00011.11非规格化数(Denormals)0/100.000.0/100.00000.010/100.00000.00零(Zero)零是指数和有效数均为0的值,其余指数为0的值作保留,即指数是0的值不能表示一个正常实数值。无穷值是指数值为全1、有效数值为全零的值,而且指数值为0x11.11

19、的所有其余值也作保留使用。非规格化数(Denormals)是一种用于表示非常小数值的特殊类值。它可以表示渐进下溢或渐进精度丢失情况。通常要求数值表示成规格化数(左移直到有效数的最高有效位是位1)。然而非规格化数的有效数最高有效位不是1。此时偏置型指数0x00.00分别是值为2-126、2-1022、2-16382的短实数、长实数和临时实数指数值的特殊表示方式。这种表示比较特殊,因为偏置型指数0x00.01对三种实数类型也分别表示相同的指数值2-126、2-1022、2-16382。伪非规格化类数值(Pseudo-denormals)是有效数最高有效位为1的值,而非规格化类数值的该位是0。伪非规

20、格化数很少见,它们可以用规格化类数来表示却没有这么做。因为上面已经说明特殊的偏置指数0x00.00与规格化数的指数0x00.01具有相同的值。因此伪非规格化类数可以表示成规格化类数值。另一种特殊情况是NaN。NaN是指“不是一个数”(Not a Number)。NaN有两种形式:会产生信号(Signaling)的和不会产生信号的或称为安静的(Quiet)。当一个产生信号的NaN(SNaN)被用于操作时就会引发一个无效操作异常,而一个安静的NaN(QNaN)则不会。SnaN是一类会引发无效操作异常的数值。使用的方法就是程序先把变量都初始化为SNaN值,在实际使用这个变量时还需要对其进行真正的赋值

21、。这样若操作过程中使用了一个未被初始化的值就会引发异常。当然,NaN类数值也可以用来存储其他信息。80387自身不会产生SNaN类的值,但会产生QNaN类的值。当发生无效操作异常时80387就会产生一个QNaN类值,并且操作的结果将是不确定值(Indefinite)。不确定值是一种特殊的QNaN类值。每种数据类型都有一个表示不确定值的数。对于整型数则是用其最大负数来表示其不确定值。另外还有一些80387不支持的临时实数值,即那些没有在上表中列出的数值范围。若80387遇到这些数值,就会引发无效操作异常。11.1.2 数学协处理器功能和结构80386虽然是一个通用微处理器,但其指令并不是非常适用

22、于数学计算。因此若使用80386来执行数学计算,那么就需要编制非常复杂的程序,而且执行效率也相对较低。80387作为80386的辅助处理芯片,极大地扩展了程序员的编程范围。以前程序员不太可能做到的事,使用协处理器后就可以很容易地,并且快速而精确地完成。80387具有一组特别的寄存器。这组寄存器可以让80387直接操作比80386所能处理的大或小几个数量级的数值。80386使用2进制补数方式表示一个数。这种方法不适合用来表示小数。而80387并不使用2的补数方法来表示数值,它使用了IEEE标准754规定的80位(10个字节)格式。这种格式不仅具有广泛的兼容性,而且能够使用二进制表示极大(或极小)

23、的数值。例如,它能表示大到1.21104932数值,也能处理小到3.310-4932的数。80387并不保持固定小数点的位置,如果数值小的话就多使用一些小数位,如果数值大的话就少用几位小数位。因此小数点的位置是可以“浮动”的。这也是术语“浮点”数的由来。为支持浮点运算,80387中包含三组寄存器,如图11-5所示。 8个80位长的数据寄存器(累加器),可用于临时存放8个浮点操作数,并且这些累加器可以执行栈式操作; 3个16位状态和控制寄存器:一个状态字寄存器SWD、一个控制字寄存器CWD和一个特征(TAG)寄存器; 4个32位出错指针寄存器(FIP、FCS、FOO和FOS)用于确定导致8038

24、7内部异常的指令和内存操作数。图11-5 80387的寄存器1栈式浮点累加器在浮点指令执行过程中,8个80位长度的物理寄存器组被作为栈式累加器使用。虽然每个80位寄存器有固定的物理顺序位置(即左边的07),但当前栈顶则由ST(即ST(0))来指明。ST之下的其余累加器使用名称ST(i)来指明(i = 17)。至于哪个80位物理寄存器是当前栈顶ST,则由具体操作过程指定。在状态字寄存器中名称为TOP的3位字段含有当前栈顶ST对应的80位物理寄存器的绝对位置。一个入栈(Push)操作将会把TOP字段值递减1,并把新值存储于新的ST中。在入栈操作之后,原来的ST变成了ST(1),而原来的ST(7)变

25、成了现在的ST。即所有累加器的名称都从原来的ST(i)变成了ST(i+1)&0x7)。一个出栈(Pop)操作将会读出当前ST对应的80位寄存器的值,并且把TOP字段值递增1。因此在出栈操作之后,原来的ST(即ST(0))变成了ST(7),原来的ST(1)成为新的ST。即所有累加器的名称都从原来的ST(i)变成ST(i-1)& 0x7)。ST的作用如同一个累加器是因为它被作为所有浮点指令的一个隐含操作数。若有另一个操作数,那么该第2个操作数可以是任何其余累加器之一ST(i),或者是一个内存操作数。栈中的每个累加器为一个实数提供了使用临时实数格式存储的80位空间,其最高位(s)是符号位,位7864

26、是15位的指数字段,位630是64位的有效数字段。浮点指令被设计成能充分利用这个累加器栈模式。浮点加载指令(FLD等)会从内存中读取一个操作数并压入栈中,而浮点存储指令则会从当前栈顶取得一个值并写到内存中。若栈中该值不再需要时还可以同时执行出栈操作。加和乘之类的操作会把当前ST寄存器内容作为一个操作数,而另一个取自其他寄存器或内存中,并且在计算完后即把结果保存在ST中。还有一类“操作并弹出”操作形式用于在ST和ST(1)两者之间进行运算。这种操作形式会执行一次弹出操作,然后把结果放入新的ST中。2状态与控制寄存器三个16位的寄存器(TAG字、控制字和状态字)控制着浮点指令的操作并且为其提供状态

27、信息。它们的具体格式如图11-6所示。下面逐一对它们进行说明。(1)控制字控制字(Control Word)可用于程序设置各种处理选项来控制80387的操作。其中可分为三个部分。位1110的RC(Rounding Control)是舍入控制字段,用于对计算结果进行舍入操作。位98的PC(Precision Control)是精度控制字段,用于在保存到指定存储单元之前对计算结果进行精度调整。所有其他操作使用临时实数格式精度,或者使用指令指定的精度。位50是异常屏蔽位,用于控制协处理器异常处理。这6位对应80387可能发生的6种异常情况。其中每一种异常都可以单独屏蔽掉。如果发生某个特定异常并且其对

28、应屏蔽位没有置位,那么80387就会向CPU通报这个异常,并且会让CPU产生异常中断int 16。然而如果设置了对应屏蔽位,那么80387就会自己处理并纠正发生的异常问题而不会通知CPU。这个寄存器随时可以读写,其中各位的具体含义参见图11-6。(2)状态字在运行期间,80387会设置状态字(Status Word)中的位,用于程序检测特定的条件。当发生异常时,它可让CPU确定发生异常的原因。因为所有6个协处理器异常都会让CPU产生异常中断int16。(3)特征字特征字(Tag Word)寄存器含有8个2位的Tag字段,分别对应8个物理浮点数据寄存器。这些特征字段分别指明相应的物理寄存器含有有

29、效、零、特殊浮点数值,或者是空的。特殊数值是指那些无限值、非数值、非规格化或不支持格式的数值。特征字段Tag可用于检测累加器堆栈上下溢出情况。如果入栈(Push)操作递减TOP指向了一个非空寄存器,就会发生栈上溢出。如果出栈(Pop)操作企图去读取或弹出空寄存器,就会造成栈下溢出(Underflow)。栈的上下溢出都将引发无效操作异常。图11-6 控制和状态寄存器格式3出错指针寄存器出错指针寄存器(Error-Pointer Register)是4个32位的80387寄存器,其中含有80387最后执行指令和所用数据的指针,参见图11-6。前两个寄存器FIP和FCS中是最后执行指令中2个操作码的

30、指针(忽略前缀码)。FCS是段选择符和操作码,FIP是段内偏移值。后两个寄存器FOO和FOS是最后执行指令内存操作数的指针。FOS中是段选择符,FOO中是段内偏移值。如果最后执行的协处理器指令不含内存操作数,则后两个寄存器值无用。指令FLDENV、FSTENV、FNSTENV、FRSTOR、FSAVE和FNSAVE用于加载和保存这4个寄存器的内容。前3条指令共加载或保存28字节内容:控制字、状态字和特征字以及4个出错指针寄存器。控制字、状态字和特征字都以32位操作,高16位为0。后3条指令用于加载或保存协处理器所有108字节的寄存器内容。4浮点指令格式对协处理器进行仿真就是解析具体的浮点指令操

31、作码和操作数,根据每一条指令的结构使用80386的普通指令来执行相应的仿真操作。数学协处理器80387共有七十多条指令,共分5类,见表11-4。每条指令的操作码都有2个字节,其中第一个字节高5位都是二进制11011。这5位的数值(0x1b或十进制27)正好是字符ESC(转义)的ASCII代码值,因此所有数学协处理器指令都被形象地称为ESC转义指令。在仿真浮点指令时可忽略相同的ESC位,只要判断低11位的值即可。表11-4 浮点指令类型第1字节第2字节可选字段11 1 0 1 1OPA1MOD1OPBR/MSIBDISP21 1 0 1 1MFOPAMODOPBR/MSIBDISP31 1 0

32、1 1dPOPA11OPBST(i)41 1 0 1 1001111OP51 1 0 1 1011111OP151110987654 3 2 1 0表中各个字段的含义如下(有关这些字段的具体含义和详细说明请参考80x86处理器手册):1)OP(Operation opcode)是指令操作码,在有些指令中它被分成了OPA和OPB两部分。2)MF(Memory Format)是内存格式。00:32位实数;01:32位整数;10:64位实数;11:64位整数。3)P(Pop)指明在操作后是否要执行一次出栈处理。0:不需要;1:操作后弹出栈。4)d(destination)指明保存操作结果的累加器。0

33、:ST(0);1:ST(i)。5)MOD(Mode)和R/M(Register/Memory)是操作方式字段和操作数位置字段。6)SIB(Scale Index Base)和DISP(Displacement)是具有MOD和R/M字段指令的可选后续字段。另外,所有浮点指令的汇编语言助记符都以字母F开头,例如:FADD、FLD等。还有如下一些标准表示方法:1)FI所有操作整型数据的指令都以FI开头,例如FIADD、FILD等。2)FB所有操作BCD类型数据的指令都以FB开头,例如FBLD、FBST等。3)FxxP所有会执行一次出栈操作的指令均以字母P结尾,例如FSTP、FADDP等。4)FxxP

34、P所有会执行二次出栈操作的指令均以字母PP结尾,例如FCOMPP、FUCOMPP等。5)FNxx除了以FN开头的指令,所有指令在执行前都会先检测未屏蔽的运算异常。而以FN开头的指令不检测运算异常情况,例如FNINIT、FNSAVE等。11.2 math_emulate.c程序11.2.1 功能描述math_emulate.c程序中的所有函数可分为3部分:第一类是设备不存在异常处理程序接口函数math_emulate(),只有这一个函数;第二类是浮点指令仿真处理主函数do_emu(),也只有一个函数;另外所有函数都是仿真运算辅助类函数,包括其余几个C语言程序中的函数。在一台不包含80387协处理

35、器芯片的PC中,如果内核初始化时在CR0中设置了仿真标志EM = 1,那么当CPU遇到一条浮点指令时就会引起CPU产生异常中断int 7,并且在该中断处理过程中调用本程序中第476行处的math_emulate(long _false)函数。在math_emulate()函数中,若判断出当前进程还没有使用过仿真的协处理运算时就会对仿真的80387控制字、状态字和特征字(Tag Word)进行初始化操作,设置控制字中所有6种协处理器异常屏蔽位并复位状态字和特征字。然后调用仿真处理主函数do_emu()。使用的参数是作为如下info结构的中断处理过程中调用math_emulate()函数的返回地址

36、指针。info结构实际上就是栈中自从CPU产生中断int7后逐渐入栈的一些数据构成的一个结构,因此它与系统调用时内核栈中数据的分布情况基本相同。参见include/linux/math_emu.h文件第11行和kernel/sys_call.s开始部分。 11 struct info 12 long _math_ret; / math_emulate()调用者(int7)返回地址。 13 long _orig_eip; / 临时保存原EIP的地方。 14 long _edi; / 异常中断int7处理过程入栈的寄存器。 15 long _esi; 16 long _ebp; 17 long _

37、sys_call_ret; / 中断7返回时将去执行系统调用的返回处理代码。 18 long _eax; / 以下部分(18-30行)与系统调用时栈中结构相同。 19 long _ebx; 20 long _ecx; 21 long _edx; 22 long _orig_eax; / 如不是系统调用而是其他中断时,该值为-1。 23 long _fs; 24 long _es; 25 long _ds; 26 long _eip; / 26 - 30行 由CPU自动入栈。 27 long _cs; 28 long _eflags; 29 long _esp; 30 long _ss; 31

38、;do_emu()函数(第52行)首先根据状态字来判断有没有发生仿真的协处理器内部异常。若有则设置状态字的忙位B(位15),否则就复位忙位B。然后从上述info结构中EIP字段处取得产生协处理器异常的二字节浮点指令代码code,并在屏蔽掉每条浮点指令码中都相同的ESC码(二进制11011)位部分后,根据此时的code值对具体的浮点指令进行软件仿真运算处理。为便于处理,该函数按5种类型浮点指令码分别使用了五个switch语句进行处理。例如,第一个switch语句(第75行)用于处理那些不涉及寻址内存操作数的浮点指令。而最后两个switch语句(第419、432行)则专门用来处理操作数与内存相关的

39、指令。对于后一种类型的指令,其处理过程的基本流程是首先根据指令代码中的寻址模式字节取得内存操作数的有效地址,然后从该有效地址处读取相应的数据(整型数、实数或BCD码数值)。接着把读取的值转换成80387内部处理使用的临时实数格式。在计算完毕后,再把临时实数格式的数值转换为原数据类型,最后保存到用户数据区中。另外,在具体仿真一条浮点指令时,若发现浮点指令无效,则程序会立刻调用放弃执行函数_math_abort()。该函数会向当前执行进程发送指定的信号,同时修改栈指针esp指向中断过程中调用math_emulate()函数的返回地址(_math_ret),并立刻返回到中断处理过程中去。11.2.2

40、 代码注释程序11-1 linux/kernel/math/math_emulate.c 1 /* 2 * linux/kernel/math/math_emulate.c 3 * 4 * (C) 1991 Linus Torvalds 5 */ 6 7 /* 8 * Limited emulation 27.12.91 - mostly loads/stores, which gcc wants 9 * even for soft-float, unless you use bruce evans patches. The patches 10 * are great, but they h

41、ave to be re-applied for every version, and the 11 * library is different for soft-float and 80387. So emulation is more 12 * practical, even though its slower. 13 * 14 * 28.12.91 - loads/stores work, even BCD. Ill have to start thinking 15 * about add/sub/mul/div. Urgel. I should find some good sou

42、rce, but Ill 16 * just fake up something. 17 * 18 * 30.12.91 - add/sub/mul/div/com seem to work mostly. I should really 19 * test every possible combination. 20 */ /* * 仿真范围有限的程序 91.12.27 - 绝大多数是一些加载/存储指令。除非你使用了Bruce Evans的 * 补丁程序,否则即使使用软件执行浮点运算,gcc也需要这些指令。Bruce的补丁程序非常好,但每 * 次更换gcc版本你都得用这个补丁程序。而且对于软

43、件浮点实现和80387,所使用的库是不同的。因 * 此使用仿真是更为实际的方法,尽管仿真方法更慢。 * * 91.12.28 - 加载/存储协处理器指令可以用了,即使是BCD码的也能使用。我将开始考虑实现add/ * sub/mul/div 指令。唉,我应该找一些好的资料,不过现在我会先仿造一些操作。 * * 91.12.30 - add/sub/mul/div/com 这些指令好像大多数都可以使用了。我真应该测试每种指令 * 可能的组合操作。 */ 21 22 /* 23 * This file is full of ugly macros etc: one problem was that

44、 gcc simply 24 * didnt want to make the structures as they should be: it has to try to 25 * align them. Sickening code, but at least Ive hidden the ugly things 26 * in this one file: the other files dont need to know about these things. 27 * 28 * The other files also dont care about ST(x) etc - they

45、 just get addresses 29 * to 80-bit temporary reals, and do with them as they please. I wanted to 30 * hide most of the 387-specific things here. 31 */ /* * 这个程序中到处都是些别扭的宏:问题之一是gcc就是不想把结构建立成其应该成为的样子:gcc 企 * 图对结构进行对齐处理。真是讨厌,不过我起码已经把所有蹩脚的代码都隐藏在这么一个文件中了: * 其他程序文件不需要了解这些信息。 * * 其他的程序也不需要知道ST(x)等80387内部结构

46、 - 它们只需要得到80位临时实数的地址就可以 * 随意操作。我想尽可能在这里隐藏所有387专有信息。 */ 32 33 #include / 信号头文件。定义信号符号,信号结构及信号操作函数原型。 34 35 #define _ALIGNED_TEMP_REAL 1 36 #include / 协处理器头文件。定义临时实数结构和387寄存器操作宏等。 37 #include / 内核头文件。含有一些内核常用函数的原形定义。 38 #include / 段操作头文件。定义了有关段寄存器操作的嵌入式汇编函数。 39 40 #define bswapw(x) _asm_(xchgb %al,%ah

47、:=a (x): (short)x) / 交换2字节位置。 41 #define ST(x) (*_st(x) / 取仿真的ST(x)累加器值。 42 #define PST(x) (const temp_real *) _st(x)/ 取仿真的ST(x)累加器的指针。 43 44 /* 45 * We dont want these inlined - it gets too messy in the machine-code. 46 */ /* * 我们不想让这些成为嵌入的语句 - 因为这会使得到的机器码太混乱。 */ / 以下这些是相同名称浮点指令的仿真函数。 47 static voi

48、d fpop(void); 48 static void fpush(void); 49 static void fxchg(temp_real_unaligned * a, temp_real_unaligned * b); 50 static temp_real_unaligned * _st(int i); 51 / 执行浮点指令仿真。 / 该函数首先检测仿真的I387结构状态字寄存器中是否有未屏蔽的异常标志置位。若有则对状态字中 / 忙标志B进行设置。然后把指令指针保存起来,并取出代码指针EIP处的2字节浮点指令代码code。 / 接着分析代码code,并根据其含义进行处理。针对不同代

49、码类型值,Linus使用了几个不同的 / switch程序块进行仿真处理。 / 参数是info结构的指针。 52 static void do_emu(struct info * info) 53 54 unsigned short code; 55 temp_real tmp; 56 char * address; 57 / 该函数首先检测仿真的I387结构状态字寄存器中是否有未屏蔽的异常标志置位。若有就设置状态字 / 中的忙标志B(位15),否则复位B标志。然后我们把指令指针保存起来。再看看执行本函数的代 / 码是不是用户代码。如果不是,即调用者的代码段选择符不等于 0x0f,则说明内核中

50、有代码使用了 / 浮点指令。于是在显示出浮点指令出的CS、EIP值和信息“内核中需要数学仿真”后停机。 58 if (I387.cwd & I387.swd & 0x3f) 59 I387.swd |= 0x8000; / 设置忙标志B。 60 else 61 I387.swd &= 0x7fff; / 清忙标志B。 62 ORIG_EIP = EIP; / 保存浮点指令指针。 63 /* 0x0007 means user code space */ 64 if (CS != 0x000F) / 不是用户代码则停机。 65 printk(math_emulate: %04x:%08xnr,CS,EIP); 66 panic(Math emulation needed in kernel); 67 / 然后我们取出代码指针EIP处的2字节浮点指令代码code。由于Intel CPU存储数据时是“小头” / (Little

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论