Обработчик сигнала не обрабатывает сигнал - PullRequest
0 голосов
/ 12 ноября 2018

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

Дочерний процесс отправляет сигнал SIGUSR1 родителю через произвольные интервалы. Чтобы создать эти интервалы, я решил усыпить ребенка.

При получении первого сигнала родитель пропускает оператор печати. При получении другого сигнала SIGUSR1 он снова начинает печатать. И так продолжается до тех пор, пока не будет отправлено случайное количество сигналов. Тогда он игнорирует пять сигналов. После игнорирования пяти сигналов родительский процесс печатает количество полученных сигналов и выходит после получения еще одного сигнала.

Моя проблема в том, что программа аварийно завершает работу при отправке сигналов от дочернего процесса. У родительского процесса есть обработчики сигналов, но они никогда не используются. Какую ошибку я делаю?

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

void checkArguments(int nbrOfArg);
static void sig_exit(int signo);
static void sig_handler(int signo);

int sigcount, n;

int main(int argc, char *argv[]){
    int interval, i, linecount = 1;
    char buffer[1024];
    FILE *f;
    sigcount = 0;
    signal(SIGUSR1, sig_handler);
    srand(time(NULL)); // generate random numbers   
    n = rand() % 20 + 10;
    if(fork() != 0){ //parent process
        printf("%d\n", getpid()); //debugging
        checkArguments(argc);
        f = fopen(argv[1], "r"); //open file
        if(f == NULL){
            printf("Error opening file\n");
            exit(EXIT_FAILURE);
        }
        if(sigcount < n){ //n is random number of signals sent that starts/stops printing
            signal(SIGUSR1, sig_handler); //set signal handler
            do {
                while(!feof(f)){// infinite printing loop
                    fgets(buffer, 1024, f);
                    if(sigcount % 2 == 0){ //stop printing at odd number of signals received
                        printf("%d %s", linecount, buffer);
                    }   
                    linecount++;
                }
                rewind(f);
                linecount = 1;  
            } while(linecount == 1); //infinite loop
        } else if(sigcount < (n + 6)) {
            signal(SIGUSR1, SIG_IGN);   // ignore signal
        } else {
            printf("%d signals received", sigcount);
            signal(SIGUSR1, sig_exit); // terminate parent process
        }   
    } else {
        for(i = 0; i < n; i++){
            interval = rand() % 10 + 1; //send signals at random interval
            sigcount++; //number of signals sent
            printf("Sending signal %d from %d to %d\n", sigcount, getpid(), getppid()); //debugging
            kill(getppid(), SIGUSR1); //send signal
            printf("Child sleeping for %d seconds\n", interval); //debugging
            sleep(interval); //let child sleep after sending signal
        }
    }
}

/** Checks so command line argument is valid **/
void checkArguments(int nbrOfArg){
    int k;
    if(nbrOfArg != 2){
        printf("Wrong number of arguments");
        exit(-1);
    }
}

void sig_handler(int signo){
    if(signo == SIGUSR1){
        printf("Receivied SIGUSR1 signal\n");   
    } else printf("Error: received undefined signal\n");

}

void sig_exit(int signo){
    if(signo == SIGUSR1){
        printf("Received SIGUSR1 signal\n");
        exit(SIGUSR1);  
    } else printf("Error: received undefined signal\n");
}

Ответы [ 2 ]

0 голосов
/ 12 ноября 2018

Различные системы ведут себя по-разному, используя signal(). На Mac под управлением macOS (10.14.1 Mojave, но это также относится и к другим версиям) оригинальный код, использующий signal(), работает нормально - есть множество ошибок, но обработка сигналов работает. В виртуальной машине под управлением Ubuntu 18.04 LTS (размещенной на том же Mac) код, использующий signal(), не работает должным образом, поскольку (как отмечено в комментариях ) обработка сигнала сбрасывается на значение по умолчанию, если сигнал перехватывается до ввода обработчика сигнала. Это устанавливает условия гонки. Оба эти различных поведения соответствуют стандарту C - macOS обеспечивает «надежные сигналы», а Linux - нет.

Однако еще не все потеряно; Linux и macOS имеют sigaction(), что обеспечивает отличный контроль - и может использоваться для имитации любого набора поведения signal(). См. Также В чем разница между sigaction() и signal()?

Существуют и другие проблемы, которые необходимо решить. Вы должны проверить аргументы и (убедитесь, что можете) открыть файл перед разветвлением; Вы должны сообщить об ошибке. Ребенок должен отметить PID своего исходного родителя, чтобы в случае смерти родителя он мог попытаться отправить ему сигнал и получить уведомление о том, что произошел сбой. Когда родительский родитель умирает, родительский PID переключается на PID 1, процесс init, который в основном заканчивает тем, что игнорирует сигнал.

