sigwait()和信号处理程序

时间:2014-02-04 12:28:50

标签: c linux multithreading pthreads signals

如果我为SIGABRT设置和信号处理程序,同时我有一个等待sigwait()的线程来接收SIGABRT(我通过pthread_sigmask在其他线程中阻塞了SIGABRT)。

那么首先处理哪一个?信号处理程序或sigwait()?

[我正面临一些问题,即sigwait()永远被阻止。我正在调试它]

main()
{
    sigset_t                    signal_set;

    sigemptyset(&signal_set);
    sigaddset(&signal_set, SIGABRT); 
    sigprocmask(SIG_BLOCK, &signal_set, NULL); 

    // Dont deliver SIGABORT while running this thread and it's kids.
    pthread_sigmask(SIG_BLOCK, &signal_set, NULL);

    pthread_create(&tAbortWaitThread, NULL, WaitForAbortThread, NULL);
    ..
    Create all other threads
    ...
}   

static void*    WaitForAbortThread(void* v)
{
    sigset_t signal_set;
    int stat;
    int sig;

    sigfillset( &signal_set);
    pthread_sigmask( SIG_BLOCK, &signal_set, NULL ); // Dont want any signals


    sigemptyset(&signal_set);
    sigaddset(&signal_set, SIGABRT);     // Add only SIGABRT

    // This thread while executing , will handle the SIGABORT signal via signal handler.
    pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL); 
    stat= sigwait( &signal_set, &sig  ); // lets wait for signal handled in CatchAbort().
    while (stat == -1)
    {
        stat= sigwait( &signal_set, &sig  );
    }

    TellAllThreadsWeAreGoingDown();

    sleep(10);

    return null;
}

// Abort signal handler executed via sigaction().
static void CatchAbort(int i, siginfo_t* info, void* v)
{
    sleep(20); // Dont return , hold on till the other threads are down.
}

在sigwait(),我会知道已收到SIGABRT。我将告诉其他线程。然后将保持中止信号处理程序,以便不终止进程。

我想知道sigwait()和信号处理程序的交互。

5 个答案:

答案 0 :(得分:4)

来自sigwait()文档:

  

sigwait()函数暂停执行调用线程,直到   信号集中指定的信号之一变为待定状态。

待处理信号表示等待传递给线程/进程之一的阻塞信号。因此,您需要来解锁信号,就像使用pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL)电话一样。

这应该有效:

static void* WaitForAbortThread(void* v){
    sigset_t signal_set;

    sigemptyset(&signal_set);
    sigaddset(&signal_set, SIGABRT); 

    sigwait( &signal_set, &sig  );

    TellAllThreadsWeAreGoingDown();

    sleep(10);

    return null;
}

答案 1 :(得分:2)

我从此< link>

获得了一些信息

它说:

为了允许线程等待异步生成的信号,线程库提供了sigwait子例程。 sigwait子例程阻塞调用线程,直到其中一个等待信号被发送到进程或线程。 使用sigwait子例程,等待信号上不得安装信号处理程序。

我将删除sigaction()处理程序并仅尝试sigwait()。

答案 2 :(得分:1)

从您发布的代码段中,您似乎错误地使用了sigwait()。 AFAIU,您需要WaitForAbortThread,如下所示:

     sigemptyset( &signal_set); // change it from sigfillset()
     for (;;) {
           stat = sigwait(&signal_set, &sig);

           if (sig == SIGABRT) {
          printf("here's sigbart.. do whatever you want.\n");
          pthread_kill(tid, signal); // thread id and signal
         }
       }

我认为不需要pthread_sigmask()。既然你只想处理SIGABRT,首先将init signal_set设为空,然后简单地添加SIGABRT,然后跳转到无限循环,sigwait将等待你正在寻找的特定信号,你检查如果它是SIGABRT则发出信号,如果是 - 做你想做的任何事情。注意使用pthread_kill(),使用它将任何信号发送到通过tid指定的其他线程和您要发送的信号,确保您知道要发送信号的其他线程的tid。希望这会有所帮助!

答案 3 :(得分:1)

我知道这个问题大约有一年了,但我经常使用一种模式,它使用pthreads和信号来解决这个问题。它有点长,但会处理我所知道的任何问题。

我最近与一个用SWIG包装的库结合使用,并在Python中调用。一个恼人的问题是我的IRQ线程使用sigwait等待SIGINT从未收到SIGINT信号。当从Matlab调用时,相同的库工作得很好,它没有捕获SIGINT信号。

解决方案是安装信号处理程序

#define _NTHREADS 8

#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <sched.h>
#include <linux/unistd.h>
#include <sys/signal.h>
#include <sys/syscall.h>
#include <setjmp.h>

#include <stdio.h>
#include <stdlib.h>

#include <errno.h>
#include <string.h> // strerror

