У кого-нибудь есть пример не асинхронного безопасного обработчика сигнала? - PullRequest
0 голосов
/ 12 марта 2019

Прежде всего позвольте мне предсказать, что Я понимаю, почему не входящие функции могут вызвать взаимоблокировку в обработчике сигналов, однако я не могу вызвать проблему независимо от того, как сильно я стараюсь.

У меня есть моя первая программа, запускающая 1024 malloc и printfs каждый сигнал, и у меня есть несколько других программ, запущенных с 2 потоками на сигналы запуска программы в первый раз, и даже после запуска их в течение получаса подряд я не вижу тупиков.

Я собираю и запускаю эти программы на 64-битной Ubuntu 14.04.5 LTS (Trusty) с помощью gcc (Ubuntu 4.8.4-2ubuntu1 ~ 14.04.4) 4.8.4.

Первая программа (та, которая должнаdeadlock is) is:

// victim.c
#include  <signal.h>
#include  <unistd.h>
#include  <stdlib.h>
#include  <string.h>
#include  <stdio.h>
// global arr to put our malloc results to avoid
// compiler doing any funny business and optimizing
// away the malloc calls, not sure if this is really
// actually necessary or not
void *arr[1024];
// sigint handler to do bad stuff in a loop
void inthandler(int sig)
{
    int i = 0;
    for (i = 0; i < 1024; ++i) {
        // some printf
        printf("Signal loop %d\n", i);
        if (arr[i]) free(arr[i]);
        arr[i] = malloc(1024);
    }
}
void main(void)
{
    // clear out our arr
    memset(arr, 0, sizeof(arr));
    // install our sigint handler
    signal(SIGINT, inthandler);
    // loop and wait for signals
    while (1) {}
}

И я скомпилирую его с (O0, чтобы быть явно, что мы не оптимизируем):

gcc ./victim.c -O0 -o victim

Затем «убийца», то есть программа, отправляющаяСигналы, которые должны в конечном итоге вызвать тупик у жертвы, выглядят следующим образом:

// killer.c
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
// hack to grab pid of victim
static pid_t __grab_victim_pid()
{
    char line[1024] = {0};
    FILE *command = NULL;
    pid_t pid = 0;
    printf("Getting pid of victim...\n");
    do {
        command = popen("pidof victim", "r");
        memset(line, 0, sizeof(line));
        fgets(line, sizeof(line) - 1, command);
        pid = strtoul(line, NULL, 10);
        pclose(command);
    } while (pid == 0);
    printf("Grabbed pid of victim: [%u]\n", pid);
    return pid;
}
static void *__loop_threadfunc(void *param)
{
    pid_t pid = 0;
    size_t i = 0;
    pid = __grab_victim_pid();
    while (1) {
        kill(pid, SIGINT);
    }
    return 0;
}
int main(int argc, char *argv[])
{
    pthread_t thread1;
    pthread_t thread2;
    // Spawn the threads
    if (pthread_create(&thread1, NULL, __loop_threadfunc, NULL) != 0 ||
        pthread_create(&thread2, NULL, __loop_threadfunc, NULL) != 0) {
        fprintf(stderr, "Failed to create a thread\n");
        return 1;
    }
    // join the threads to wait for them
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    return 0;
}

Скомпилировано с:

gcc ./killer.c -O0 -o killer -lpthread

И затем я запускаю процесс жертвы в одном терминале, перепрыгиваю надругой терминал и запускает несколько фоновых процессов-убийц; естественно, процесс-жертва выплевывает множество строк для вывода из каждого полученного сигнала, но он никогда не блокируется ...

Кроме того, выпадающие линии всегдапо порядку, то есть сообщения «Цикл сигнала% d» никогда не прерываются, что указывает на то, что сигнал никогда не доставляется в середине выполнения активного обработчика сигнала.Кажется, это идет вразрез с тем, что все говорят об обработчиках сигналов.

Я что-то не так делаю?Мне просто очень повезло?Или, может быть, моя ОС защищена от этой проблемы (это вообще возможно)?

Я пытался прикрепить strace к жертве, и я вижу, что он всегда сообщает rt_sigreturn, а затем последующий SIGINT в паре:

rt_sigreturn()                          = 0
--- SIGINT {si_signo=SIGINT, si_code=SI_USER, si_pid=11564, si_uid=0} ---

