在计算机上执行计算量较大的运算通常可以使用三种方法来完成。一种是直接使用CPU普通指令执行计算。由于CPU指令是一类通用指令,因此使用这些指令进行复杂和大量的运算工作需要编制复杂的计算子程序

内核目录kernel/math目录中包括数学协处理器仿真处理代码文件,共包括9个C言语程序,见表11-1。本章内容与具体硬件结构联系十分亲近,因而需求读者具有较深的有关Intel CPU和协处理器指令代码结构的常识。但好在这些内容与内核完结联系不大,因而越过本章内容并不会阻碍读者对内核完结办法的完好了解。不过若能了解本章内容,那么关于完结体系级应用程序(例如汇编和反汇编等程序)和编制协处理器浮点处理程序将有很大协助。  

Linux内核彻底分析---数学协处理器(linux网络内核分析与开发pdf)  Linux 内核 第1张

11.1 整体功用描绘

在核算机上履行核算量较大的运算一般能够运用三种办法来完结。一种是直接运用CPU一般指令履行核算。由于CPU指令是一类通用指令,因而运用这些指令进行杂乱和很多的运算作业需求编制杂乱的核算子程序,而且一般只需知晓数学和核算机的专业人员才干编制出这些子程序。另一种办法是为CPU装备一个数学协处理器芯片。运用协处理器芯片能够极大地简化数学处理编程难度,而且运算速度和功率也会成倍进步,但需求别的添加硬件投入。还有一种办法是在体系内核级运用仿真程序来仿照协处理器的运算功用。这种办法或许是运算速度和功率最低的一种,但与运用了协处理器相同能够便利程序员编制核算程序,而且能够在对程序不加任何改动的状况下把所编程序运行在具有协处理器的机器上。

在Linux 0.1x乃至Linux 0.9x内核开发初期,数学协处理器芯片80387(或其兼容芯片)价格不菲,而且一直是一般PC中的奢侈品。因而除非在科学核算量很大的场合或特别需求之处,一般PC中不会装置80387芯片。尽管现在的Intel 处理器中都内置了数学协处理器功用部件,然后现在的操作体系中现已无须包括协处理器仿真程序代码,可是由于80387仿真程序彻底建立在仿照80387芯片处理结构和分析指令代码结构基础上,因而学习本章内容后读者不只能够了解80387协处理器编程办法,而且对编写汇编和反汇编处理程序也有很大协助。

假如80386 PC中没有包括80387数学协处理器芯片,那么当CPU履行到一条协处理器指令时就会引发“设备不存在”反常中断7。该反常进程的处理代码在sys_call.s第158行开端处。假如操作体系在初始化时现已设置了CPU操控寄存器CR0的EM位,那么此刻就会调用math_emulate.c程序中的math_emulate()函数来用软件“解说”履行每一条协处理器指令。

Linux 0.12内核中的数学协处理器仿真程序math_emulate.c彻底仿照了80387芯片履行协处理器指令的办法。在处理一条协处理器指令之前,该程序会首要运用数据结构等类型在内存中建立起一个“软”80387环境,包括仿照一切80387内部栈式累加器组ST[]、操控字寄存器CWD、状况字寄存器SWD和特征字TWD(TAG word)寄存器,然后分析引起反常的当时协处理器指令操作码,并依据具体操作码履行相应的数学仿照运算。因而在描绘math_emulate.c程序的处理进程之前,有必要先介绍一下80387的内部结构和根本作业原理。

11.1.1 浮点数据类型

本节首要介绍协处理器运用的浮点数据类型。首要简略回忆一下整型数的几种表明办法,然后阐明浮点数的几种规范表明办法以及在80387中运算时运用的暂时实数表明办法。

1.整型数据类型

关于Intel 32位CPU来讲,有三种根本无符号数据类型:字节(byte)、字(word)和双字(double word),别离有8、16和32位。无符号数的表明办法很简略,字节中的每个位都代表一个二进制数,而且依据其所在方位具有不同的权值。例如一个无符号二进制数0b10001011可表明为:

U = 0b10001011 = 1×27 + 0×26 + 0×25 + 0×24 + 1×23 + 0×22 + 1×21 + 1×20 = 139

