Почему sem_wait не разблокирует (и возвращает -1) прерывания? - PullRequest
6 голосов
/ 27 апреля 2020

У меня есть программа, использующая sem_wait. Спецификация Posix гласит:

Функция sem_wait() прерывается при доставке сигнала.

Кроме того, в разделе об ошибках он говорит:

[EINTR] - сигнал прервал эту функцию.

Однако в моей программе отправка сигнала не разблокирует вызов (и не возвращает -1 как указано в spe c).

Ниже приведен минимальный пример. Эта программа зависает и 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, затем зависает.

Есть еще один вопрос здесь об этом, но на самом деле это не дает никакой ясности.

Неужели я не понимаю, что говорит спец 1033 *? К вашему сведению, мой компьютер использует Ubuntu GLIB C 2.31-0ubuntu9.

Ответы [ 2 ]

9 голосов
/ 27 апреля 2020

Существует три причины, по которым эта программа работает не так, как вы ожидаете, только две из них исправимы.

  1. Как указано в ответе Дэвида Шварца, в многопоточном Программа raise отправляет сигнал в поток, который вызывает raise.

    Чтобы получить сигнал, отправленный в нужный поток, в этой тестовой программе , измените raise(SIGUSR1) до pthread_kill(thread, SIGUSR1). Однако, если вы хотите, чтобы указанный поток c обрабатывал SIGUSR1 при отправке во весь процесс , вам нужно использовать pthread_sigmask, чтобы заблокировать SIGUSR1 во всех потоках. кроме того, который должен с этим справиться. (Подробнее об этом см. Ниже.)

  2. В системах, использующих glib c, 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 вызовы в main перед созданием каких-либо потоков, просто чтобы убедиться, что обработчики сигналов активны до того, как могут возникнуть какие-либо сигналы.

  3. Сигнал может быть доставлен в поток до поток может вызвать sem_wait. Если это произойдет, обработчик сигнала будет вызван и возвращен, а затем будет вызван sem_wait, и он будет заблокирован навсегда. В этой тестовой программе вы можете сделать это произвольно маловероятным, увеличив длину sleep в main, но невозможно сделать невозможным . Это неустранимая причина.

    Существует небольшое количество системных вызовов, которые атомно разблокируют сигналы во время сна, а затем снова блокируют их перед возвратом в пространство пользователя, например sigsuspend, sigwaitinfo и pselect. Это системные вызовы only , для которых можно избежать этого состояния гонки.

    Лучшая практика для многопоточной программы, которая имеет дело с сигналами, - выделять один поток для обработки сигналов. Чтобы это работало надежно, вы должны заблокировать все сигналы, кроме синхронных исключений ЦП (SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGSYS и SIGTRAP) в самом начале из main, до создания каких-либо потоков. Затем вы устанавливаете обработчик сигналов бездействия ( с SA_RESTART) для сигналов, которые вы хотите обрабатывать; они никогда не будут вызываться, их цель - не дать ядру убить процесс из-за действия по умолчанию SIGUSR1 или чего-либо еще. Набор сигналов, которые вам нужны, должен включать все сигналы для пользовательских прерываний: SIGHUP, SIGINT, SIGPWR, SIGQUIT, SIGTERM, SIGTSTP, SIGXCPU, SIGXFSZ. Наконец, вы создаете поток обработки сигналов, который зацикливает вызов sigwaitinfo для соответствующего набора сигналов и отправляет сообщения остальным потокам, используя каналы или переменные условия или что угодно, кроме , но сигналов в действительности. Этот поток никогда не должен блокироваться ни в каком системном вызове, кроме sigwaitinfo.

    . В случае этой тестовой программы поток обработки сигналов будет отвечать на SIGUSR1, вызывая sem_post(&sem). Это либо пробудит поток слушателя, либо приведет к тому, что поток слушателя не будет заблокирован на sem_wait.

4 голосов
/ 27 апреля 2020

В многопоточной программе raise отправляет сигнал в поток, который вызывает raise. Вам нужно использовать kill(getpid(), ...) или pthread_signal(thread, ...).

...