Я исправил проблему с feof() - нет смысла использовать feof() в условиях контроля цикла (см. while (!feof(file)) всегда неправильно! ). Вместо этого протестируйте основную функцию ввода / вывода. (Библиотека C в Ubuntu помечает функцию fgets(), поэтому необходимо использовать возвращаемое значение. Обратите внимание на предупреждения компилятора.)

Приведенный ниже код замедляет основной цикл печати, поэтому он обрабатывает 4 строки в секунду, а не работает с полным наклоном. MacOS и Linux имеют nanosleep(); Linux не сделал доступным usleep(), хотя он имеет более простой (но менее мощный) интерфейс.

Приведенный ниже код отделяет sigcount, используемый родителем, от i, используемого ребенком для подсчета полученных и отправленных сигналов. Я также предположил поддержку C99 или более поздних версий и переместил объявления переменных ближе к месту их использования.

Как написано, код никогда не входит в состояние, в котором SIGUSR1 игнорируется, не говоря уже о состоянии, в котором он вызывает ошибку. Дочерний элемент (сейчас) отправляет достаточное количество сигналов (n *= 2; отправляет вдвое больше сигналов, чем ожидает «родитель»), но родитель все еще застрял в исходном коде для if (sigcount < n). Обратите внимание, что вы не можете считать полученные сигналы, если игнорируете эти сигналы. Эта часть кода нуждается в серьезной переработке. Вы должны выйти из цикла чтения файлов, когда вы получили соответствующее количество сигналов, а затем просто сосчитать следующие пять сигналов (не игнорировать их), а затем установить обработчик sig_exit. Это не реализовано в приведенном ниже коде.

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

#define _XOPEN_SOURCE 700 позволяет определять функции POSIX и т. Д., Даже если для параметров компиляции требуется -std=c11 (что отключает большинство расширений). Исходный код программы был sig41.c, поэтому программа была sig41. Код правильно компилируется с помощью (GCC 8.2.0):

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes \
>     sig41.c -o sig41
$

Наряду с некоторыми другими незначительными изменениями (например, сообщая об ошибках на stderr), этот код работает как в Ubuntu, так и в macOS:

#define _XOPEN_SOURCE 700
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

static void sig_exit(int signo);
static void sig_handler(int signo);

static int sigcount, n;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s filename\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    FILE *f = fopen(argv[1], "r");
    if (f == NULL)
    {
        fprintf(stderr, "Error opening file %s for reading\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    struct sigaction sa = { 0 };
    sa.sa_handler = sig_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGUSR1, &sa, 0);

    srand(time(NULL));
    n = rand() % 20 + 10;
    int pid = fork();
    if (pid < 0)
    {
        fprintf(stderr, "failed to fork!\n");
        exit(EXIT_FAILURE);
    }
    else if (pid != 0)
    {
        printf("%d\n", getpid());
        if (sigcount < n)
        {
            int linecount = 1;
            while (linecount == 1)
            {
                char buffer[1024];
                while (fgets(buffer, 1024, f))
                {
                    if (sigcount % 2 == 0)
                    {
                        printf("%d %s", linecount, buffer);
                    }
                    linecount++;
                    // nap time = quarter second
                    struct timespec nap = { .tv_sec = 0, .tv_nsec = 250000000 };
                    nanosleep(&nap, NULL);
                }
                rewind(f);
                linecount = 1;
            }
        }
        else if (sigcount < (n + 6))
        {
            printf("Going into SIG_IGN mode\n");
            sa.sa_handler = SIG_IGN;
            sigaction(SIGUSR1, &sa, 0);
        }
        else
        {
            printf("%d of %d signals received - sig_exit mode\n", sigcount, n);
            sa.sa_handler = sig_exit;
            sigaction(SIGUSR1, &sa, 0);
        }
    }
    else
    {
        fclose(f);
        int pid = getpid();
        int ppid = getppid();
        n *= 2;                     // Child needs to send more signals
        for (int i = 0; i < n; i++)
        {
            int interval = rand() % 10 + 1;
            printf("Sending signal %d of %d from %d to %d\n", i + 1, n, pid, ppid);
            if (kill(ppid, SIGUSR1) != 0)
            {
                fprintf(stderr, "Child failed to signal parent - exiting\n");
                exit(1);
            }
            printf("Child sleeping for %d seconds\n", interval);
            sleep(interval);
        }
    }
}

static void sig_handler(int signo)
{
    sigcount++;
    if (signo == SIGUSR1)
        printf("Received SIGUSR1 signal %d of %d\n", sigcount, n);
    else
        printf("Error: received undefined signal\n");
}

