Последовательные сигналы между двумя процессами
У меня есть родительский процесс и двое детей. Родительский процесс создает только двух детей, читателя и счетчика, и ожидает его смерти. Дети делают следующие вещи.
Первый ребенок (читатель):
- открывает файл,
- читает строку,
- отправляет сигнал (SIGUSR1) второму ребенку,
- ждет сигнала от второго ребенка,
- перейдите к 2, если мы можем прочитать другую строку, иначе убить второго ребенка.
Второй ребенок (счетчик):
- ждет сигнала (SIGUSR1) от считывателя,
- считает длину строки,
- отправляет сигнал читателю и переходит к 1.
У меня проблемы с ожиданием сигнала от другого процесса. Сигнал можно получить до того, как я позвоню pause()
функция, т. е. процесс может быть заблокирован навсегда. Я также пытался использовать sigprocmask()
, sigsuspend()
а также sigwaitinfo()
, но это не работает правильно.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
enum { MAX_LEN = 16 };
void handler(int signo)
{
// do nothing
}
int main(int argc, const char * argv[])
{
sigset_t ss;
sigemptyset(&ss);
sigaddset(&ss, SIGUSR1);
// handle SIGUSR1
signal(SIGUSR1, handler);
// shared memory for file line
char *data = mmap(NULL, MAX_LEN, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
pid_t *pid_counter = mmap(NULL, sizeof(*pid_counter), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
pid_t pid_reader;
if (!(pid_reader = fork())) {
sigprocmask(SIG_BLOCK, &ss, NULL);
printf("READER: waiting signal from COUNTER\n"); fflush(stdout);
sigwaitinfo(&ss, NULL);
sigprocmask(SIG_UNBLOCK, &ss, NULL);
printf("READER: got signal\n"); fflush(stdout);
printf("READER: opening file\n"); fflush(stdout);
FILE *f = fopen(argv[1], "r");
while (fgets(data, MAX_LEN, f) > 0) {
printf("READER: reading line and waiting signal from COUNTER\n"); fflush(stdout);
sigprocmask(SIG_BLOCK, &ss, NULL);
kill(*pid_counter, SIGUSR1);
sigwaitinfo(&ss, NULL);
sigprocmask(SIG_UNBLOCK, &ss, NULL);
printf("READER: got signal\n"); fflush(stdout);
}
printf("READER: closing file and killing COUNTER\n"); fflush(stdout);
fclose(f);
kill(*pid_counter, SIGTERM);
_exit(0);
}
if (!(*pid_counter = fork())) {
sleep(1);
printf("COUNTER: send signal to READER that COUNTER is ready\n"); fflush(stdout);
while (1) {
sigprocmask(SIG_BLOCK, &ss, NULL);
kill(pid_reader, SIGUSR1);
printf("COUNTER: waiting signal from READER\n"); fflush(stdout);
sigwaitinfo(&ss, NULL);
sigprocmask(SIG_UNBLOCK, &ss, NULL);
printf("COUNTER: got signal\n"); fflush(stdout);
printf("%d\n", strlen(data));
fflush(stdout);
}
}
wait(NULL);
wait(NULL);
return 0;
}
Для этого кода я могу получить следующую последовательность:
$ gcc -o prog prog.c && ./prog input.dat
READER: waiting signal from COUNTER
COUNTER: send signal to READER that COUNTER is ready
COUNTER: waiting signal from READER
READER: got signal
READER: opening file
READER: reading line and waiting signal from COUNTER
READER: got signal
READER: reading line and waiting signal from COUNTER
READER: got signal
READER: reading line and waiting signal from COUNTER
READER: got signal
READER: reading line and waiting signal from COUNTER
READER: got signal
READER: reading line and waiting signal from COUNTER
READER: got signal
READER: reading line and waiting signal from COUNTER
READER: got signal
READER: closing file and killing COUNTER
Почему счетчик не пишет "получил сигнал"? Как я могу отправлять и получать сигналы синхронно? (Я знаю о других методах IPC, но мне нужна синхронизация через сигналы.)
1 ответ
Здесь есть несколько очевидных проблем.
То, что на самом деле вызывает вашу проблему, это:
if (!(*pid_counter = fork())) {
Помните, что (1) pid_counter
указывает на общую память; и (2) fork()
возвращается дважды при каждом вызове. Когда он вернется в ребенка, он установит *pid_counter
в 0
, но когда он вернется в родительский, он установит *pid_counter
ПИД ребенка. Вы не можете предсказать, что произойдет первым. Что в действительности происходит в вашем случае, так это то, что он в конечном итоге настроен на 0
и так все ваши kill
вызовы в процессе считывания отправляют сигналы каждому процессу в вашей группе процессов, поэтому и считыватель, и счетчик получают их одновременно. Это приводит к сбою синхронизации, потому что оба процесса возвращаются из sigwaitinfo()
в то же время. В ридер, вы должны отправить SIGUSR1
только встречный процесс.
Что вам нужно сделать, это изменить это на:
pid_t temp_pid
if ( !(temp_pid = fork()) ) {
*pid_counter = getpid();
Другие моменты:
sigprocmask()
сохраняется черезfork()
звонки, поэтому вы должны просто установить его один раз, прежде чемfork()
ING. Вам никогда не нужно разблокировать его в вашем случае, и не должны.Нет необходимости (и, возможно, лучше не устанавливать) обработчик для
SIGUSR1
если ты звонишьsigwaitinfo()
в теме.strlen()
возвращает типsize_t
, Так что вашиprintf()
спецификатор формата должен быть%zu
не%d
,Все ваши звонки на
fflush(stdout)
, хотя и безвредны, здесь излишни.Вы вряд ли когда-либо проверяете какие-либо из ваших системных вызовов. Это не только для производственного кода - если ваша программа не работает, первым делом убедитесь, что вы проверяете все системные вызовы на успешность, потому что причина, по которой она может не работать, заключается в том, что один из этих вызовов возможно, потому что вы передали ему плохую информацию. Более важно проверять успешность, когда вы тестируете свою программу, не менее важно.
В любом случае, вот рабочая версия вашей программы:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
enum { MAX_LEN = 16 };
int main(int argc, const char * argv[])
{
if ( argc != 2 ) {
fprintf(stderr, "Enter one argument, and one argument only.\n");
return EXIT_FAILURE;
}
// shared memory for file line
char *data = mmap(NULL, MAX_LEN, PROT_READ | PROT_WRITE, MAP_SHARED |
MAP_ANON, -1, 0);
if ( data == MAP_FAILED ) {
perror("mmap() failed for data");
exit(EXIT_FAILURE);
}
pid_t *pid_counter = mmap(NULL, sizeof *pid_counter, PROT_READ |
PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
if ( pid_counter == MAP_FAILED ) {
perror("mmap() failed for pid_counter");
exit(EXIT_FAILURE);
}
pid_t pid_reader, temp_pid;
sigset_t ss;
sigemptyset(&ss);
sigaddset(&ss, SIGUSR1);
if ( sigprocmask(SIG_BLOCK, &ss, NULL) == -1 ) {
perror("sigprocmask() failed");
exit(EXIT_FAILURE);
}
if ( (pid_reader = fork()) == -1 ) {
perror("fork() failed for reader");
exit(EXIT_FAILURE);
}
else if ( !pid_reader ) {
printf("READER: waiting signal from COUNTER\n");
if ( sigwaitinfo(&ss, NULL) == -1 ) {
perror("sigwaitinfo() failed in reader");
_exit(EXIT_FAILURE);
}
printf("READER: got signal\n");
printf("READER: opening file\n");
FILE *f = fopen(argv[1], "r");
if ( !f ) {
fprintf(stderr, "Couldn't open input file\n");
_exit(EXIT_FAILURE);
}
while ( fgets(data, MAX_LEN, f) ) {
printf("READER: reading line and waiting signal from COUNTER\n");
if ( kill(*pid_counter, SIGUSR1) == -1 ) {
perror("kill() for SIGUSR1 failed in reader");
_exit(EXIT_FAILURE);
}
if ( sigwaitinfo(&ss, NULL) == -1 ) {
perror("sigwaitinfo() failed in reader");
_exit(EXIT_FAILURE);
}
printf("READER: got signal\n");
}
printf("READER: closing file and killing COUNTER\n");
fclose(f);
if ( kill(*pid_counter, SIGTERM) == -1 ) {
perror("kill() for SIGTERM failed in reader");
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
if ( (temp_pid = fork()) == -1 ) {
perror("fork() failed for counter");
exit(EXIT_FAILURE);
}
else if ( temp_pid == 0 ) {
*pid_counter = getpid();
sleep(1);
printf("COUNTER: send signal to READER that COUNTER is ready\n");
while (1) {
if ( kill(pid_reader, SIGUSR1) == -1 ) {
perror("kill() failed for SIGUSR1 in counter");
_exit(EXIT_FAILURE);
}
printf("COUNTER: waiting signal from READER\n");
if ( sigwaitinfo(&ss, NULL) == -1 ) {
perror("sigwaitinfo() failed in counter");
_exit(EXIT_FAILURE);
}
printf("COUNTER: got signal\n");
printf("%zu\n", strlen(data));
}
_exit(EXIT_SUCCESS);
}
if ( wait(NULL) == -1 ) {
perror("first wait() failed");
exit(EXIT_FAILURE);
}
if ( wait(NULL) == -1 ) {
perror("second wait() failed");
exit(EXIT_FAILURE);
}
return 0;
}
со следующим выводом для показанного файла:
paul@thoth:~/src$ cat file.txt
line one
line two
line three
paul@thoth:~/src$ ./sig file.txt
READER: waiting signal from COUNTER
COUNTER: send signal to READER that COUNTER is ready
COUNTER: waiting signal from READER
READER: got signal
READER: opening file
READER: reading line and waiting signal from COUNTER
COUNTER: got signal
9
COUNTER: waiting signal from READER
READER: got signal
READER: reading line and waiting signal from COUNTER
COUNTER: got signal
9
COUNTER: waiting signal from READER
READER: got signal
READER: reading line and waiting signal from COUNTER
COUNTER: got signal
11
COUNTER: waiting signal from READER
READER: got signal
READER: reading line and waiting signal from COUNTER
COUNTER: got signal
1
COUNTER: waiting signal from READER
READER: got signal
READER: closing file and killing COUNTER
paul@thoth:~/src$