C语言中的Abort函数

6

程序 1:

#include<stdio.h>
#include<signal.h>
void handler(int sig);
void main()
{
    printf("PID: %d\n",getpid());
    signal(SIGABRT,handler);
    while(1){
        printf("Hai\n");
        sleep(1);
        abort();
    }
}

void handler(int sig)
{
    printf("Signal handled\n");
}

输出1:

$ ./a.out 
PID: 32235
Hai
Signal handled
Aborted (core dumped)
$

根据参考资料,abort函数的工作方式类似于raise(SIGABRT)。因此,由abort()函数生成的信号是SIGABRT。为此,我创建了上述程序。
在该程序中,处理了SIGABRT信号。信号处理程序执行后,它不会返回到调用它的主函数。为什么处理程序完成后不返回到主函数?
程序2:
#include<stdio.h>
#include<signal.h>
void handler(int sig);
void main()
{
    printf("PID: %d\n",getpid());
    signal(SIGABRT,handler);
    while(1){
        printf("Hai\n");
        sleep(1);
    }
}

void handler(int sig)
{
    printf("Signal handled\n");
}

输出2:

$ ./a.out 
PID: 32247
Hai
Hai
Hai
Signal handled
Hai
Signal handled
Hai
Hai
^C
$ 

与程序1不同,程序2按预期执行。在上述程序中,通过以下方式使用kill命令将信号发送到进程。

$ kill -6 32247
$ kill -6 32247

因此,一旦信号发生,处理程序函数会执行,然后返回到主函数。但在程序1中,这种情况并不会发生。为什么会出现这种情况?abort函数和SIGABRT有什么不同吗?


我认为你想知道这两个调用之间的区别。 可能是重复的问题 - Bilal
1
阅读 signal(7)。不要在信号处理程序中调用 printf - Basile Starynkevitch
@BasileStarynkevitch:实际上,如果您确定信号不是异步生成的,您可以从信号处理程序调用printf()。从shell中的kill -6是异步的,而对abort()的调用不是异步的。 - rodrigo
5个回答

6
请参考来自man 3 abort的文档:
这将导致进程异常终止,除非捕获了SIGABRT信号并且信号处理程序没有返回(请参阅longjmp(3))。
还有这个:
如果忽略SIGABRT信号或由返回的处理程序捕获,则abort()函数仍将终止进程。 它通过恢复SIGABRT的默认设置,然后再次引发信号来实现此目的。
因此,唯一可以防止abort()中止程序的方法是从信号处理程序中执行longjmp()

2

Libc实现了abort()函数。在其实现中,abort()会检查进程是否仍然存活,因为abort()是在执行raise(SIGABRT)之后执行的。如果它仍然存活,则知道用户已经处理了SIGABRT。根据文档,这并不重要,因为进程仍然会退出:

您可以在GLIBC源代码中查看确切的实现(stdlib/abort.c):

/* Cause an abnormal program termination with core-dump.  */
void
abort (void)
{
  struct sigaction act;
  sigset_t sigs;

  /* First acquire the lock.  */
  __libc_lock_lock_recursive (lock);

  /* Now it's for sure we are alone.  But recursive calls are possible.  */

  /* Unlock SIGABRT.  */
  if (stage == 0)
    {
      ++stage;
      if (__sigemptyset (&sigs) == 0 &&
      __sigaddset (&sigs, SIGABRT) == 0)
    __sigprocmask (SIG_UNBLOCK, &sigs, (sigset_t *) NULL);
    }

  /* Flush all streams.  We cannot close them now because the user
     might have registered a handler for SIGABRT.  */
  if (stage == 1)
    {
      ++stage;
      fflush (NULL);
    }

  /* Send signal which possibly calls a user handler.  */
  if (stage == 2)
    {
      /* This stage is special: we must allow repeated calls of
     `abort' when a user defined handler for SIGABRT is installed.
     This is risky since the `raise' implementation might also
     fail but I don't see another possibility.  */
      int save_stage = stage;

      stage = 0;
      __libc_lock_unlock_recursive (lock);

      raise (SIGABRT);

      __libc_lock_lock_recursive (lock);
      stage = save_stage + 1;
    }

  /* There was a handler installed.  Now remove it.  */
  if (stage == 3)
    {
      ++stage;
      memset (&act, '\0', sizeof (struct sigaction));
      act.sa_handler = SIG_DFL;
      __sigfillset (&act.sa_mask);
      act.sa_flags = 0;
      __sigaction (SIGABRT, &act, NULL);
    }

  /* Now close the streams which also flushes the output the user
     defined handler might has produced.  */
  if (stage == 4)
    {
      ++stage;
      __fcloseall ();
    }

  /* Try again.  */
  if (stage == 5)
    {
      ++stage;
      raise (SIGABRT);
    }

  /* Now try to abort using the system specific command.  */
  if (stage == 6)
    {
      ++stage;
      ABORT_INSTRUCTION;
    }

  /* If we can't signal ourselves and the abort instruction failed, exit.  */
  if (stage == 7)
    {
      ++stage;
      _exit (127);
    }

  /* If even this fails try to use the provided instruction to crash
     or otherwise make sure we never return.  */
  while (1)
    /* Try for ever and ever.  */
    ABORT_INSTRUCTION;
}

1

abort函数发送真实的SIGABRT信号,但是即使你捕获(或忽略)了该信号,abort函数仍然会退出你的进程。

来自链接页面:

返回值

abort()函数永远不会返回。


0

根据标准,如果处理SIGABRT信号,则不完全指定应发生什么:

除非捕获信号SIGABRT并且信号处理程序不返回,否则abort函数会导致异常程序终止。未写入缓冲数据的打开流是否刷新,打开流是否关闭或临时文件是否被删除是实现定义的。通过函数调用raise(SIGABRT)以实现向主机环境返回一种实现定义的失败终止状态。

但是已经指定了不应该发生的情况:

abort函数不会返回到其调用者。

因此,正确的行为是确保发生“异常终止”。这由abort函数尽最大努力以各种方式终止程序来确保,如果没有任何方法可以解决问题,它将进入无限循环(并至少确保不返回给调用者)。


0

它们不是一样的。 abort 函数会调用两次 raise(SIGABRT)。如果你为 SIGABRT 定义了一个处理程序,它将首先调用你的处理程序,然后再调用默认处理程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接