I have a parent process and two children. The parent process only creates two children, a reader and a counter, and waits for its death. The children do the following things.
The first child (reader):
- opens file,
- reads a line,
- sends a signal (SIGUSR1) to the second child,
- waits for a signal from the second child,
- go to 2 if we can read another line, else kill the second child.
The second child (counter):
- waits for a signal (SIGUSR1) from reader,
- counts line length,
- sends a signal to reader and go to 1.
I have trouble waiting for a signal from another process. The signal can be received before I call pause()
function, i.e., process can be blocked forever. I also tried to use sigprocmask()
, sigsuspend()
and sigwaitinfo()
, but it doesn't work correctly.
#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;
}
For this code I can get the following sequence:
$ 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
Why counter doesn't write "got signal"? How I can send and receive signals synchronously? (I know about other IPC methods, but I need synchronization through signals.)
There are a few obvious problems here.
The one that is actually causing your issue is this:
Remember that (1)
pid_counter
points to shared memory; and (2)fork()
returns twice every time it is called. When it returns in the child, it will set*pid_counter
to0
, but when it returns in the parent, it'll set*pid_counter
to the PID of the child. You cannot predict which will happen first. What's actually going on in your case is that it ends up set to0
, and so all yourkill
calls in the reader process are sending signals to every process in your process group, so both reader and counter are getting them at the same time. This is causing your synchronization to fail, because both processes are returning fromsigwaitinfo()
at the same time. In the reader, you should sendSIGUSR1
to the counter process only.What you need to do is change it to this:
Other points:
sigprocmask()
is preserved acrossfork()
calls, so you should just set it once beforefork()
ing. You never need to unblock it in your case, and should not.There is no need to (and arguably better not to) establish a handler for
SIGUSR1
if you're callingsigwaitinfo()
on it.strlen()
returns typesize_t
, so yourprintf()
format specifier should be%zu
, not%d
.All your calls to
fflush(stdout)
, while harmless, are here superfluous.You hardly ever check any of the returns from your system calls. This is not just for production code - if your program is not working, the very first thing to do it verify that you're checking all your system calls for success, because the reason it may not be working is because one of those calls is failing, perhaps because you passed it bad information. It's more important to check for success when you are testing your program, not less important.
Anyway, here's a working version of your program:
with the following output for the shown file: