Как вы «буферизуете» UNIX сигналов - PullRequest
0 голосов
/ 07 января 2020

Как один буфер UNIX сигнал таким образом блокирует функции при вызове? В нашем программном обеспечении мы используем сокеты. Теперь мы хотим разблокировать / отменить вызов recv() через сигнал. Проблема в том, что если сигнал отправляется до ввода recv(), он теряется и recv() никогда не разблокируется.

Основная функция выглядит следующим образом:

bool signalHandlerSetup;
bool signalSent;

int main(int argc, char* argv[])
{
    pthread_t thread;
    signalHandlerSetup = false;
    signalSent = false;

    // Block the SIGUSR1 signal in the main thread
    sigset_t signalSet;
    sigemptyset(&signalSet);
    sigaddset(&signalSet, SIGUSR1);
    pthread_sigmask(SIG_BLOCK, &signalSet, NULL);

    // Setup the signal handler for all future threads
    struct sigaction signalAction;
    signalAction.sa_flags = 0;
    signalAction.sa_handler = [](int signalNumber) {};
    sigaction(SIGUSR1, &signalAction, NULL);

    pthread_create(&thread, NULL, secondaryThreadFunction, NULL);
    std::cout << "Started thread" << std::endl;

    // Wait until the signal handler is setup
    while (!signalHandlerSetup);
    pthread_kill(thread, SIGUSR1);

    signalSent = true;
    pthread_join(thread, NULL);
    std::cout << "Joined thread" << std::endl;
}

Вторичный поток просто создает сокет и пытается прочитать из него:

void* secondaryThreadFunction(void *arg)
{
    // Setup socket
    int internSocket = setupSocket();

    // Setup the signal handling
    sigset_t signalSet;
    sigemptyset(&signalSet);
    sigaddset(&signalSet, SIGUSR1);
    pthread_sigmask(SIG_UNBLOCK, &signalSet, NULL);
    signalHandlerSetup = true;

    while (!signalSent);

    char buffer;
    std::cout << "recv()..." << std::endl;
    ssize_t bytesRead = recv(internSocket, static_cast<void*>(&buffer), sizeof(buffer), 0);
    std::cout << "recv() canceled" << std::endl;

    close(internSocket);
}

Как вы можете видеть Сигнал явно отправляется до ввода функции recv(), чтобы проиллюстрировать проблему.

Помимо буферизации, для этого могут быть и другие решения, но у каждого из них есть недостатки:

  • Подождите, пока вторичный поток заблокирован (Заставляет основной поток блокировать тоже или занято l oop)
  • Постоянная отправка сигнала (Другое занято l oop)

РЕДАКТИРОВАТЬ: Сокет не должен быть закрыт только для разблокировки вторичного потока. Мы можем захотеть снова работать на том же сокете после разблокировки.

РЕДАКТИРОВАТЬ 2: Я смог решить проблему с помощью решения, предоставленного Мартином Джеймсом и Дарреном Смитом. Сейчас я использую функцию select() в сочетании с дескриптором файла событий. Для справки вот рабочее решение:

int eventDescriptor;

int main(int argc, char* argv[])
{
    pthread_t thread;

    // Create the event file descriptor
    eventDescriptor = eventfd(
        0,  // Initial value
        0   // Flags
    );
    if (eventDescriptor == -1)
    {
        std::cout << "Failed to create event file descriptor" << std::endl;
        return -1;
    }

    pthread_create(&thread, NULL, secondaryThreadFunction, NULL);
    std::cout << "Started thread" << std::endl;

    // Notify the event descriptor
    uint64_t valueToWrite = 1;
    if (write(eventDescriptor, &valueToWrite, sizeof(uint64_t)) == -1)
    {
        std::cout << "Failed to write to event file descriptor" << std::endl;
        return -1;
    }

    pthread_join(thread, NULL);
    std::cout << "Joined thread" << std::endl;

    close(eventDescriptor);

    return 0;
}

Вот вторичный поток:

void* secondaryThreadFunction(void *arg)
{
    // Setup socket
    int internSocket = setupSocket();

    // Set up the file descriptor set
    fd_set readFileDescriptorSet;
    FD_ZERO(&readFileDescriptorSet);
    FD_SET(internSocket, &readFileDescriptorSet);
    FD_SET(eventDescriptor, &readFileDescriptorSet);

    char buffer;
    std::cout << "select()..." << std::endl;
    int fileDescriptorsSet = select(std::max(internSocket, eventDescriptor) + 1, &readFileDescriptorSet, NULL, NULL, NULL);
    if (FD_ISSET(eventDescriptor, &readFileDescriptorSet))
    {
        std::cout << "select() canceled via event" << std::endl;
    }
    else if (FD_ISSET(internSocket, &readFileDescriptorSet))
    {
        std::cout << "select() canceled through socket" << std::endl;
        ssize_t bytesRead = recv(internSocket, static_cast<void*>(&buffer), sizeof(buffer), 0);
    }

    close(internSocket);
}

