【问题标题】:Can a C program continue execution after a signal is handled?处理完信号后,C 程序能否继续执行?
【发布时间】:2013-01-09 10:44:03
【问题描述】:

我是通过 C 在 Unix 中处理信号的新手,我一直在看一些关于它的教程(出于纯粹的兴趣)。

我的问题是,是否可以在处理信号之后继续执行程序?

我知道信号处理函数会进行清理,但本着异常处理的精神(例如在 C++ 中),是否可以以相同的方式处理该信号并让程序继续正常运行?

目前 catch 进入无限循环(大概退出的方法是调用 exit(1) )。

我的意图是将b 分配为 1 并让程序优雅地完成(当然,如果可能的话)。

这是我的代码:

#include <signal.h>
#include <stdio.h>

int a = 5;
int b = 0;

void catch(int sig)
{
    printf("Caught the signal, will handle it now\n");
    b = 1;
}

int main(void)
{
    signal(SIGFPE, catch);

    int c = a / b;

    return 0;
}

另外,由于 C 是过程性的,为什么在违规语句执行后实际上调用了在违规语句之前声明的信号处理程序?

最后,为了让处理函数正确地进行清理,所有在发生异常时需要清理的变量都需要在函数之前声明,对吧?

如果上述某些内容非常明显,请提前感谢您的回答和道歉。

【问题讨论】:

  • 常见用例:许多(守护进程)进程可以处理SIGHUP 信号,这使得它们重新加载配置文件。

标签: c signals


【解决方案1】:

是的,这就是信号处理程序的用途。但有些信号需要特别处理才能让程序继续运行(例如 SIGSEGV、SIGFPE、...)。

查看sigaction的手册页:

根据 POSIX,进程的行为在它忽略了未定义的 SIGFPE、SIGILL 或 SIGSEGV 信号后是未定义的 由 kill(2) 或 raise(3) 生成。整数除以零有未定义的结果。在某些架构上,它会生成一个 SIGFPE 信号。 (同样将最大负整数除以 -1 可能会生成 SIGFPE。)忽略此信号可能会导致 无限循环。

现在,您 忽略了信号,没有采取任何措施阻止它(再次)发生。您需要信号处理程序中的执行上下文并手动修复它,这涉及覆盖一些寄存器。

如果在 sa_flags 中指定了 SA_SIGINFO,则 sa_sigaction(而不是 sa_handler) 指定signum 的信号处理函数。这 函数接收信号编号作为它的第一个参数,一个指针 以 siginfo_t 作为其第二个参数和指向 ucontext_t 的指针 (cast to void *) 作为它的第三个参数。 (通常,处理程序 函数不使用第三个参数。看 getcontext(2) 获取有关 ucontext_t 的更多信息。)

上下文允许在发生故障时访问寄存器,需要进行更改以允许您的程序继续运行。请参阅此lkml post。如那里所述,siglongjmp 也可能是一种选择。该帖子还提供了一种可重用的解决方案来处理错误,而无需将变量设为全局等:

而且因为您自己处理,所以您拥有所需的任何灵活性 与错误处理。例如,您可以制作故障处理程序 跳转到函数中的某个指定点,例如 这个:

 __label__ error_handler;   
 __asm__("divl %2"      
         :"=a" (low), "=d" (high)       
         :"g" (divisor), "c" (&&error_handler))     
 ... do normal cases ...

 error_handler:     
     ... check against zero division or overflow, so  whatever you want to ..

然后,您的 SIGFPE 处理程序只需要执行类似的操作

context.eip = context.ecx;

【讨论】:

  • 非常感谢您提供的信息丰富的答案。我对SIGALRM 做了类似的事情,它确实捕获了异常并正常进行,不知道SIGFPE 很特别。如果不是要求太多,能否请您简要告诉我,我需要如何在帖子中集成代码来处理此异常?我假设我需要在main 例程中声明汇编代码,但我不确定将标签或帖子中提到的sigfpe_handler 函数放在哪里(我想我不能把它作为参数signal 因为指针参数只接受信号 int 作为参数)。
  • 您需要使用sigaction 代替signal。 sigaction 结构在它提供的选项数量上更加通用。对于另一部分,请仔细阅读 lkml 帖子,我认为我无法为您编写该代码。
  • 没关系,谢谢,只是需要一些有关使用情况的信息。
【解决方案2】:

如果您知道自己在做什么,则可以将指令指针设置为指向违规指令之后的位置。下面是我的 x86(32 位和 64 位)示例。不要在家里或在实际产品中尝试!!!

#define _GNU_SOURCE /* Bring REG_XXX names from /usr/include/sys/ucontext.h */

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <ucontext.h>

static void sigaction_segv(int signal, siginfo_t *si, void *arg)
{
    ucontext_t *ctx = (ucontext_t *)arg;

    /* We are on linux x86, the returning IP is stored in RIP (64bit) or EIP (32bit).
       In this example, the length of the offending instruction is 6 bytes.
       So we skip the offender ! */
    #if __WORDSIZE == 64
        printf("Caught SIGSEGV, addr %p, RIP 0x%lx\n", si->si_addr, ctx->uc_mcontext.gregs[REG_RIP]);
        ctx->uc_mcontext.gregs[REG_RIP] += 6;
    #else
        printf("Caught SIGSEGV, addr %p, EIP 0x%x\n", si->si_addr, ctx->uc_mcontext.gregs[REG_EIP]);
        ctx->uc_mcontext.gregs[REG_EIP] += 6;
    #endif
}

int main(void)
{
    struct sigaction sa;

    memset(&sa, 0, sizeof(sa));
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = sigaction_segv;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGSEGV, &sa, NULL);

    /* Generate a seg fault */
    *(int *)NULL = 0;

    printf("Back to normal execution.\n");

    return 0;
}

【讨论】:

  • 我很感兴趣是否存在任何高级 RIP 函数,因为 x86 具有变长指令集。
【解决方案3】:

一般来说,是的,处理程序返回后继续执行。但是,如果信号是由硬件错误(例如浮点异常或分段错误)引起,则您无法撤消该错误,因此无论如何您的程序都将终止。 p>

换句话说,您必须区分信号和引起信号的事物。信号本身非常好且易于处理,但它们并不总能让您修复导致信号的错误。

(有些信号是特殊的,比如 ABRT 和 STOP,从某种意义上说,即使你只是用kill 手动发出这样的信号,你仍然不能“阻止它的影响”。当然 KILL 甚至不能完全可以处理。)

【讨论】:

  • SIGSTOP 不能被捕获或忽略,就像 SIGKILL 一样。
  • 您的回答提出了一个值得注意的问题:“区分信号和引起信号的事物”。谢谢。
猜你喜欢
  • 2020-09-20
  • 1970-01-01
  • 2018-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多