简体   繁体   English

为什么 sem_wait 不会在中断时解除阻塞(并返回 -1)?

[英]Why does sem_wait not unblock (and return -1) on an interrupt?

I have a programme using sem_wait .我有一个使用sem_wait的程序。 The Posix specification says: Posix 规范说:

The sem_wait() function is interruptible by the delivery of a signal. sem_wait() function 可以通过传递信号来中断。

Additionally, in the section about errors it says:此外,在关于错误的部分中它说:

[EINTR] - A signal interrupted this function. [EINTR] - 一个信号中断了这个 function。

However, in my programme, sending a signal does not unblock the call (and return -1 as indicated in the spec).但是,在我的程序中,发送信号不会解除对调用的阻塞(并返回-1 ,如规范中所示)。

A minimal example can be found below.可以在下面找到一个最小的示例。 This programme hangs and sem_wait never unblocks after the signal is sent.该程序挂起,并且sem_wait在信号发送后永远不会解除阻塞。

#include <semaphore.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

sem_t sem;

void sighandler(int sig) {
  printf("Inside sighandler\n");
}

void *thread_listen(void *arg) {
  signal(SIGUSR1, &sighandler);
  printf("sem_wait = %d\n", sem_wait(&sem));
  return NULL;
}

int main(void) {

  pthread_t thread;

  sem_init(&sem, 0, 0); 

  pthread_create(&thread, NULL, &thread_listen, NULL);

  sleep(1);
  raise(SIGUSR1);

  pthread_join(thread, NULL);

  return 0;
}

The programme outputs Inside sighandler then hangs.程序输出Inside sighandler然后挂起。

There is another question here about this, but it doesn't really provide any clarity. 这里还有另一个问题,但它并没有真正提供任何明确性。

Am I misunderstanding what the spec says?我是否误解了规范所说的内容? FYI my computer uses Ubuntu GLIBC 2.31-0ubuntu9.仅供参考,我的电脑使用 Ubuntu GLIBC 2.31-0ubuntu9。