#define CallErr(fun, arg)  { if ((fun arg)<0)          \
      FailErr(#fun) }

#define CallErrExit(fun, arg, ret)  { if ((fun arg)<0) \
      FailErrExit(#fun,ret) }

#define FailErrExit(msg,ret) {                    \
  (void)fprintf(stderr, "FAILED: %s(errno=%d strerror=%s)\n", \
        msg, errno, strerror(errno));             \
  (void)fflush(stderr);                       \
  return ret; }

#define FailErr(msg) {                                        \
  (void)fprintf(stderr, "FAILED: %s(errno=%d strerror=%s)\n", \
        msg, errno, strerror(errno));             \
  (void)fflush(stderr);}

typedef struct thread_arg {
  int cpu_id;
  int thread_id;
} thread_arg_t;

static jmp_buf jmp_env;

static struct sigaction act;
static struct sigaction oact;

size_t exitnow = 0;
pthread_mutex_t exit_mutex;

pthread_attr_t attr;

pthread_t pids[_NTHREADS];
pid_t     tids[_NTHREADS+1];

static volatile int status[_NTHREADS]; // 0: suspended, 1: interrupted, 2: success

sigset_t mask;

static pid_t gettid( void );
static void *thread_function(void *arg);
static void signalHandler(int);

int main() {

  cpu_set_t cpuset;
  int nproc;
  int i;
  thread_arg_t thread_args[_NTHREADS];
  int id;

  CPU_ZERO( &cpuset );
  CallErr(sched_getaffinity,
      (gettid(), sizeof( cpu_set_t ), &cpuset));

  nproc = CPU_COUNT(&cpuset);

  for (i=0 ; i < _NTHREADS ; i++) {
    thread_args[i].cpu_id = i % nproc;
    thread_args[i].thread_id = i;
    status[i] = 0;
  }

  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

  pthread_mutex_init(&exit_mutex, NULL);

  // We pray for no locks on buffers and setbuf will work, if not we
  // need to use filelock() on on FILE* access, tricky

  setbuf(stdout, NULL);
  setbuf(stderr, NULL);

  act.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT;
  act.sa_handler = signalHandler;
  sigemptyset(&act.sa_mask);
  sigemptyset(&mask);
  sigaddset(&mask, SIGINT);

  if (setjmp(jmp_env)) {

    if (gettid()==tids[0]) {
      // Main Thread
      printf("main thread: waiting for clients to terminate\n");
      for (i = 0; i < _NTHREADS; i++) {
    CallErr(pthread_join, (pids[i], NULL));
    if (status[i] == 1)
      printf("thread %d: terminated\n",i+1);
      }
      // On linux this can be done immediate after creation
      CallErr(pthread_attr_destroy, (&attr));
      CallErr(pthread_mutex_destroy, (&exit_mutex));

      return 0;
    }
    else {
      // Should never happen
      printf("worker thread received signal");
    }
    return -1;
  }

  // Install handler
  CallErr(sigaction, (SIGINT, &act, &oact));

  // Block SIGINT
  CallErr(pthread_sigmask, (SIG_BLOCK, &mask, NULL));

  tids[0] = gettid();
  srand ( time(NULL) );

  for (i = 0; i < _NTHREADS; i++) {
    // Inherits main threads signal handler, they are blocking
    CallErr(pthread_create,
        (&pids[i], &attr, thread_function,
         (void *)&thread_args[i]));
  }

  if (pthread_sigmask(SIG_UNBLOCK, &mask, NULL)) {
    fprintf(stderr, "main thread: can't block SIGINT");
  }
  printf("Infinite loop started - CTRL-C to exit\n");

  for (i = 0; i < _NTHREADS; i++) {
    CallErr(pthread_join, (pids[i], NULL));
    //printf("%d\n",status[i]);
    if (status[i] == 2)
      printf("thread %d: finished succesfully\n",i+1);
  }

  // Clean up and exit 
  CallErr(pthread_attr_destroy, (&attr));
  CallErr(pthread_mutex_destroy, (&exit_mutex));

  return 0;

}

static void signalHandler(int sig) {

  int i;
  pthread_t id;

  id = pthread_self();

  for (i = 0; i < _NTHREADS; i++)
    if (pids[i] == id) {
      // Exits if worker thread
      printf("Worker thread caught signal");
      break;
    }

  if (sig==2) {
    sigaction(SIGINT, &oact, &act);
  }

  pthread_mutex_lock(&exit_mutex);
  if (!exitnow)
    exitnow = 1;
  pthread_mutex_unlock(&exit_mutex);

  longjmp(jmp_env, 1); 
}

