Oracle® Solaris Studio 12.4:数值计算指南

退出打印视图

更新时间: 2015 年 1 月
 
 

4.4.2 使用信号处理程序来查找异常

上一节提供了几种用来在程序的开头启用捕获以查找第一次出现的异常的方法。与之相反,可通过在程序本身内启用捕获功能来隔离出现的任何特定异常。如果您启用了捕获,但未安装 SIGFPE 处理程序,则该程序将在下次出现捕获的异常时中止。或者,如果您安装了 SIGFPE 处理程序,则下次出现捕获的异常时将导致系统将控制权转交给该处理程序,该处理程序随后打印诊断信息(如在其中发生了异常的指令的地址),然后中止或继续执行。为了继续执行任何预计产生有意义结果的运算,处理程序可能需要为异常运算提供一个结果,如下一节所述。

使用 ieee_handler 可以同时启用对五个 IEEE 浮点异常中的任何异常的捕获,要求在发生指定的异常时中止该程序或者建立 SIGFPE 处理程序。还可以使用较低级别的函数 sigfpe(3)、signal(3c) 或 sigaction(2) 之一来安装 SIGFPE 处理程序;但是,这些函数不像 ieee_handler 那样启用捕获功能。切记,只有启用了浮点异常的陷阱时,浮点异常才触发 SIGFPE 信号。

4.4.2.1 ieee_handler (3m)

ieee_handler 的调用语法是:

i = ieee_handler(action, exception, handler)

前两个输入参数 actionexception 是字符串。第三个输入参数 handler 的类型为 sigfpe_handler_type,该类型定义在 floatingpoint.h 中。

三个输入参数可使用以下值:

输入参数
C 或 C++ 类型
可能的值
action
char *
get, set, clear
exception
char *
invaliddivisionoverflow,
underflowinexact
allcommon
handler
sigfpe_handler_type
用户定义的例程
SIGFPE_DEFAULT
SIGFPE_IGNORE
SIGFPE_ABORT

当请求的操作为 "set" 时,ieee_handler 建立由 handler 指定的名为 exception 的异常的处理函数。处理函数可能为 SIGFPE_DEFAULTSIGFPE_IGNORE,两者都选择缺省 IEEE 行为 SIGFPE_ABORT 或者用户提供的子例程的地址,当发生任一指定异常时,选择此缺省行为会导致程序中止,选择此地址会导致该子例程被调用(通过 sigaction(2) 手册页中关于装有 SA_SIGINFO 标志设置的信号处理程序中所介绍的参数来完成)。如果处理程序为 SIGFPE_DEFAULTSIGFPE_IGNORE,发生指定异常时,ieee_handler 也会禁用捕获;对于任何其他处理程序,ieee_handler 会启用捕获。

在 x86 平台上,每当异常的陷阱处于启用状态并且引发相应标志时,浮点硬件都会启用陷阱。因此,要避免伪陷阱,程序应在调用 ieee_handler 来启用捕获之前清除每个指定的 exception 的标志。

当请求的 action"clear" 时,ieee_handler 将撤销处理函数当前正针对指定 exception 安装的异常并禁用其陷阱。这与针对 SIGFPE_DEFAULT 执行 "set" 操作相同。当 action"clear" 时,会忽略第三个参数。

对于 "set""clear" 操作,如果请求的操作可用,则 ieee_handler 返回 0,否则返回非零值。

当请求的 action"get" 时,ieee_handler 返回当前为指定 exception 安装的处理程序的地址,如果未安装任何处理程序,则返回 SIGFPE_DEFAULT

下面的示例显示几个代码段,通过它们可阐释如何使用 ieee_handler。下面的 C 代码导致该程序在遇到除以零时中止:

#include <sunmath.h> 
/* uncomment the following line on x86 systems */
    /* ieee_flags("clear", "exception", "division", NULL); */
    if (ieee_handler("set", "division", SIGFPE_ABORT) != 0)
        printf("ieee trapping not supported here \n");

下面是等效的 Fortran 代码:

#include <floatingpoint.h> 
c uncomment the following line on x86 systems
c     ieee_flags('clear', 'exception', 'division', %val(0))
      i = ieee_handler('set', 'division', SIGFPE_ABORT) 
      if(i.ne.0) print *,'ieee trapping not supported here'

下面的 C 代码段恢复针对所有异常的 IEEE 缺省异常处理:

#include <sunmath.h> 
    if (ieee_handler("clear", "all", 0) != 0) 
        printf("could not clear exception handlers\n");

下面是 Fortran 中同样的操作:

      i = ieee_handler('clear', 'all', 0) 
      if (i.ne.0) print *, 'could not clear exception handlers'

4.4.2.2 报告来自信号处理程序的异常

当通过 ieee_handler 安装的 SIGFPE 处理程序被调用时,操作系统提供其他信息,指出所发生的异常的类型、导致该异常的指令的地址以及计算机的整数和浮点寄存器的内容。该处理程序会检查这些信息,并打印用来标识异常及其所发生位置的消息。

要访问由系统提供的信息,请按如下方式声明该处理程序。本章的其余部分提供 C 样例代码;要查看 Fortran 中 SIGFPE 处理程序的示例,请参见Appendix A, 示例

#include <siginfo.h>
#include <ucontext.h>
 
void handler(int sig, siginfo_t *sip, ucontext_t *uap)
{
    ...
}

当该处理程序被调用时,sig 参数包含已发送的信号的数量。信号数量定义在 sys/signal.h 中;SIGFPE 的信号数量是 8。

