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):
The second child (counter):
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:
if (!(*pid_counter = fork())) {
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
to 0
, 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 to 0
, and so all your kill
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 from sigwaitinfo()
at the same time. In the reader, you should send SIGUSR1
to the counter process only.
What you need to do is change it to this:
pid_t temp_pid
if ( !(temp_pid = fork()) ) {
*pid_counter = getpid();
Other points:
sigprocmask()
is preserved across fork()
calls, so you should just set it once before fork()
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 calling sigwaitinfo()
on it.
strlen()
returns type size_t
, so your printf()
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:
#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;
}
with the following output for the shown file:
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$