void *thread_function(void *arg) {
  cpu_set_t set;

  thread_arg_t* threadarg;

  int thread_id;

  threadarg = (thread_arg_t*) arg;

  thread_id = threadarg->thread_id+1;

  tids[thread_id] = gettid();

  CPU_ZERO( &set );
  CPU_SET( threadarg->cpu_id, &set );

  CallErrExit(sched_setaffinity, (gettid(), sizeof(cpu_set_t), &set ),
          NULL);

  int k = 8;
  // While loop waiting for exit condition
  while (k>0) {
    sleep(rand() % 3);
    pthread_mutex_lock(&exit_mutex);
    if (exitnow) {
      status[threadarg->thread_id] = 1;
      pthread_mutex_unlock(&exit_mutex);
      pthread_exit(NULL);
    }
    pthread_mutex_unlock(&exit_mutex);
    k--;
  }

  status[threadarg->thread_id] = 2;
  pthread_exit(NULL);
}

static pid_t gettid( void ) {
  pid_t pid;
  CallErr(pid = syscall, (__NR_gettid));
  return pid;
}

答案 4 :(得分:0)

我进行了服务器测试,结果是:

对于所有测试用例,我通过在主线程中调用sigaction来注册信号处理程序。

  1. 主线程阻塞目标信号,线程A通过调用pthread_sigmask解除阻塞目标信号,线程A睡眠,发送目标信号。

    结果:信号处理程序在线程A中执行。

  2. 主线程阻塞目标信号,线程A通过调用pthread_sigmask解除阻塞目标信号,线程A调用sigwait,发送目标信号。

    结果:sigwait被执行。

  3. 主线程不阻止目标信号,线程A不阻止目标信号,线程A调用sigwait,发送目标信号。

    结果:选择主线程,并在主线程中执行注册的信号处理程序。


如您所见,组合1和2很容易理解和得出结论。

是:

如果信号被线程阻塞,那么sigaction注册的进程范围的信号处理程序将无法捕获甚至不知道。

如果没有阻塞信号,并且在调用sigwait之前发送了信号,则进程范围的信号处理程序将获胜。这就是为什么 APUE 这些书要求我们在调用sigwait之前阻塞目标信号的原因。在这里,我在线程A中使用sleep来模拟较长的“窗口时间”。

如果没有阻塞信号,并且在sigwait已经等待时发送,则sigwait获胜。

但是您应该注意到,对于测试用例1和2,主线程被设计为阻塞目标信号。

最后对于测试用例3,当主线程未阻塞目标信号,并且线程A中的sigwait也正在等待时,信号处理程序将在主线程中执行。

我相信测试用例3的行为是 APUE 所谈论的:

来自 APUE §12.8:

  

如果捕获到信号(进程已建立信号   例如,使用sigaction处理程序),并且线程正在等待   调用sigwait时使用相同的信号,直到   实施以决定传递信号的方式。的   实现可以允许sigwait返回或调用   信号处理程序,但不能同时使用。


首先,如果要完成一个线程<->一个信号模型,则应:

  1. 使用pthread_sigmask阻止主线程中的所有信号(在主线程中创建的后续线程继承了信号掩码)
  2. 创建线程并使用目标信号调用sigwait(target_signal)

测试代码

#define _POSIX_C_SOURCE 200809L

#include <signal.h>
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

FILE* file;


void* threadA(void* argv){
    fprintf(file, "%ld\n", pthread_self());

    sigset_t m;
    sigemptyset(&m);
    sigaddset(&m, SIGUSR1);
    int signo;
    int err;


    // sigset_t q;
    // sigemptyset(&q);
    // pthread_sigmask(SIG_SETMASK, &q, NULL);
    // sleep(50);

    fprintf(file, "1\n");


    err = sigwait(&m, &signo);
    if (err != 0){
        fprintf(file, "sigwait error\n");
        exit(1);
    }

    switch (signo)
    {
    case SIGUSR1:
        fprintf(file, "SIGUSR1 received\n");
        break;

    default:
        fprintf(file, "?\n");
        break;
    }

    fprintf(file, "2\n");
}

void hello(int signo){
    fprintf(file, "%ld\n", pthread_self());
    fprintf(file, "hello\n");
}


int main(){
    file = fopen("daemon", "wb");

    setbuf(file, NULL);


    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = hello;
    sigaction(SIGUSR1, &sa, NULL);

    sigset_t n;
    sigemptyset(&n);
    sigaddset(&n, SIGUSR1);

    // pthread_sigmask(SIG_BLOCK, &n, NULL);

    pthread_t pid;
    int err;
    err = pthread_create(&pid, NULL, threadA, NULL);

    if(err != 0){
        fprintf(file, "create thread error\n");
        exit(1);
    }

    pause();

    fprintf(file, "after pause\n");
    fclose(file);
    return 0;

}

运行./a.out &(在后台运行),然后使用kill -SIGUSR1 pid进行测试。请勿使用raiseraisesleeppause是线程级的。