Ловля SIGTERM и сон не позволяют ему работать

У меня есть код, написанный на C (работает на Ubuntu 17):

void sig_stop(int sig_num) {
    /* Some cleanup that needs to be done */
}

void some_routine(const char *array[], const int length) {
    /* Initialization */
    signal(SIGTERM, sig_stop);

    while (true) {
        /* Some function */

        /* I have this sleep to minimize the load on the CPU 
            as I don't need to check the conditions here 
            all the time. */
        sleep(5);
    }
}

Всякий раз, когда я включаю 5-минутный сон (sleep(5)), кажется, что sig_stop не вызывается. Однако, когда я комментирую sleep(5), очистка sig_stop работает просто отлично. У меня что-то не так с пониманием того, как поймать SIGTERM?

Если я не могу использовать функцию sleep, есть ли лучший способ «усыпить» программу, чтобы она запускала цикл только каждые x минут или таким образом, чтобы минимизировать нагрузку на ЦП?


person Athena    schedule 22.07.2018    source источник


Ответы (1)


sleep() и сигналы

sleep() не должен препятствовать перехвату сигнала и выполнению обработчика сигнала. Из справочной страницы для sleep() (выделено мной):

sleep() заставляет вызывающий поток засыпать либо до тех пор, пока не истечет количество секунд реального времени, указанное в секундах, либо пока не прибудет сигнал, который не будет проигнорирован.

Возьмем следующий пример...

#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

static volatile sig_atomic_t flag = 0;

static void sig_stop(int signum) { flag = 1; }

int main(void) {
    int secs_remaining = 0;
    signal(SIGTERM, sig_stop);

    while (!flag) {
        printf("Sleeping at time %d\n", time(NULL));
        secs_remaining = sleep(5);
    }
    printf(
        "Flag raised. Exiting at time %d. sleep() was interrupted %d seconds "
        "early ...\n",
        time(NULL), secs_remaining);

    return 0;
}

Обратите внимание, что - в случае, когда он был прерван сигналом - sleep() возвращает количество секунд, оставшихся для сна. Например, если он будет прерван на 3 секунды раньше, он вернет 3. Он вернет 0, если он не прерван.

Скомпилируйте как gcc -o test test.c и запустите. Затем с другого терминала запустите

pkill -15 test

Вы увидите вывод, похожий на следующий...

Время сна 1532273709
Флаг поднят. Выход в момент времени 1532273711. Sleep() был прерван на 2 секунды раньше...

Кстати... sleep(x) спит x секунд, а не минут.

signal() vs sigaction()

Из-за проблем с переносимостью, связанных с signal(), вместо этого часто рекомендуется использовать sigaction(). Использование sigaction() будет примерно следующим.

int main(void) {
    struct sigaction sa;

    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = sig_stop;
    if (sigaction(SIGTERM, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }
    // Etc.
}

Как видите, использование sigaction() немного более многословно, чем использование signal(). Возможно, поэтому люди до сих пор иногда используют signal().

person David Collins    schedule 22.07.2018
comment
Мои извинения за ошибку сна, глупая ошибка. Я могу отлично запустить ваш test.c, и он ведет себя так, как вы сказали (и имеет большой смысл). Однако, когда я пытаюсь выполнить тот же pkill -15 в своей собственной программе, он выдает сообщение: pkill: kill pid 45 failed: операция не разрешена, и с префиксом sudo он выполняется, но не уничтожает программу. - person Athena; 22.07.2018
comment
@Athena: SIGTERM действительно обычно завершает программу. Однако при использовании signal() для установки пользовательского обработчика сигналов поведение по умолчанию переопределяется. Если вы хотите выйти из программы, вам нужно установить флаг/переменную в обработчике сигналов и отслеживать это в цикле while(). - person David Collins; 22.07.2018
comment
После того, как я изменил условие while(true) на while(!flag), как вы рекомендовали, все работает отлично. - person Athena; 22.07.2018
comment
@Афина: Круто. Кроме того ... я обновил свой ответ, упомянув sigaction() (который является рекомендуемой альтернативой signal().) - person David Collins; 22.07.2018
comment
@Athena Учитывая static int flag;, while (!flag) не обязательно увидит изменение на flag=1 в static void sig_stop(int signum) { flag = 1; }. Любой компилятор может преобразовать while(!flag)... в бесконечный цикл, поскольку flag начинается со значения 0 и никогда не меняет значение в main. В данном случае flag должно быть volatile, а еще лучше sig_atomic_t. См. port70.net/~nsz/c/c11/n1570.html#7.14. - person Andrew Henle; 22.07.2018