There are three reasons why this program doesn't behave as you expect, only two of which are fixable.该程序无法按预期运行的原因有三个,其中只有两个是可以修复的。

  1. As pointed out in David Schwartz's answer, in a multi-threaded program, raise sends a signal to the thread that calls raise .正如大卫施瓦茨的回答中指出的那样,在多线程程序中, raise向调用raise的线程发送一个信号。

    To get the signal sent to the thread you wanted, in this test program , change the raise(SIGUSR1) to pthread_kill(thread, SIGUSR1) .为了将信号发送到您想要的线程,在这个测试程序中,将raise(SIGUSR1)更改为pthread_kill(thread, SIGUSR1) However, if you want that specific thread to handle SIGUSR1 when it's sent to the entire process , what you need to do is use pthread_sigmask to block SIGUSR1 in all of the threads except the one that's supposed to handle it.但是,如果您希望该特定线程在发送到整个进程时处理SIGUSR1 ,您需要做的是使用pthread_sigmask在所有线程中阻止SIGUSR1除了应该处理它的线程。 (See below for more detail on this.) (有关此内容的更多详细信息,请参见下文。)

  2. On systems that use glibc, signal installs a signal handler that does not interrupt blocking system calls.在使用 glibc 的系统上, signal安装一个不会中断阻塞系统调用的信号处理程序。 To get a signal handler that does, you need to use sigaction and set sa_flags to a value that doesn't include SA_RESTART .要获得这样的信号处理程序,您需要使用sigaction并将sa_flags设置为包含SA_RESTART的值。 For instance,例如,

     struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_handler = sighandler; sa.sa_flags = 0; sigaction(SIGUSR1, &sa, 0);

    Note: memset(&sa, 0, sizeof sa) is not guaranteed to have the same effect as sigemptyset(&sa.sa_mask) .注意: memset(&sa, 0, sizeof sa)保证与sigemptyset(&sa.sa_mask)具有相同的效果。

    Note: Signal handlers are process-global, so it doesn't matter which thread you call sigaction on.注意:信号处理程序是进程全局的,因此在哪个线程上调用sigaction并不重要。 In almost all cases, multithreaded programs should do all their sigaction calls in main before creating any threads, just to make sure the signal handlers are active before any signals can happen.在几乎所有情况下,多线程程序都应该在创建任何线程之前在main中执行所有sigaction调用,以确保信号处理程序在任何信号发生之前处于活动状态。

  3. The signal could be delivered to the thread before the thread has a chance to call sem_wait .信号可以在线程有机会调用sem_wait之前传递给线程。 If that happens, the signal handler will be called and return, and then sem_wait will be called and it will block forever.如果发生这种情况,信号处理程序将被调用并返回,然后sem_wait将被调用,它将永远阻塞。 In this test program, you can make this arbitrarily unlikely by increasing the length of the sleep in main , but there is no way to make it impossible .在这个测试程序中,你可以通过增加main中的sleep的长度来使这种情况变得不可能,但是没有办法让它变得不可能 This is the unfixable reason.这是无法解决的原因。

    There are a small number of system calls that atomically unblock signals while sleeping, and then block them again before returning to user space, such as sigsuspend , sigwaitinfo , and pselect .有少量系统调用会在睡眠时自动解除对信号的阻塞,然后在返回用户空间之前再次阻塞它们,例如sigsuspendsigwaitinfopselect These are the only system calls for which this race condition can be avoided.这些是唯一可以避免这种竞争条件的系统调用。

    Best practice for a multi-threaded program that has to deal with signals is to have one thread devoted to signal handling.必须处理信号的多线程程序的最佳实践是让一个线程专门用于信号处理。 To make that work reliably, you should block all signals except for synchronous CPU exceptions ( SIGABRT , SIGBUS , SIGFPE , SIGILL , SIGSEGV , SIGSYS , and SIGTRAP ) at the very beginning of main , before creating any threads.为了使其可靠地工作,您应该在main的最开始,创建任何线程之前阻止所有信号,除了同步 CPU 异常( SIGABRTSIGBUSSIGFPESIGILLSIGSEGVSIGSYSSIGTRAP )。 Then you set a do-nothing signal handler ( with SA_RESTART ) for the signals you want to handle;然后为要处理的信号设置一个无操作信号处理程序(使用SA_RESTART ); these will never actually be called, their purpose is to prevent the kernel from killing the process due to the default action of SIGUSR1 or whatever.这些实际上永远不会被调用,它们的目的是防止 kernel 由于SIGUSR1的默认操作或其他原因而终止进程。 The set of signals you care about must include all of the signals for user interrupts: SIGHUP , SIGINT , SIGPWR , SIGQUIT , SIGTERM , SIGTSTP , SIGXCPU , SIGXFSZ .您关心的信号集必须包括用户中断的所有信号: SIGHUPSIGINTSIGPWRSIGQUITSIGTERMSIGTSTPSIGXCPUSIGXFSZ Finally, you create the signal-handling thread, which loops calling sigwaitinfo for the appropriate set of signals, and dispatches messages to the rest of the threads using pipes or condition variables or anything but signals really.最后,您创建信号处理线程,该线程循环调用sigwaitinfo以获得适当的信号集,并使用管道或条件变量或信号之外的任何其他东西将消息分派到线程的 rest。 This thread must never block in any system call other than sigwaitinfo .该线程绝不能阻塞在sigwaitinfo以外的任何系统调用中。

    In the case of this test program, the signal-handling thread would respond to SIGUSR1 by calling sem_post(&sem) .在这个测试程序的情况下,信号处理线程将通过调用sem_post(&sem)来响应SIGUSR1 This would either wake up the listener thread, or it would cause the listener thread not to become blocked on sem_wait in the first place.这将唤醒侦听器线程,或者它会导致侦听器线程首先不会在sem_wait上被阻塞。

In a multi-threaded program, raise sends a signal to the thread that calls raise .在多线程程序中, raise向调用raise的线程发送一个信号。 You need to use kill(getpid(), ...) or pthread_signal(thread, ...) .您需要使用kill(getpid(), ...)pthread_signal(thread, ...)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM