Linux内核完全剖析---math_emulate.c程序


math_emulate.c程序中的所有函数可分为3部分:第一类是设备不存在异常处理程序接口函数math_emulate(),只有这一个函数;第二类是浮点指令仿真处理主函数do_emu(),也只有一个函数;另外所有函数都是仿真运算辅助类函数,包括其余几个C语言程序中的函数。
在一台不包含80387协处理器芯片的PC中,如果内核初始化时在CR0中设置了仿真标志EM = 1,那么当CPU遇到一条浮点指令时就会引起CPU产生异常中断int 7,并且在该中断处理过程中调用本程序中第476行处的math_emulate(long ___false)函数。

在math_emulate()函数中,若判断出当前进程还没有使用过仿真的协处理运算时就会对仿真的80387控制字、状态字和特征字(Tag Word)进行初始化操作,设置控制字中所有6种协处理器异常屏蔽位并复位状态字和特征字。然后调用仿真处理主函数do_emu()。使用的参数是作为如下info结构的中断处理过程中调用math_emulate()函数的返回地址指针。info结构实际上就是栈中自从CPU产生中断int7后逐渐入栈的一些数据构成的一个结构,因此它与系统调用时内核栈中数据的分布情况基本相同。参见include/linux/math_emu.h文件第 11 行和kernel/sys_call.s开始部分。

Linux内核完全剖析---math_emulate.c程序
do_emu()函数(第52行)首先根据状态字来判断有没有发生仿真的协处理器内部异常。若有则设置状态字的忙位B(位15),否则就复位忙位B。然后从上述info结构中EIP字段处取得产生协处理器异常的二字节浮点指令代码code,并在屏蔽掉每条浮点指令码中都相同的ESC码(二进制11011)位部分后,根据此时的code值对具体的浮点指令进行软件仿真运算处理。为便于处理,该函数按5种类型浮点指令码分别使用了五个switch语句进行处理。例如,第一个switch语句(第75行)用于处理那些不涉及寻址内存操作数的浮点指令。而最后两个switch语句(第419、432行)则专门用来处理操作数与内存相关的指令。对于后一种类型的指令,其处理过程的基本流程是首先根据指令代码中的寻址模式字节取得内存操作数的有效地址,然后从该有效地址处读取相应的数据(整型数、实数或BCD码数值)。接着把读取的值转换成80387内部处理使用的临时实数格式。在计算完毕后,再把临时实数格式的数值转换为原数据类型,最后保存到用户数据区中。

另外,在具体仿真一条浮点指令时,若发现浮点指令无效,则程序会立刻调用放弃执行函数__math_abort()。该函数会向当前执行进程发送指定的信号,同时修改栈指针esp指向中断过程中调用math_emulate()函数的返回地址(___math_ret),并立刻返回到中断处理过程中去。

相关内容