它对应十进制数139。其间权值最小的一位(20)一般被称为最低有用位(LSB,Least Significant Bit),而权值最大的位(27)被称为最高有用位(MSB,Most Significant Bit)。

#p#而核算机中具有负数值的整型数据表明办法一般也有三种:2的补码(Two’s complement)、符号数(Sign magnitude)和偏置数(biased number)表明办法。表11-2给出了这三种办法表明的一些数值。

  

Linux内核彻底分析---数学协处理器(linux网络内核分析与开发pdf)  Linux 内核 第2张

2的补码(二进制补码)表明法是现在大多数核算机CPU运用的整数表明办法,由于CPU的无符号数的简略加法也适用于这种格局的数据运算。运用这种表明法,一个数的负数便是该数每位取反后再加1。MSB位便是该数的符号位。MSB= 0表明一个正数;MSB = 1表明负数。80386 CPU具有8位(1字节)、16位(1字)和32位(双字)2的补码数据类型,别离能够表明的数据规模是:-128~127、-32768~32767、-2147483648~2146473647。别的,在80387仿真程序中运用了一种称为暂时整数类型的格局,如图11-1所示。它的长度为10字节,可表明64位整型数据类型。其间低8字节最大可表明63位无符号数,而最高2字节仅运用了最高有用位来表明数值的正负。关于32位整型值则运用低4字节来表明,16位整型值则运用低2字节表明。

数的偏置表明法一般用于表明浮点数格局中的指数字段值。把一个数加上指定的偏置值便是该数的偏置数表明的值。从表11-1能够看出,这种表明办法的数值具有无符号数的巨细次序。因而这种表明办法易于比较数值巨细。即大数值的偏置表明值总是无符号值的一个大数,而其他两种表明办法却并非如此。

符号数表明法有一个位专门用于表明符号(0表明正数,1表明负数),而其他位则与无符号整数表明的数值相同。浮点数的有用数(尾数)部分运用的便是这种表明办法,而符号位代表整个浮点数的正负符号。


Linux内核彻底分析---数学协处理器(linux网络内核分析与开发pdf)  Linux 内核 第3张

2.BCD码数据类型

BCD(Binary Coded Decimal)码数值是二进制编码的十进制数值,关于紧缩的BCD编码,每个字节可表明两位十进制数,其间每4位表明一位0~9的数。例如,十进制数59的紧缩BCD码表明是0x01011001。关于非紧缩的BCD码,每个字节只运用低4位表明1位十进制数。

80387协处理器支撑10字节紧缩BCD码的表明和运算,可表明18位十进制数,如图11-2所示。与暂时整数格局相似,其间最高字节仅运用了符号位(最高有用位)来表明数值的正负,其他位均不必。若BCD码数据是负数,则会运用最高地址处1字节的最高有用方位1来表明负值。不然最高字节一切位均是0。

  

Linux内核彻底分析---数学协处理器(linux网络内核分析与开发pdf)  Linux 内核 第4张



#p#3.浮点数据类型

具有整数部分和小数(尾数)部分的数称为实数或浮点数。实践上整型数是小数部分为0的实数,是实数集的一个子集。由于核算机运用固定长度位来表明一个数,因而并不能准确地表明一切实数。由于核算机表明实数时为了在固定长度位内能表明尽量准确的实数值,分配给表明小数部分的位个数并不是固定的,即小数点是能够“起浮”的,因而核算机表明的实数数据类型也称为浮点数。为了便于程序移植,现在核算机中都运用IEEE规范754指定的浮点数表明办法来表明实数。

这种实数表明办法的一般格局如图 11-3 所示。它由有用数(Significant)部分、指数(Exponent)部分和符号位(Sign)组成。80387协处理器支撑三种实数类型,它们每个部分运用的位数如图11-4所示。

  

Linux内核彻底分析---数学协处理器(linux网络内核分析与开发pdf)  Linux 内核 第5张

  

Linux内核彻底分析---数学协处理器(linux网络内核分析与开发pdf)  Linux 内核 第6张

