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