Ответы [ 3 ]

1 голос
/ 07 января 2020

Не пытайтесь "разблокировать recv". Вместо этого используйте неблокирующую recv и используйте ppoll для блокировки, которая предназначена для этой цели. (pselect также может использоваться)

Установите struct pollfd:

struct pollfd pollfd = {.fd = internSocket, .events = POLLIN};

Блокируйте сигнал, пока вы не будете готовы его получить:

sigset_t signalSet;
sigemptyset(&signalSet);
sigaddset(&signalSet, SIGUSR1);
pthread_sigmask(SIG_BLOCK, &signalSet, NULL);

Подготовьте набор сигналов с разблокированным сигналом:

pthread_sigmask(SIG_SETMASK /* ignored when second parameter is null */, NULL, &signalSet);
sigdelset(&signalSet, SIGUSR1);

Вызовите ppoll для блокировки:

int ppoll_result = ppoll(&pollfd, 1, NULL /* no timeout */, &signalSet);

Проверьте, не прервана ли ppoll сигнал, или есть ли у вас какие-либо данные:

if (ppoll_result < 0) {
    if (errno == EINTR) {
        // interrupted by signal
    } else {
        // error occurred
    }

} else {
    assert(ppoll_result == 1); // Should always be true, but it's a good idea to check anyway

    // call recv
}

Примечание: обычно при использовании poll / ppoll мы проверяем pollfd.events, чтобы увидеть, какие события вызвали его пробуждение, но это не требуется, так как вы ожидаете только одного сокета. При использовании poll / ppoll.

можно ожидать более одного сокета за раз. Примечание. Это не единственный способ ожидания сигнала с poll. Вы можете использовать signalfd, который «преобразует» сигналы в нечто, похожее на сокет.

1 голос
/ 07 января 2020

Вы можете рассмотреть возможность сделать следующее (аналогично предложению MartinJames в комментариях).

Перестройте ваш вторичный поток так, чтобы вместо выполнения прямых вызовов блокировки recv() вы заменяли его блокирующим вызовом на один из циклы событий на основе файлов (например, epoll или select)

Блокирующий вызов будет прослушивать события для двух описаний файлов:

(1 ) файловый дескриптор сокета (internSocket)

(2) новый файловый дескриптор, созданный eventfd()

В вашей основной функции вы создадите дескриптор файла событий, вызвав eventfd(). При поступлении сигнала запишите значение в дескриптор файла событий; это приведет к тому, что заблокированный поток выйдет из режима ожидания выбора.

Basi c пример:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/eventfd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

void write_to_eventfd(int fd) {
  /* we must write an 8 byte integet to the eventfd. */
  uint64_t u = 1;
  if (-1 == write(fd, &u, sizeof(uint64_t)))
    perror("write()");
}

int main()
{
  /* create event file descriptor */
  int efd = eventfd(0, 0);
  if (efd == -1)
    perror("eventfd()");

  /* For example purpose, do an immediate write; this causes select() to
   * immediately return (simulate signal arrive before socket read). Later
   * perform this in separate thread upon signal arrival. */
  write_to_eventfd(efd);

  /* Watch stdin (fd 0) to see when it has input.  Watch the eventfd for an
   * event.  Add other socket file descriptors etc. */
  fd_set rfds;
  FD_ZERO(&rfds);
  FD_SET(0, &rfds);
  FD_SET(efd, &rfds);

  /* Blocking read */
  int retval = select(efd+1, &rfds, NULL, NULL, NULL);

  if (retval == -1)
    perror("select()");

  if (FD_ISSET(efd, &rfds))
    printf("event!\n"); /* next: read the value from efd */

  if (FD_ISSET(0, &rfds))
    printf("some data on fd(0)\n");

  return EXIT_SUCCESS;
}

Также рассмотрите другие циклы событий.

0 голосов
/ 08 января 2020

Вы можете создать signalfd, который буферизует сигналы для вас, не нужно устанавливать обработчик сигналов. См. Пример в man signalfd.

. Вам нужно будет использовать select / poll / epoll событие l oop для ожидания сигнала или вашего сокета fd готовы к чтению и затем читают их в неблокирующем режиме до EAGAIN, как советуют другие ответы здесь.

...