其间S是一个位的符号位。S=1表明是负实数;S=0表明是正实数。有用数(Significant)给出了实数数值的有用位数或尾数。当运用指数时,一个实数能够表明成多种办法。例如十进制数字10.34能够表明成1034.0×10-2、10.34×100、1.034×101或0.1034×102等。为了使核算能够得到最大精度值,咱们总是对实数进行规格化(Normalize)处理,即调整实数的指数值,使得二进制最高有用数值总是1,而且小数点就坐落其右侧。因而,上述比如正确的规格化处理成果便是1.034×101。关于二进制数来说便是1.XXXXX×2N(其间X是1或0)。假如咱们总是运用这种办法来表明一个实数,那么小数点左面肯定是1。所以在80387的短实数(单精度)和长实数(双精度)格局中,这个“1”就没有必要清晰地表明出来。因而在短实数或长实数的二进制有用数中,0x0111...010实践上便是0x1.0111...010。

格局中的指数字段含有把一个数表明成规格化办法时所需求的2的幂次值。正如前面说到的,为了便于数字巨细的比较,80387运用偏置数办法来存储指数值。短实数、长实数和暂时实数的偏置基量别离是127、1023和16383。因而一个短实数指数值0b10000000实践表明21(0b01111111 + 0b00000001)。

别的,暂时实数是80387内部运算时表明数的格局。它的最高有用数1被清晰地放置在位63处,而且不管你给出的数是什么数据类型的(例如,整型数、短实数或BCD码数等),80387都会把它转换成暂时实数格局。80387这样做的意图是为了使得精度最大化而且尽量削减运算进程中的溢出反常。显式地把1表明出来是由于80387在运算进程中的确需求该位(用于表明极小的数值)。当输入到80387中的短型或长型实数被转换成暂时实数格局时,就会清晰地在位63处放置一个1。

4.特别实数

与上面表中格局某些值无法表明的状况相似,运用实数格局表明的某些值也有其特别意义。关于80位长度格局的暂时实数,80387并没有运用其可表明的一切规模数值。表11-3是80387运用中的暂时实数所能表明的一切或许的数值,其间有用数一栏虚线左面1位表明暂时实数位63,即清晰表明数值1的位。短实数和长实数没有此位,因而也没有表中的伪非规格化类别。下面阐明其间的一些特别值:零值、无量值、非规格化值、伪非规格化值以及信号NaN(Not a Number)和安静NaN。

  

Linux内核彻底分析---数学协处理器(linux网络内核分析与开发pdf)  Linux 内核 第7张

零是指数和有用数均为0的值,其他指数为0的值作保存,即指数是0的值不能表明一个正常实数值。无量值是指数值为全1、有用数值为全零的值,而且指数值为0x11...11的一切其他值也作保存运用。

#p#非规格化数(Denormals)是一种用于表明十分小数值的特别类值。它能够表明渐进下溢或渐进精度丢掉状况。一般要求数值表明成规格化数(左移直到有用数的最高有用位是位1)。但是非规格化数的有用数最高有用位不是1。此刻偏置型指数0x00...00别离是值为2-126、2-1022、2-16382的短实数、长实数和暂时实数指数值的特别表明办法。这种表明比较特别,由于偏置型指数0x00...01对三种实数类型也别离表明相同的指数值2-126、2-1022、2-16382。

伪非规格化类数值(Pseudo-denormals)是有用数最高有用位为1的值,而非规格化类数值的该位是0。伪非规格化数很少见,它们能够用规格化类数来表明却没有这么做。由于上面现已阐明特别的偏置指数0x00...00与规格化数的指数0x00...01具有相同的值。因而伪非规格化类数能够表明成规格化类数值。

另一种特别状况是NaN。NaN是指“不是一个数”(Not a Number)。NaN有两种办法:会产生信号(Signaling)的和不会产生信号的或称为安静的(Quiet)。当一个产生信号的NaN(SNaN)被用于操作时就会引发一个无效操作反常,而一个安静的NaN(QNaN)则不会。SnaN是一类会引发无效操作反常的数值。运用的办法便是程序先把变量都初始化为SNaN值,在实践运用这个变量时还需求对其进行真实的赋值。这样若操作进程中运用了一个未被初始化的值就会引发反常。当然,NaN类数值也能够用来存储其他信息。

