【问题标题】:Signal handling in C with process and 2 threads doesn't work带有进程和 2 个线程的 C 中的信号处理不起作用
【发布时间】:2011-09-09 22:29:18
【问题描述】:

我正在使用以下示例(基于 linux 中 pthread_sigmask 手册页中的示例):

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <string.h>

/* Simple error handling functions */

#define handle_error_en(en, msg) \
        do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

static void * silly_worker(void *arg)
{
  for(int index=0,max=5; index<max; ++index) {
    printf("waiting %d of %d\n",index,max);
    sleep(1);
  }
  puts("Finished waiting.  Here comes the SIGSEGV");
  strcpy(NULL,"this will crash");
}

static void *
sig_thread(void *arg)
{
    sigset_t *set = (sigset_t *) arg;
    int s, sig;

    for (;;) {
        s = sigwait(set, &sig);
        if (s != 0)
            handle_error_en(s, "sigwait");
        printf("Signal handling thread got signal %d\n", sig);
    }
}

int
main(int argc, char *argv[])
{
    pthread_t thread;
    pthread_t thread2;
    sigset_t set;
    int s;

    /* Block SIGINT; other threads created by main() will inherit
       a copy of the signal mask. */

    sigemptyset(&set);
    sigaddset(&set, SIGQUIT);
    sigaddset(&set, SIGUSR1);
    sigaddset(&set, SIGSEGV);
    s = pthread_sigmask(SIG_BLOCK, &set, NULL);
    if (s != 0)
        handle_error_en(s, "pthread_sigmask");

    s = pthread_create(&thread, NULL, &sig_thread, (void *) &set);
    if (s != 0)
        handle_error_en(s, "pthread_create");

    /* Main thread carries on to create other threads and/or do
       other work */

    s = pthread_create(&thread2, NULL, &silly_worker, (void *) &set);
    if (s != 0)
        handle_error_en(s, "pthread_create");

    pause();            /* Dummy pause so we can test program */
}

根据手册页,这应该捕获 silly_worker 线程生成的 SIGSEGV。但事实并非如此。事实上,我完全不确定哪个机构收到了信号。当程序运行时,我得到以下输出:

waiting 0 of 5
waiting 1 of 5
waiting 2 of 5
waiting 3 of 5
waiting 4 of 5
Finished waiting.  Here comes the SIGSEGV
Segmentation fault

您可以看到信号处理程序没有输出“分段错误”字符串,因此它必须来自默认处理程序。如果是默认设置,那么它会破坏示例的目的 - 设置信号处理程序并捕获信号并对其进行处理。

我可以找到很多处理程序的示例,但它们都不适用于这种情况:它们都没有演示导致非常明显的 SIGSEGV 的线程,并在其自定义处理程序中捕获并报告错误。

问题仍然存在:如何获得自定义信号处理程序以从该 SIGSEGV 线程获取信号?

【问题讨论】:

  • 这对 SIGINT 等其他信号有效吗?像 SIGSEGV 和 SIGILL 这样的一些信号是同步的,并且总是被传递给导致它们的线程。我不确定 sigwait 是否可以处理这些。
  • 请注意,当 shell 看到您的进程被 SIGSEGV 信号杀死时,通常会打印“分段错误”。
  • 它适用于其他信号,但这无济于事:我正在尝试专门捕获 SIGSEGV。在一个简短得多的示例中,在 main() 中只有一个循环并且没有线程,我可以捕获 SIGSEGV,完全没有问题。当线程开始发挥作用时,它似乎无法正常工作。
  • 请注意,如果您使用pthread_killraise 将它们发送到特定线程,它也不适用于其他信号...

标签: c linux multithreading


【解决方案1】:

来自无效内存访问的SIGSEGV(与killsigqueue 发送的“假”相反)被发送到执行无效内存访问的线程,而不是整个进程。因此,您不能拥有专用的段错误处理程序线程。如果你想处理它,你必须在它发生的线程中处理它。 (您看到 shell 打印 Segmentation fault 的原因是,当 SIGSEGV 在线程中被阻塞并发生段错误时,内核执行杀死进程的默认操作。实际上它是每个 POSIX 的 UB,但这是Linux 如何处理 UB。)

但是请注意,您可以让SIGSEGV 的信号处理程序通过专用线程触发操作。一个丑陋的方法是使用另一个信号,您可以通过sigqueue 发送(连同一个参数)。更简洁的方法(至少在我看来)是让SIGSEGV 处理程序使用sem_post,它是异步信号安全的,可用于唤醒另一个等待信号量的线程。

【讨论】:

  • +1。您不能使用 SIGSEGV 执行此操作“如果在阻塞时生成 SIGBUS、SIGFPE、SIGILL 或 SIGSEGV,则结果未定义,除非信号由 kill(2)、sigqueue(2) 或 raise( 3). "
  • 那么与其他所有信号类型不同,sigsegv 总是传递给 Linux 上的触发线程?信号进入随机线程通常是使用专用信号线程并屏蔽所有其他线程的原因。
  • 通过kill 或通过终端(例如Ctrl-C)发送到进程的信号转到任意线程(在未屏蔽信号的线程集中)。这些是异步信号。作为线程某些操作的直接结果而发生的信号(如无效的内存访问、写入到读取端关闭的管道或套接字等)是 同步 信号并转到其所在的线程行为产生了他们。
最近更新 更多