C: select () - Прерывание сигнала - PullRequest
0 голосов
/ 01 февраля 2019

Я пишу многопоточную серверную программу на C, которая работает с сокетами AF_UNIX.Базовая структура сервера:

  • Основной поток инициализирует структуры данных и создает пул «рабочих» потоков.
  • Рабочие потоки начинают ожидать новых запросов в пустом потоке.безопасная очередь
  • Основной поток прослушивает различные сокеты (новое подключение и уже подключенные клиенты) с помощью вызова select().
    • select() показывает возможное чтение по сокету соединения: основной поток вызывает accept() и помещает возвращенный дескриптор файла в fd_set (набор чтения).
    • select() показывает возможное чтениена уже подключенных сокетах: основной поток удаляет готовые файловые дескрипторы из fd_set (набор чтения) и помещает их в потокобезопасную очередь.
  • Рабочий поток извлекает дескриптор файла изОчередь и начинает связываться со связанным клиентом для подачи запроса.В конце рабочего потока службы помещает дескриптор файла сокета обратно в fd_set (я полагаю, что функция делает эту операцию потокобезопасной) и возвращает ожидание в очереди нового запроса.

Эта процедура повторяется в бесконечном цикле, пока не будет поднят сигнал SIGINT.Другая функция должна быть выполнена на SIGUSR1 без выхода из цикла.

Я сомневаюсь в этом, потому что, если я вызову SIGINT, моя программа завершится с EINTR = Interrupted system call.Я знаю о вызове pselect() и трюке "self pipe", но не могу понять, как заставить вещи работать в многопоточной ситуации.

Я ищу (POSIX-совместимый) сигналуправление, которое предотвращает ошибку EINTR, пока основной поток ожидает pselect().

Я публикую некоторые фрагменты кода для пояснения:

Здесь я настраиваю обработчики сигналов (игнорировать errorConsolePrint function)