80387本身不会产生SNaN类的值,但会产生QNaN类的值。当产生无效操作反常时80387就会产生一个QNaN类值,而且操作的成果将是不确认值(Indefinite)。不确认值是一种特别的QNaN类值。每种数据类型都有一个表明不确认值的数。关于整型数则是用其最大负数来表明其不确认值。

别的还有一些80387不支撑的暂时实数值,即那些没有在上表中列出的数值规模。若80387遇到这些数值,就会引发无效操作反常。

11.1.2 数学协处理器功用和结构

80386尽管是一个通用微处理器,但其指令并不是十分适用于数学核算。因而若运用80386来履行数学核算,那么就需求编制十分杂乱的程序,而且履行功率也相对较低。80387作为80386的辅佐处理芯片,极大地扩展了程序员的编程规模。曾经程序员不太或许做到的事,运用协处理器后就能够很容易地,而且快速而准确地完结。

80387具有一组特别的寄存器。这组寄存器能够让80387直接操作比80386所能处理的大或小几个数量级的数值。80386运用2进制补数办法表明一个数。这种办法不适合用来表明小数。而80387并不运用2的补数办法来表明数值,它运用了IEEE规范754规则的80位(10个字节)格局。这种格局不只具有广泛的兼容性,而且能够运用二进制表明极大(或极小)的数值。例如,它能表明大到1.21×104932数值,也能处理小到3.3×10-4932的数。80387并不坚持固定小数点的方位,假如数值小的话就多运用一些小数位,假如数值大的话就少用几位小数位。因而小数点的方位是能够“起浮”的。这也是术语“浮点”数的由来。

为支撑浮点运算,80387中包括三组寄存器,如图11-5所示。① 8个80位长的数据寄存器(累加器),可用于暂时寄存8个浮点操作数,而且这些累加器能够履行栈式操作;② 3个16位状况和操控寄存器:一个状况字寄存器SWD、一个操控字寄存器CWD和一个特征(TAG)寄存器;③ 4个32位犯错指针寄存器(FIP、FCS、FOO和FOS)用于确认导致80387内部反常的指令和内存操作数。

  

Linux内核彻底分析---数学协处理器(linux网络内核分析与开发pdf)  Linux 内核 第8张



#p#

1.栈式浮点累加器

在浮点指令履行进程中,8个80位长度的物理寄存器组被作为栈式累加器运用。尽管每个80位寄存器有固定的物理次序方位(即左面的0~7),但当时栈顶则由ST(即ST(0))来指明。ST之下的其他累加器运用称号ST(i)来指明(i = 1~7)。至于哪个80位物理寄存器是当时栈顶ST,则由具体操作进程指定。在状况字寄存器中称号为TOP的3位字段含有当时栈顶ST对应的80位物理寄存器的肯定方位。一个入栈(Push)操作将会把TOP字段值递减1,并把新值存储于新的ST中。在入栈操作之后,本来的ST变成了ST(1),而本来的ST(7)变成了现在的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)是符号位,位78~64是15位的指数字段,位63~0是64位的有用数字段。

浮点指令被规划成能充分利用这个累加器栈办法。浮点加载指令(FLD等)会从内存中读取一个操作数并压入栈中,而浮点存储指令则会从当时栈顶获得一个值并写到内存中。若栈中该值不再需求时还能够一起履行出栈操作。加和乘之类的操作会把当时ST寄存器内容作为一个操作数,而另一个取自其他寄存器或内存中,而且在核算完后即把成果保存在ST中。还有一类“操作并弹出”操作办法用于在ST和ST(1)两者之间进行运算。这种操作办法会履行一次弹出操作,然后把成果放入新的ST中。

2.状况与操控寄存器

三个16位的寄存器(TAG字、操控字和状况字)操控着浮点指令的操作而且为其供给状况信息。它们的具体格局如图11-6所示。下面逐个对它们进行阐明。

(1)操控字

