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

问题描述 投票:5回答:2

我有一个使用sem_wait的程序。 Posix specification说:

sem_wait()功能可通过信号传送中断。

此外,在有关错误的部分中说:

[[EINTR]-信号中断了此功能。

但是,在我的程序中,发送信号并不会解除阻止呼叫(并按规范所示返回-1

下面是一个最小的示例。发送信号后,该程序会挂起并且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;
}

程序输出Inside sighandler然后挂起。

关于此还有另一个问题here,但实际上并没有提供任何清晰性。

我是否误解了规格说明?仅供参考,我的计算机使用Ubuntu GLIBC 2.31-0ubuntu9。

c pthreads semaphore interrupt interrupt-handling
2个回答
6
投票

此程序有三个错误,只有两个是可修复的。

  1. 正如David Schwartz的回答所指出的,在多线程程序中,raise向调用raise的线程发送信号。

    要获取发送到所需线程的信号,在此测试程序中

    ,将raise(SIGUSR1)更改为pthread_kill(thread, SIGUSR1)。但是,如果要让特定线程在发送到整个过程时处理SIGUSR1,则需要做的是在所有线程中使用pthread_sigmask阻塞SIGUSR1 except应该处理的那个。为了使该信号可靠地工作,应从一开始就阻塞除同步CPU异常(SIGABRTSIGBUSSIGFPESIGILLSIGSEGVSIGSYSSIGTRAP)之外的所有信号main中的before创建任何线程,然后您指定的信号处理线程解除对它关心的信号的阻塞,该信号必须至少包括用于用户中断的信号(SIGHUPSIGINT,[C0 ],SIGPWRSIGQUITSIGTERMSIGTSTPSIGXCPU)。
  2. 在使用glibc的系统上,SIGXFSZ安装一个信号处理程序,该信号处理程序not

  3. 中断阻止系统调用。要获得能够执行此操作的信号处理程序,您需要使用signal并将sigaction设置为没有包含sa_flags的值。例如,
    SA_RESTART

    注意:保证 struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_handler = sighandler; sa.sa_flags = 0; sigaction(SIGUSR1, &sa, 0); memset(&sa, 0, sizeof sa)具有相同的效果。

    注意:信号处理程序是全局进程的,因此,在哪个线程上调用sigemptyset(&sa.sa_mask)都没有关系。在几乎所有情况下,多线程程序都应在创建任何线程之前执行其在sigaction中的所有sigaction调用,只是为了确保信号处理程序在任何信号发生之前都处于活动状态。

  4. 信号可以传递到线程

    之前

    ,线程有机会调用main。如果发生这种情况,将调用信号处理程序并返回,然后将调用sem_wait,它将永远阻塞。在此测试程序中,可以通过增加sem_waitsleep的长度来使这种情况不太可能发生,但是没有办法使其变为不可能
  5. 。这是无法修复的错误。有少量系统调用,它们在睡眠时会解除阻塞,然后在返回用户空间之前再次阻塞它们,例如mainsigsuspendsigsuspend。这些是

    only

    系统调用,可以避免这种竞争情况。对于必须处理信号的多线程程序的最佳实践是让一个线程devoted进行信号处理。如上所述,主程序会阻塞除同步CPU异常以外的所有信号;然后,它创建信号处理线程,该线程循环调用sigwaitinfo以获取适当的信号集,并使用管道或条件变量或任何真正的信号将消息分发到其余线程。除了sigwaitinfo之外,此线程绝不能阻止任何系统调用。 (您仍然需要为所有信号设置信号处理程序,但实际上它们永远不会被调用,因此它们可以成为无功能的函数;它们存在的唯一原因是由于默认操作而导致内核无法杀死整个进程等于pselect或其他。)

    在该测试程序的假设情况下,信号处理线程将通过调用pselect来响应sigwaitinfo,这将唤醒侦听器线程,或者将导致侦听器线程不会在[ C0]放在首位。

    在多线程程序中,sigwaitinfo向调用SIGUSR1的线程发送信号。您需要使用SIGUSR1sem_post(&sem)


1
投票

在使用glibc的系统上,signal安装一个信号处理程序,该信号处理程序执行not中断阻止系统调用。要获得能够执行此操作的信号处理程序,您需要使用sigaction并将sa_flags设置为没有包含SA_RESTART的值。

((大卫·施瓦茨的答案也是正确的。)

© www.soinside.com 2019 - 2024. All rights reserved.