Что происходит во время этой программы обработки сигналов?

void main ( )
{   int x;
    signal (SIGUSR1, f);
    x= fork ( );
    if (x == -1) exit (1);
    if (x != 0) 
    {   kill (x, SIGUSR1) ;
        sleep (2);
        exit (0);
    }
}
void f ( )
{
    printf ("signal received");
    exit (0);
}

Я думаю, что программа выше просит систему запустить функцию f (которая отображает "принятый сигнал" ), когда сигнал SIGUSR1 принимается родительским процессом. но я не уверен в этом, пожалуйста, не стесняйтесь исправить или дать более подробную информацию. Спасибо за помощь!

Ответ 1

В коде есть некоторые ошибки:

  • Избегайте вызова функции printf( ) в обработчике сигналов. SIGNAL(7) руководство предоставляет список разрешенных функций, вызывающих их, безопасно внутри обработчиков сигналов. Читайте:

    Функции безопасности с использованием Async

    Функция обработчика сигнала должна быть очень осторожной, поскольку обработка    в другом месте может быть прервано в какой-то произвольной точке исполнения    программы. POSIX имеет понятие "безопасная функция". Если    сигнал прерывает выполнение небезопасной функции, а обработчик    вызывает небезопасную функцию, тогда поведение программы     undefined.

  • Использовать тип возврата main() int; прочитайте "Что должно main() вернуться в C?"

  • x должен быть pid_t. (Идентификация процесса).

Теперь давайте предположим, что ваша программа компилируется и запускается (не прерывается никаким другим сигналом при выполнении обработчика): Я просто отступаю от вашего кода и переключая определение функции f() до main, потому что отсутствует объявление функции, а также добавление некоторых комментариев, которые вы должны прочитать:

 
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void f( )
{
    printf ("signal received\n");
    exit (0);//if child receives signal then child exits from here
}            // ******* mostly happens
int main ( )
{   int x;
    signal (SIGUSR1, f);// First: handler registered
    x = fork ( );       // Second: Child created, now child will execute in
    if (x == -1)        // parallel with parent 
      exit (1);
    if (x != 0) {// parent           
        kill(x, SIGUSR1) ; // Third: sends signal to child
        sleep(2);          // Forth: parent sleep
    }
    return 0; // parent always exits  
    // Child can exits from here **** 
    // if signal sent from parent delayed
}

В функции main() вы регистрируете функцию f() для сигнала SIGUSR1, после чего вызывает fork(), чтобы создать новый процесс. Во время выполнения функции fork() возвращается дочерний процесс, который запускается параллельно с родительским процессом.
Поскольку я вижу ваш код, я думаю, что вы понимаете, что дочерний процесс - это копирование родительского процесса, за исключением того, что значения переменных могут отличаться от возвращаемой точки fork() и, следовательно, x отличается в дочернем и родительском процессах. Мы можем использовать возвращаемое значение из fork, чтобы определить, запущена ли программа в родительском процессе или в дочернем. Но учтите, что это не родительский, а фактически дочерний процесс получает сигнал SIGUSR1. Значение идентификатора самого процесса всегда 0 для любого процесса. Вы проверяете возвращаемое значение x = fork(), которое является pid вновь созданного дочернего процесса, в дочернем процессе значение x равно 0 и в родительском x != 0. Следовательно, сигнал отправляется из родительского процесса в дочерний процесс.

Ваши комментарии:

Я думаю, что программа выше просит систему запустить функцию f( ) (которая отображает "signal received"), когда сигнал SIGUSR1 принимается родительским процессом.

У меня создалось впечатление, что вы не считаете, что оба процесса выполняются одновременно, и "может случиться так, что вскоре после fork() создайте дочерний процесс, запуск дочернего процесса и его немедленное завершение до того, как родительский процесс сможет отправить сигнал для ребенка (или дочерний процесс может принимать сигнал)". В этом случае функция f() никогда не получит возможность выполнить, а printf в обработчике сигналов никогда не будет печататься.

Но возможность того, что я описал выше, очень низок, потому что fork требует времени для создания нового процесса. И даже если вы снова и снова выполняете код, большинство сигналов, передаваемых из родительского процесса, будут выполнять обработчик сигналов.

Каков правильный способ написания этого кода?