if(signal(SIGINT, &on_SIGINT) == SIG_ERR)
{
    errorConsolePrint("File: %s; Line: %d; ", "Setting SIGINT handler", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
}
if(signal(SIGTERM, &on_SIGINT) == SIG_ERR)
{
    errorConsolePrint("File: %s; Line: %d; ", "Setting SIGINT handler", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
}
if(signal(SIGUSR1, &on_SIGUSR1) == SIG_ERR)
{
    errorConsolePrint("File: %s; Line: %d; ", "Setting to SIGUSR1 handler", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
}

if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
{
    errorConsolePrint("File: %s; Line: %d; ", "Setting to ignore SIGPIPE", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
}

Здесь я настраиваю маску сигнала для pselect

sigemptyset(&mask);
sigemptyset(&saveMask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGPIPE);

Здесь я звоню pselect

test = saveSet(masterSet, &backUpSet, &saveMaxFd);
CHECK_MINUS1(test, "Server: creating master set's backup ");

int test = pselect(saveMaxFd+1, &backUpSet, NULL, NULL, &waiting, &mask);
if(test == -1 && errno != EINTR)
{
    ...error handling...
    continue;
}

Надеюсь на помощь!Спасибо всем заранее.

Ответы [ 4 ]

0 голосов
/ 02 февраля 2019

Считать сначала сигнал (7) и сигнал-безопасность (7) ;Возможно, вы захотите использовать специфичный для Linux signalfd (2) , поскольку он хорошо вписывается (для SIGTERM & SIGQUIT и SIGINT) в циклы событий вокруг poll (2) или старый select (2) (или более новый pselect или ppoll)

См. также этот ответ (и pipe (7) к упомянутому там самому трюку, который совместим с POSIX) к очень похожему вопросу.

Кроме того, сигнал (2) документы:

  The effects of signal() in a multithreaded process are unspecified.

, поэтому вы действительно должны использовать sigaction (2) (то есть POSIX).

0 голосов
/ 01 февраля 2019

Я бы предложил следующую стратегию:

  • Во время инициализации настройте обработчики сигналов, как и вы.
  • Во время инициализации блокируйте все (блокируемые) сигналы.См., Например, Можно ли игнорировать все сигналы? .
  • Используйте pselect в своем основном потоке, чтобы разблокировать потоки на время вызова, опять же, как вы делаете.

Это имеет то преимущество, что все ваших системных вызовов, включая все вызовы во всех ваших рабочих потоках, никогда не будут возвращать EINTR, за исключением одного pselectв основной теме.См., Например, ответы на Чрезмерно ли я чрезмерно проектирую блокировку сигналов для каждого потока? и pselect не возвращает сигнал при вызове из отдельного потока, но отлично работает в однопоточной программе .

Эта стратегия также будет работать с select: просто разблокируйте сигналы в главном потоке непосредственно перед вызовом select, а затем заблокируйте их снова.Вам действительно нужно pselect, чтобы предотвратить зависание, если ваш select тайм-аут длинный или бесконечный, и если ваши файловые дескрипторы в основном неактивны.(Я никогда не использовал pselect, работая в основном с более старыми Unix, у которых его не было.)

Я предполагаю, что ваши обработчики сигналов подходят: например, они просто атомарно устанавливают глобальную переменную.

Кстати, в вашем примере кода вам нужно sigaddset(&mask, SIGPIPE), так как SIGPIPE уже игнорируется?

0 голосов
/ 02 февраля 2019

Хорошо, наконец-то я нашел решение.

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

После этого я обнаружил другую проблему, которую решил, о устаревшем вызове signal().Во время выполнения, когда я в первый раз поднимаюсь на SIGUSR1, программа ловит и управляет ею, как и ожидалось, но во второй раз она завершается с User defined signal 1.

. Я выяснил, что signal() вызов установлен «один раз».обработчик для конкретного сигнала, после первой обработки сигнала поведение этого сигнала возвращает значение по умолчанию.

Итак, вот что я сделал:

Вот обработчики сигнала:

Примечание: я сбрасываю обработчик для SIGUSR1 внутри самого обработчика

static void on_SIGINT(int signum)
{
    if(signum == SIGINT || signum == SIGTERM)
        serverStop = TRUE;
}

static void on_SIGUSR1(int signum)
{
    if(signum == SIGUSR1)
        pendingSIGUSR1 = TRUE;

    if(signal(SIGUSR1, &on_SIGUSR1) == SIG_ERR)
        exit(EXIT_FAILURE);
}

Здесь я устанавливаю обработчики во время инициализации сервера:

 if(signal(SIGINT, &on_SIGINT) == SIG_ERR)
    exit(EXIT_FAILURE);
if(signal(SIGTERM, &on_SIGINT) == SIG_ERR)
    exit(EXIT_FAILURE);
if(signal(SIGUSR1, &on_SIGUSR1) == SIG_ERR)
    exit(EXIT_FAILURE);
if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
    exit(EXIT_FAILURE);

А вот цикл прослушивания сервера:

while(!serverStop)
{
    if (pendingSIGUSR1)
    {
        ... things i have to do on SIGUSR1...
        pendingSIGUSR1 = FALSE;
    }


    test = saveSet(masterSet, &backUpSet, &saveMaxFd);
    CHECK_MINUS1(test, "Server: creating master set's backup ");

    int test = select(saveMaxFd+1, &backUpSet, NULL, NULL, &waiting);
    if((test == -1 && errno == EINTR) || test == 0)
        continue;
    if (test == -1 && errno != EINTR)
    {
        perror("Server: Monitoring sockets: ");
        exit(EXIT_FAILURE);
    }

    for(int sock=3; sock <= saveMaxFd; sock++)
    {
        if (FD_ISSET(sock, &backUpSet))
        {
            if(sock == ConnectionSocket)
            {
                ClientSocket = accept(ConnectionSocket, NULL, 0);
                CHECK_MINUS1(ClientSocket, "Server: Accepting connection");

                test = INset(masterSet, ClientSocket);
                CHECK_MINUS1(test, "Server: Inserting new connection in master set: ");
            }
            else
            {
                test = OUTset(masterSet, sock);
                CHECK_MINUS1(test, "Server: Removing file descriptor from select ");
                test = insertRequest(chain, sock);
                CHECK_MINUS1(test, "Server: Inserting request in chain");
            }
         }
    }
}
0 голосов
/ 01 февраля 2019

Что вам, вероятно, следует сделать, это посвятить поток обработке сигналов.Вот эскиз:

В main, прежде чем создавать какие-либо потоки, заблокируйте все сигналы (используя pthread_sigmask), кроме SIGILL, SIGABRT, SIGFPE, SIGSEGV и SIGBUS.

Затем,порождайте ваш поток обработчика сигналов.Этот поток повторяет вызов sigwaitinfo для сигналов, которые вам нужны.Требуется любое действие, подходящее для каждого;это может включать в себя отправку сообщения в основной поток, чтобы инициировать чистое завершение работы (SIGINT), постановку в очередь «другой функции» для обработки в рабочем пуле (SIGUSR1) и т. д. Вы не устанавливаете обработчики дляэти сигналы.

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

...