操控字(Control Word)可用于程序设置各种处理选项来操控80387的操作。其间可分为三个部分。位11~10的RC(Rounding Control)是舍入操控字段,用于对核算成果进行舍入操作。位9~8的PC(Precision Control)是精度操控字段,用于在保存到指定存储单元之前对核算成果进行精度调整。一切其他操作运用暂时实数格局精度,或许运用指令指定的精度。位5~0是反常屏蔽位,用于操控协处理器反常处理。这6位对应80387或许产生的6种反常状况。其间每一种反常都能够独自屏蔽掉。假如产生某个特定反常而且其对应屏蔽位没有置位,那么80387就会向CPU通报这个反常,而且会让CPU产生反常中断int 16。但是假如设置了对应屏蔽位,那么80387就会自己处理并纠正产生的反常问题而不会告诉CPU。这个寄存器随时能够读写,其间各位的具体意义参见图11-6。

(2)状况字

在运行期间,80387会设置状况字(Status Word)中的位,用于程序检测特定的条件。当产生反常时,它可让CPU确认产生反常的原因。由于一切6个协处理器反常都会让CPU产生反常中断int16。

(3)特征字

特征字(Tag Word)寄存器含有8个2位的Tag字段,别离对应8个物理浮点数据寄存器。这些特征字段别离指明相应的物理寄存器含有有用、零、特别浮点数值,或许是空的。特别数值是指那些无限值、非数值、非规格化或不支撑格局的数值。特征字段Tag可用于检测累加器仓库上下溢出状况。假如入栈(Push)操作递减TOP指向了一个非空寄存器,就会产生栈上溢出。假如出栈(Pop)操作妄图去读取或弹出空寄存器,就会形成栈下溢出(Underflow)。栈的上下溢出都将引发无效操作反常。

  

Linux内核彻底分析---数学协处理器(linux网络内核分析与开发pdf)  Linux 内核 第9张



#p#3.犯错指针寄存器

犯错指针寄存器(Error-Pointer Register)是4个32位的80387寄存器,其间含有80387最终履行指令和所用数据的指针,参见图11-6。前两个寄存器FIP和FCS中是最终履行指令中2个操作码的指针(疏忽前缀码)。FCS是段选择符和操作码,FIP是段内偏移值。后两个寄存器FOO和FOS是最终履行指令内存操作数的指针。FOS中是段选择符,FOO中是段内偏移值。假如最终履行的协处理器指令不含内存操作数,则后两个寄存器值无用。指令FLDENV、FSTENV、FNSTENV、FRSTOR、FSAVE和FNSAVE用于加载和保存这4个寄存器的内容。前3条指令共加载或保存28字节内容:操控字、状况字和特征字以及4个犯错指针寄存器。操控字、状况字和特征字都以32位操作,高16位为0。后3条指令用于加载或保存协处理器一切108字节的寄存器内容。

4.浮点指令格局

对协处理器进行仿真便是解析具体的浮点指令操作码和操作数,依据每一条指令的结构运用80386的一般指令来履行相应的仿真操作。数学协处理器80387共有七十多条指令,共分5类,见表11-4。每条指令的操作码都有2个字节,其间第一个字节高5位都是二进制11011。这5位的数值(0x1b或十进制27)正好是字符ESC(转义)的ASCII代码值,因而一切数学协处理器指令都被形象地称为ESC转义指令。在仿真浮点指令时可疏忽相同的ESC位,只需判别低11位的值即可。

  

Linux内核彻底分析---数学协处理器(linux网络内核分析与开发pdf)  Linux 内核 第10张

表中各个字段的意义如下(有关这些字段的具体意义和具体阐明请参阅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: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)FxxPP 一切会履行二次出栈操作的指令均以字母PP结束,例如FCOMPP、FUCOMPP等。

5)FNxx 除了以FN最初的指令,一切指令在履行前都会先检测未屏蔽的运算反常。而以FN最初的指令不检测运算反常状况,例如FNINIT、FNSAVE等。

转载请说明出处
知优网 » Linux内核彻底分析---数学协处理器(linux网络内核分析与开发pdf)

发表评论

您需要后才能发表评论