Проверьте следующую программу-пример, example.c :
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
static inline const char *signal_name(const int signum)
{
switch (signum) {
case SIGINT: return "SIGINT";
case SIGHUP: return "SIGHUP";
case SIGTERM: return "SIGTERM";
case SIGQUIT: return "SIGQUIT";
case SIGUSR1: return "SIGUSR1";
case SIGUSR2: return "SIGUSR2";
default: return "(unnamed)";
}
}
int main(void)
{
sigset_t mask;
siginfo_t info;
pid_t child, p;
int signum;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGQUIT);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
fprintf(stderr, "Cannot block SIGUSR1: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
child = fork();
if (child == -1) {
fprintf(stderr, "Cannot fork a child process: %s.\n", strerror(errno));
return EXIT_FAILURE;
} else
if (!child) {
/* This is the child process. */
printf("Child process %d sleeping for 3 seconds ...\n", (int)getpid());
fflush(stdout);
sleep(3);
printf("Child process %d sending SIGUSR1 to parent process (%d) ...\n", (int)getpid(), (int)getppid());
fflush(stdout);
kill(getppid(), SIGUSR1);
printf("Child process %d exiting.\n", (int)getpid());
return EXIT_SUCCESS;
}
/* This is the parent process. */
printf("Parent process %d is waiting for signals.\n", (int)getpid());
fflush(stdout);
while (1) {
signum = sigwaitinfo(&mask, &info);
if (signum == -1) {
/* If some other signal was delivered to a handler installed
without SA_RESTART in sigaction flags, it will interrupt
slow calls like sigwaitinfo() with EINTR error. So, those
are not really errors. */
if (errno == EINTR)
continue;
printf("Parent process: sigwaitinfo() failed: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (info.si_pid == child)
printf("Parent process: Received signal %d (%s) from child process %d.\n", signum, signal_name(signum), (int)child);
else
if (info.si_pid)
printf("Parent process: Received signal %d (%s) from process %d.\n", signum, signal_name(signum), (int)info.si_pid);
else
printf("Parent process: Received signal %d (%s).\n", signum, signal_name(signum));
fflush(stdout);
/* Exit when SIGUSR1 received from child process. */
if (signum == SIGUSR1 && info.si_pid == child) {
printf("Parent process: Received SIGUSR1 from child.\n");
break;
}
/* Also exit if Ctrl+C pressed in terminal (SIGINT). */
if (signum == SIGINT && !info.si_pid) {
printf("Parent process: Ctrl+C pressed.\n");
break;
}
}
printf("Reaping child process...\n");
fflush(stdout);
do {
p = waitpid(child, NULL, 0);
if (p == -1) {
if (errno == EINTR)
continue;
printf("Parent process: waitpid() failed: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
} while (p != child);
printf("Done.\n");
return EXIT_SUCCESS;
}
Скомпилируйте и запустите ее, например,
gcc -Wall -O2 example.c -o example
./example
Вы можете увеличить время, в течение которого ребенокПроцесс спит, если вы хотите, чтобы извне ввести сигнал в родительский процесс.Если идентификатор родительского процесса, скажем, 24316, вы можете отправить его, например.сигнал SIGHUP через kill -HUP 24316
с другого терминала.Если вы запустите пример в интерактивном режиме, как показано выше, вы также можете заставить терминал отправлять сигнал SIGINT процессу, нажав Ctrl + C .
Наблюдения:
sigprocmask()
используется для блокировки интересных сигналов в родительском процессе до fork()
, чтобы родительский процесс мог перехватить сигнал.Это также означает, что сигналы заблокированы в дочернем процессе.
Если сигналы были заблокированы в родительском процессе после fork()
, то дочерний процесс мог бы отправитьсигнал до того, как родительский процесс готов его перехватить.В случае SIGUSR1 действие по умолчанию завершит родительский процесс. Функция
signal_name()
существует только для приятной печати имени сигнала.
Он помечен static inline
главным образом для того, чтобы разработчики-люди понимали, что это вспомогательная функция, видимая только в текущем модуле компиляции.Для компилятора static
говорит, что функция видима только в текущем модуле компиляции, а inline
говорит, что компилятор может свободно включать свою функциональность в тот, кто ее вызывает, вместо вызова именованной функции.
Возвращаемое значение const char *
, поскольку функция возвращает строковый литерал.
fork()
может вернуть -1 в случае ошибки.
fork()
возвращается дважды.В родительском процессе возвращаемое значение является положительным;идентификатор дочернего процесса.В дочернем процессе возвращаемое значение равно нулю.
Новый дочерний процесс по сути является копией снимка родительского процесса.Порядок, в котором они начинают выполняться, в основном случайный: они могут работать одновременно, оба одновременно;или один может бежать первым, а другой чуть позже.В наши дни компьютеры настолько быстры, что такие понятия, как «достаточно скоро», измеряемые в микросекундах, все же могут привести к ошибкам, поэтому нам нужно быть осторожными и понимать общую картину.Следовательно, ранняя установка маски сигнала.
Многие функции возвращают -1 или NULL в случае возникновения ошибки, а errno
указывает на ошибку.При написании кода вы всегда должны выполнять проверку ошибок.Они позволяют обнаруживать логические и функциональные ошибки при тестировании кода.В чрезвычайно редком случае, когда они «замедляют» что-либо, вы всегда можете удалить их после профилирования и тестирования.На практике они того стоят, каждый раз;если не для чего-то еще, для того, чтобы поймать неверные ожидания программиста.
См. man 2 sigaction
и man 7 signal
, чтобы увидеть, какие сигналы заполняютсякакие поля siginfo_t
и как вы можете определить, был ли сигнал отправлен другим процессом (через kill()
или sigqueue()
), вызван, вызван таймером POSIX и т. д.
См. Цикл while с waitpid()
о том, как получить дочерний процесс.Мы могли бы использовать второй параметр, указатель на int и WIFEXITED()
/ WEXITSTATUS()
и WIFSIGNALED()
/ WTERMSIG()
, чтобы проверить состояние выхода дочернего процесса.Я не стал беспокоиться, потому что дочерний процесс всегда возвращает EXIT_SUCCESS
, который в системах POSIXy равен 0.
Научиться проектировать и создавать программы из модульных частей, а не объединять все в одинPill, а затем попытаться разобраться.
Мы могли бы упростить понимание приведенного выше примера, если бы мы разделили дочерние операции процесса на отдельную функцию.
Однако разделение на функции не является целью: это всего лишь инструмент, используемый для того, чтобы сделать код максимально простым и легким для понимания и сопровождения.Мы, люди, обладаем ограниченными умственными способностями, но если мы фокусируем правильно, мы можем создавать удивительные вещи.
Хорошие комментарии по крайней мере так же важны, как и хороший код.
Комментарии, которые описывают, что делает код, бесполезны.Мы можем прочитать код, чтобы увидеть, что он делает.Код не сообщает нам: почему программист написал код: какова цель кода, какова логическая модель или алгоритм, который пытается реализовать код.
Естьтолько пять комментариев в примере программы.Этого недостаточно;но даже после пары десятилетий написания кода профессионально, я борюсь с написанием лучших комментариев.(В основном, трудно описать ментальные структуры, с которыми я думаю, используя линейный текст. Это все равно, что выучить язык чтением и не в состоянии произнести или понять разговорный язык.) Если бы я научился писать хорошие комментариипоскольку я научился писать хороший код, я бы сэкономил МНОГО усилий.
Я рекомендую вам избежать разочарований и потратить усилия на то, чтобы научиться писать хорошие комментарии с самого начала.
Вот еще один пример, example2.c , который выполняет немного больше пинг-понга между родительским и дочерним процессами:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
static inline const char *signal_name(const int signum)
{
switch (signum) {
case SIGINT: return "SIGINT";
case SIGHUP: return "SIGHUP";
case SIGTERM: return "SIGTERM";
case SIGQUIT: return "SIGQUIT";
case SIGUSR1: return "SIGUSR1";
case SIGUSR2: return "SIGUSR2";
default: return "(unnamed)";
}
}
int child_process(const pid_t parent, sigset_t *mask)
{
siginfo_t info;
int signum;
printf("Child: sleep(1).\n");
fflush(stdout);
sleep(1);
printf("Child: Sending SIGUSR1 to parent.\n");
fflush(stdout);
kill(parent, SIGUSR1);
printf("Child: Waiting for a SIGUSR2 from parent.\n");
fflush(stdout);
while (1) {
signum = sigwaitinfo(mask, &info);
if (signum == SIGUSR2 && info.si_pid == parent) {
printf("Child: Received SIGUSR2 from parent.\n");
break;
}
if (info.si_pid == parent)
printf("Child: Received %s from parent.\n", signal_name(signum));
else
if (info.si_pid)
printf("Child: Received %s from process %d.\n", signal_name(signum), (int)info.si_pid);
else
printf("Child: Received %s.\n", signal_name(signum));
fflush(stdout);
}
printf("Child: Sending SIGHUP to parent.\n");
fflush(stdout);
kill(parent, SIGHUP);
printf("Child: sleep(1).\n");
fflush(stdout);
sleep(1);
printf("Child: Done.\n");
return EXIT_SUCCESS;
}
void parent_process(const pid_t child, sigset_t *mask)
{
siginfo_t info;
int signum;
printf("Parent: Waiting for a SIGUSR1 from child.\n");
while (1) {
signum = sigwaitinfo(mask, &info);
if (signum == SIGUSR1 && info.si_pid == child) {
printf("Parent: Received SIGUSR1 from child.\n");
break;
}
if (info.si_pid == child)
printf("Parent: Received %s from child.\n", signal_name(signum));
else
if (info.si_pid)
printf("Parent: Received %s from process %d.\n", signal_name(signum), (int)info.si_pid);
else
printf("Parent: Received %s.\n", signal_name(signum));
fflush(stdout);
}
printf("Parent: sleep(1).\n");
fflush(stdout);
sleep(1);
printf("Parent: Sending SIGUSR2 to child.\n");
fflush(stdout);
kill(child, SIGUSR2);
printf("Parent: Waiting for a SIGHUP from child.\n");
while (1) {
signum = sigwaitinfo(mask, &info);
if (signum == SIGHUP && info.si_pid == child) {
printf("Parent: Received SIGHUP from child.\n");
break;
}
if (info.si_pid == child)
printf("Parent: Received %s from child.\n", signal_name(signum));
else
if (info.si_pid)
printf("Parent: Received %s from process %d.\n", signal_name(signum), (int)info.si_pid);
else
printf("Parent: Received %s.\n", signal_name(signum));
fflush(stdout);
}
return;
}
int main(void)
{
sigset_t mask;
pid_t child, p;
int status;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGQUIT);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
fprintf(stderr, "Cannot block SIGUSR1: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
child = fork();
if (child == -1) {
fprintf(stderr, "Cannot fork a child process: %s.\n", strerror(errno));
return EXIT_FAILURE;
} else
if (!child)
return child_process(getppid(), &mask);
else
parent_process(child, &mask);
printf("Parent: Reaping child process.\n");
fflush(stdout);
do {
p = waitpid(child, &status, 0);
if (p == -1) {
if (errno == EINTR)
continue;
printf("Parent: waitpid() error: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
} while (p != child);
if (WIFEXITED(status)) {
switch (WEXITSTATUS(status)) {
case EXIT_SUCCESS:
printf("Parent: Child reaped; EXIT_SUCCESS.\n");
break;
case EXIT_FAILURE:
printf("Parent: Child reaped; EXIT_FAILURE.\n");
break;
default:
printf("Parent: Child reaped; exit status %d.\n", WEXITSTATUS(status));
}
} else
if (WIFSIGNALED(status)) {
printf("Parent: Child died from signal %d.\n", WTERMSIG(status));
} else {
printf("Parent: Child process was lost unexpectedly.\n");
}
return EXIT_SUCCESS;
}
Вы можете поэкспериментировать с сигналами и выходным статусом ребенка, так как этот также сообщает об этом.
Чтобы разработать схему пинг-понга сигнала, я предлагаю сначала записать ее в виде временной шкалы.Например:
Child sleeps for a second ┆ Parent waits for SIGUSR1
Child sends SIGUSR1 ┆
Child waits for SIGUSR2 ┆ Parent receives SIGUSR1
┆ Parent sleeps for a second
┆ Parent sends SIGUSR2
Child receives SIGUSR2 ┆ Parent waits for SIGHUP
Child sends SIGHUP ┆
Child sleeps for a second ┆ Parent receives SIGHUP
Child exits ┆
┆ Parent reaps child
┆ Parent exits
Такая диаграмма помогает писать код, проверять, соответствует ли вывод ожиданиям (отмечая, что не все события упорядочены), и проверять, правильно ли код реализует диаграмму.Это тоже должно быть частью программной документации.