Код xc. Правильный способ - установить флаг, который указывает, что обработчик сигнала выполнен, и затем вызвать функцию printf на основе значения флага вне обработчика сигнала, как я описал в своем ответе: Как избежать использования printf в обработчике сигналов? И причина, по которой это объясняется Джонатаном Леффлером в его ответить.

 
#define _POSIX_SOURCE 
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
volatile sig_atomic_t flag = 0; //initially flag == 0
void f(){
    flag = 1; // flag is one if handler executes
}
int main(void){   
  pid_t x;
  (void) signal (SIGUSR1, f);
  x = fork();
  if(x == -1) 
    exit(EXIT_FAILURE);
  if (x != 0){// parent 
    kill(x, SIGUSR1);
    sleep(1);
  }
  if(flag)//print only if signal caught and flag == 1
    printf("%s signal received \n", x == 0 ? "Child:" : "Parent:");
  return EXIT_SUCCESS;
}

Скомпилируйте его и выполните:

@:~$ gcc -Wall  -pedantic -std=c99  x.c  -o x
@:~$ ./x
Child: signal received 
@:~$ 

Обратите внимание, что дочерний процесс печатает, потому что родительский посылает сигнал дочернему устройству (но родительский процесс не печатает как отсутствие сигнала в родительском элементе). Таким образом, поведение вышеуказанного кода по-прежнему похоже на то, что вы получали в своем коде. Ниже я добавил еще один пример, в котором я пытаюсь продемонстрировать, что "одновременное выполнение результатов процессов отличается в разных экземплярах исполнения" (читать комментарии).

// include header files...
volatile sig_atomic_t flag = 0;
void f(){
    flag = 1;
}
int main(void){   
  pid_t x;
  (void) signal (SIGUSR1, f);
  (void) signal (SIGUSR2, f); // added one more signal
  x= fork ( );
  if(x == -1) 
    exit(EXIT_FAILURE);
  if (x != 0){// parent 
    kill(x, SIGUSR1);
    while(!flag); // in loop until flag == 0
  }
  if (x == 0){//child 
    kill(getppid(), SIGUSR2); // send signal to parent 
    while(!flag); // in loop until flag == 0
  }//  ^^^^ loop terminates just after signal-handler sets `flag` 
  if(flag)
    printf("%s signal received \n", x == 0 ? "Child:" : "Parent:"); 
  return EXIT_SUCCESS;
}

В приведенном выше коде два сигнала регистрируются как в родительском, так и в дочернем процессах. Родительский процесс не спит, а занят в цикле while, пока сигнал не установит флаг. Аналогично, у дочернего процесса есть цикл, который прерывается, когда флаг становится 1 в обработчике сигналов. Теперь скомпилируйте этот код и повторите его. Я часто пытался получить следующий вывод в своей системе.

@:~$ gcc -Wall  -pedantic -std=c99  x.c  -o x
@:~$ ./x
Child: signal received 
Parent: signal received 
@:~$ ./x
Child: signal received 
Parent: signal received 
@:~$ ./x
Child: signal received  
Parent: signal received 
@:~$ ./x
Parent: signal received   // <------
@:~$ Child: signal received 
./x
Child: signal received 
Parent: signal received 
@:~$ ./x
Parent: signal received   // <------
@:~$ Child: signal received 

@:~$ 

Отметьте вывод, в одном случае: "пока дочерний процесс не создаст родительский переданный сигнал и не войдет во время цикла, и когда дочерний процесс получит шанс выполнить (в зависимости от планирования процессора), он отправляет сигнал родителям и перед родительским процессом получить шанс на выполнение дочернего процесса, получает сигнал и выводит сообщение". Но иногда бывает так, что перед печатью printf print; родитель получает и печатает сообщение (это я помечен с помощью стрелки).

В последнем примере я пытаюсь показать выполнение дочернего процесса параллельно с родительским процессом, и вывод может отличаться, если вы не применяете механизм управления concurrency.

Некоторый хороший ресурс Чтобы узнать сигналы (1) Библиотека GNU C: Обработка сигналов (2) Стандарт кодирования CERT C 11. Сигналы (SIG).

Ответ 2

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

Вы можете позвонить, например. pause в дочернем элементе.

Ответ 3

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

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <bits/signum.h>
void f ( );

int main ( )
{   int x;
    signal (SIGUSR1, f);
    x= fork ( );
    if (x == -1) exit (1);
    if (x != 0)
{   kill (x, SIGUSR1) ;
        sleep (2);
        exit (0);
    }
}
void f ( )
{
    printf ("signal received\n");
    exit (0);
}

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

BTW: Я не очень опытен в C. Существует ряд комментариев, утверждающих, что использование printf() в дочернем процессе небезопасно. Зачем?? Детский процесс является дубликатом родительского объекта, включая виртуальное адресное пространство. Итак, почему printf() небезопасно?