Я мог бы представить, что "SIGINT" должен бытьдоставляется до rt_sigreturn (до того, как он существует из обработчика сигнала), но этого, кажется, никогда не происходит, он выглядит , как будто процесс блокирует SIGINT до тех пор, пока текущий обработчик сигнала не завершится ... (Это может 'не так ли, правда?)

Заранее спасибо, любые разъяснения по этому поводу были бы очень благодарны!

Edit1: я оставил 10 процессов-убийц, работающих на одном процессе-жертве, будет публиковать результаты, есличто-нибудь происходит.

Edit2: Может ли это иметь какое-либо отношение к тому факту, что я запускаю эти тесты на виртуальной машине?

Ответы [ 3 ]

2 голосов
/ 12 марта 2019

Вы не видите тупика или другого сбоя по двум причинам. Во-первых, и это самое главное, ваша программа находится в цикле вращения, ожидая доставки сигналов.

// loop and wait for signals
while (1) {}

Это означает, что обработчику сигнала никогда не может быть что-либо «интересное» для прерывания. Если вы изменили его на что-то вроде этого:

while (1) {
  size_t n = rand();
  char *p = malloc(n);
  free(p);
}

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

Другая причина заключается в том, что в вашей системе signal(SIGINT, handler) устанавливает обработчик, выполнение которого не может быть прервано другим SIGINT. Стандарт C не говорит, делает ли signal это или нет, но большинство современных Unix-ов так и делают. Вы можете получить обработчик сигнала, выполнение которого можно * прервать , опустившись на нижний уровень sigaction: замените ваш signal вызов на

struct sigaction sa;
sa.sa_handler = inthandler;
sa.sa_flags = SA_NODEFER | SA_RESTART;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, 0);

Это также включит возможность поступления сигнала в пределах кишки malloc.

Сигналы являются одним из самых запутанных и сложных аспектов низкоуровневого Unix API. Я призываю вас приобрести копию книги У. Ричарда Стивенса Расширенное программирование в среде Unix и прочитать главы, посвященные обработке сигналов. Это дорогая книга, но вы можете запросить ее в местной публичной библиотеке.

2 голосов
/ 12 марта 2019

Кажется, что вопрос сводится к биту в конце о том, может ли обработчик сигнала быть прерван при получении того же сигнала, который он уже обрабатывает.

Я что-то не так делаю? Мне просто очень повезло? Или, может быть, мой ОС защищена от этой проблемы (это вообще возможно)?

Я пытался прикрепить страус к жертве, и я вижу, что это всегда сообщает rt_sigreturn, а затем последующий SIGINT в паре:

rt_sigreturn () = 0 --- SIGINT {si_signo = SIGINT, si_code = SI_USER, si_pid = 11564, si_uid = 0} ---

Я полагаю, что "SIGINT" должен быть доставлен до rt_sigreturn (до того, как он существует из обработчика сигнала), но это никогда кажется, что происходит, как будто процесс блокирует SIGINT пока текущий обработчик сигнала не вышел ... (Это не может быть правильно может ли это?)

Фактически, полностью возможно, что SIGINT заблокирован, пока обрабатывается SIGINT. Страница руководства Linux для signal (2) содержит это предупреждение в начале описания функции:

Поведение signal () варьируется в зависимости от версии UNIX, а также исторически различался в разных версиях Linux. Избегайте его используйте: вместо этого используйте sigaction (2).

В примечаниях по переносимости описываются эти изменения в поведении программы после установки функции обработки сигналов через signal():

  • расположение сигнала сбрасывается на SIG_DFL, когда для него вызывается обработчик. И сигнал не был заблокирован. Это было оригинальное поведение UNIX signal(), также реализованного в System V.

  • расположение сигнала остается неизменным, когда для него вызывается обработчик, , и сигнал блокируется, пока обработчик выполняет . Это поведение, реализуемое BSD, которое также вызывает перезапуск определенных системных вызовов, если он прерывается при получении сигнала.

Вы, скорее всего, будете тестировать в системе, которая демонстрирует последнее, поскольку Mac OS является BSD, и хотя системный вызов signal() ядра Linux реализует семантику System V, функция-оболочка signal() GLIBC обеспечивает семантику BSD. Реализация сигналов и обработки сигналов в Windows слишком слаба для вашего тестирования, поэтому единственным вероятным источником семантики System V был бы потомок System V, такой как Solaris или HP-UX (и я не уверен в их поведении здесь). Конечно, существуют и другие операционные системы, но те, о которых я упоминал, охватывают подавляющее большинство установленной базы компьютеров общего назначения.

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

struct sigaction action = {
    .sa_handler = inthandler,
    .sa_flags = SA_NODEFER
};

int result = sigaction(SIGINT, &action, NULL);
1 голос
/ 12 марта 2019

Я понимаю, почему не входящие функции могут вызвать тупик в обработчике сигналов, однако я не могу вызвать проблему независимо от того, как сильно я стараюсь.

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

Более того, в Linux семантика signal фактически совпадает с семантикой BSD. Это означает, что во время работы обработчика сигнала последующие экземпляры того же сигнала блокируются. Все сигналы от вашего «убийцы» обрабатываются последовательно «жертвой».

Потенциальные проблемы с вызовом не реентерабельных функций более коварны, чем просто остановка в тупике. Например, если malloc прерывается сигналом, когда он находится в процессе манипулирования своими структурами данных, вызов malloc в обработчике сигнала аналогичен вызову его в поврежденной куче. Это не обязательно приведет к взаимоблокировке, вы можете просто обнаружить необъяснимое нарушение сегментации или просто испортить данные. Однако я должен подчеркнуть, что это не та ситуация, с которой вы сталкиваетесь, потому что вы вызываете только malloc в своем обработчике сигналов.

...