sip 参数指向可记录有关该信号的其他信息的结构。对于 SIGFPE 信号,该结构的相关成员是 sip‐>si_codesip->si_addr(请参见 /usr/include/sys/siginfo.h)。这些成员的重要性取决于系统以及用来触发 SIGFPE 信号的事件。

sip->si_code 成员是列在Table 4–3 中的 SIGFPE 信号类型之一。所显示的标记是在 sys/machsig.h 中定义的。

表 4-3  运算异常的类型
SIGFPE 类型
IEEE 类型
FPE_INTDIV
n/a
FPE_INTOVF
n/a
FPE_FLTRES
不精确
FPE_FLTDIV
FPE_FLTUND
下溢
FPE_FLTINV
无效
FPE_FLTOVF
溢出

正如上表所示,每种类型的 IEEE 浮点异常都有一个与之对应的 SIGFPE 信号类型。整数除以零 (FPE_INTDIV) 和整数溢出 (FPE_INTOVF) 也包括在 SIGFPE 类型中,但是由于它们不是 IEEE 浮点异常,所以您不能通过 ieee_handler 来为它们安装处理程序。可通过 sigfpe(3) 来为这些 SIGFPE 类型安装处理程序;但是,请注意,在所有的 SPARC 和 x86 平台上,会在缺省情况下忽略整数溢出。特殊的指令可导致传递 FPE_INTOVF 类型的 SIGFPE 信号,但是 Sun 编译器不生成这些指令。

对于与 IEEE 浮点异常对应的 SIGFPE 信号,sip->si_code 成员用于指明出现了哪个异常。在基于 x86 的系统上,它实际上用于指明引发了其标志的拥有最高优先级的解除屏蔽异常。这通常与最后出现的异常是一样的。在基于 SPARC 的系统上,sip->si_addr 成员存放导致了异常的指令的地址,而在基于 x86 的系统上,它存放用来进行捕获的指令的地址,通常是紧跟在导致异常的指令后面的浮点指令。

最后,uap 参数指向用来记录在进行捕获时系统状态的结构。该结构的内容与系统有关;要查看它的某些成员的定义,请参见 /usr/include/sys/siginfo.h

使用操作系统提供的信息,可以编写 SIGFPE 处理程序,该处理程序报告所发生的异常的类型以及导致它的指令的地址。Example 4–1 显示了此类处理程序。

示例 4-1  SIGFPE 处理程序
#include <stdio.h>
#include <sys/ieeefp.h>
#include <sunmath.h>
#include <siginfo.h>
#include <ucontext.h>

void handler(int sig, siginfo_t *sip, ucontext_t *uap)
{
    unsigned    code, addr;

    code = sip->si_code;
    addr = (unsigned) sip->si_addr;
    fprintf(stderr, "fp exception %x at address %x\n", code,
        addr);
}

int main()
{
    double  x;

    /* trap on common floating point exceptions */
    if (ieee_handler("set", "common", handler) != 0)
        printf("Did not set exception handler\n");
    /* cause an underflow exception (will not be reported) */
    x = min_normal();
    printf("min_normal = %g\n", x);
    x = x / 13.0;
    printf("min_normal / 13.0 = %g\n", x);

    /* cause an overflow exception (will be reported) */
    x = max_normal();
    printf("max_normal = %g\n", x);
    x = x * x;
    printf("max_normal * max_normal = %g\n", x);
    ieee_retrospective(stderr);
    return 0;
}

在 SPARC 系统上,该程序的输出结果类似于如下内容:

min_normal = 2.22507e-308
min_normal / 13.0 = 1.7116e-309
max_normal = 1.79769e+308
fp exception 4 at address 10d0c
max_normal * max_normal = 1.79769e+308
Note: IEEE floating-point exception flags raised:
    Inexact;  Underflow; 
IEEE floating-point exception traps enabled:
    overflow; division by zero; invalid operation; 
See the Numerical Computation Guide, ieee_flags(3M), ieee_handler(3M)

在 x86 平台上,在调用 SIGFPE 处理程序之前,操作系统会保存已发生异常标志的副本,然后清除这些标志。除非该处理程序执行用来保存这些标志的步骤,否则已发生标志将在该处理程序返回之后丢失。因此,在使用 –xarch=386 进行编译时,上述程序的输出结果并未指出引发了下溢异常:

min_normal = 2.22507e-308
min_normal / 13.0 = 1.7116e-309
max_normal = 1.79769e+308
fp exception 4 at address 8048fe6
max_normal * max_normal = 1.79769e+308
 Note: IEEE floating-point exception traps enabled: 
    overflow;  division by zero;  invalid operation; 
 See the Numerical Computation Guide, ieee_handler(3M)

但在缺省情况下,或在使用 –xarch=sse2 进行编译时,由于 PC 永远不会使循环指令通过,因此 tjos 测试程序将进行循环。对于 Oracle Solaris Studio 12.4 ,添加一行代码就足以使 PC 进行递增:

uap -> UC_mcontext.gregs[REG_PC= +=5;

上述代码仅对 –xarch=sse2 有效,并且只有在 SSE2 指令的长度恰好为 5 个字节时。完全通用的 SSE2 解决方案涉及对优化代码进行解码,以查找下一指令的开头。改用 fex_set_handling

在大多数情况下,在捕获处于启用状态时,导致异常的指令不传递 IEEE 缺省结果:在上面的输出结果中,针对 max_normal * max_normal 报告的值不是溢出运算(即,带有正确符号的无穷大)的缺省结果。通常,SIGFPE 处理程序必须为导致捕获异常的运算提供一个结果,以便继续用有意义的值进行计算。要查看执行上述操作的一种方法,请参见处理异常