static void sig_exit(int signo)
{
    if (signo == SIGUSR1)
    {
        fprintf(stderr, "Received SIGUSR1 signal\n");
        exit(SIGUSR1);
    }
    else
        printf("Error: received undefined signal\n");
}

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

$ ./sig41 sig41.c
3247
1 #define _XOPEN_SOURCE 700
Sending signal 1 of 30 from 3248 to 3247
Child sleeping for 7 seconds
Received SIGUSR1 signal 1 of 15
Sending signal 2 of 30 from 3248 to 3247
Child sleeping for 7 seconds
Received SIGUSR1 signal 2 of 15
30     sa.sa_flags = 0;
31     sigemptyset(&sa.sa_mask);
…
56                     }
57                     linecount++;
Sending signal 3 of 30 from 3248 to 3247
Child sleeping for 1 seconds
Received SIGUSR1 signal 3 of 15
Sending signal 4 of 30 from 3248 to 3247
Child sleeping for 4 seconds
Received SIGUSR1 signal 4 of 15
62                 rewind(f);
63                 linecount = 1;
…
76             sigaction(SIGUSR1, &sa, 0);
77         }
Sending signal 5 of 30 from 3248 to 3247
Child sleeping for 2 seconds
Received SIGUSR1 signal 5 of 15
Sending signal 6 of 30 from 3248 to 3247
Child sleeping for 3 seconds
Received SIGUSR1 signal 6 of 15
86         {
87             int interval = rand() % 10 + 1;
…
96         }
97     }
Sending signal 7 of 30 from 3248 to 3247
Child sleeping for 7 seconds
Received SIGUSR1 signal 7 of 15
Sending signal 8 of 30 from 3248 to 3247
Child sleeping for 10 seconds
Received SIGUSR1 signal 8 of 15
8 static void sig_exit(int signo);
9 static void sig_handler(int signo);
…
46         {
47             int linecount = 1;
Sending signal 9 of 30 from 3248 to 3247
Child sleeping for 5 seconds
Received SIGUSR1 signal 9 of 15
Sending signal 10 of 30 from 3248 to 3247
Child sleeping for 8 seconds
Received SIGUSR1 signal 10 of 15
68             printf("Going into SIG_IGN mode\n");
69             sa.sa_handler = SIG_IGN;
…
98 }
99 
Sending signal 11 of 30 from 3248 to 3247
Child sleeping for 9 seconds
Received SIGUSR1 signal 11 of 15
Sending signal 12 of 30 from 3248 to 3247
Child sleeping for 4 seconds
Received SIGUSR1 signal 12 of 15
18         exit(EXIT_FAILURE);
19     }
…
32     sigaction(SIGUSR1, &sa, 0);
33 
Sending signal 13 of 30 from 3248 to 3247
Child sleeping for 6 seconds
Received SIGUSR1 signal 13 of 15
Sending signal 14 of 30 from 3248 to 3247
Child sleeping for 6 seconds
Received SIGUSR1 signal 14 of 15
58                     // nap time = quarter second
59                     struct timespec nap = { .tv_sec = 0, .tv_nsec = 250000000 };
…
80     {
81         fclose(f);
Sending signal 15 of 30 from 3248 to 3247
Child sleeping for 7 seconds
Received SIGUSR1 signal 15 of 15
Sending signal 16 of 30 from 3248 to 3247
Child sleeping for 8 seconds
Received SIGUSR1 signal 16 of 15
110 {
111     if (signo == SIGUSR1)
…
22     if (f == NULL)
23     {
Sending signal 17 of 30 from 3248 to 3247
Child sleeping for 1 seconds
Received SIGUSR1 signal 17 of 15
Sending signal 18 of 30 from 3248 to 3247
Child sleeping for 6 seconds
Received SIGUSR1 signal 18 of 15
28     struct sigaction sa = { 0 };
29     sa.sa_handler = sig_handler;
…
^C
$

Если вы работаете с выходными данными, вы можете увидеть, что выходные данные останавливаются для заданного количества строк - если дочерний элемент спит в течение 1 секунды, 4 выходные строки опускаются.

0 голосов
/ 12 ноября 2018

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

         fork()
      /         \
 Parent          Child
 CheckArguments  Send signal     
 Open file
 Create Handler

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

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

void sig_handler(int signo){
    if(signo == SIGUSR1)
    {
        printf("Receivied SIGUSR1 signal\n");
    } 
    else {
        printf("Error: received undefined signal\n");
    }
    signal(SIGUSR1, sig_handler);
}

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

...