
Question:
I'm attempting to get my child process to generate a random number between 1 -9, then send it to my parent process for which it would display it to screen each time control + Z is pressed. I'm also using dup2() to change the stream from printf and scanf to the read and write ends of the pipe for each process.
The code works as below, you press control C to start the main program. Now it waits for control Z to be pressed for each time a random number is sent from child and displayed at parent. The issue I am facing is, it seems the child only runs <strong>once</strong> when control Z is pressed again, it only runs the parent code. The pipe also only ever shows the same number and never changes so its not being updated. I also noticed that if I remove the printf line before the dup function <strong>printf("a \n")</strong>, it prints out random numbers....however the problem with the child running once still exists.
Would appreciate if someone can assist me on this. Cheers.
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
int selection = 0;
int secondSel = 0;
int fd[2];
int pipe1;
pid_t fork1;
void handleSignal(int sig)
{
if (sig == SIGINT)
{
selection = 1;
secondSel = 1;
}
if (sig == SIGTSTP)
{
selection = 1;
}
}
int main()
{
int firstPipe[2];
int secondPipe[2];
//wait for control+C
if (signal(SIGINT, handleSignal) == SIG_ERR)
{
printf("Error catching signal C \n");
exit(0);
}
while(1)
{
//wait till control c is pressed
if (selection == 1)
{
signal(SIGINT, SIG_IGN);
if (secondSel == 1)
{
pipe1 = pipe(fd);
}
if (pipe1 < 0)
{
printf("Error creating pipe 1 \n");
exit(1);
}
if (secondSel == 1)
{
fork1 = fork();
}
if (fork1 < 0)
{
printf("Error with first fork. \n");
exit(1);
}
else if (fork1 == 0) //first child process
{
signal(SIGTSTP, handleSignal);
pause();
printf("a \n");
int randNum1;
close(fd[0]);
dup2(fd[1], 1);
randNum1 = rand() % 9 + 1;
printf("%d ", randNum1);
fflush(stdout);
close(fd[1]);
}
else //parent
{
signal(SIGTSTP, handleSignal);
pause();
printf("b \n");
int f;
close(fd[1]);
dup2(fd[0], 0);
scanf("%d \n", &f);
printf("%d \n", f);
close(fd[0]);
}
secondSel = 0;
}
}
}
Answer1:The comment/code:
//wait for control+C
if (signal(SIGINT, handleSignal) == SIG_ERR)
is a disconcerting start. The signal()
function sets the signal handler for SIGINT
, but it no way waits for a signal to arrive. The simplest fix for that is to add a call to <a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/pause.html" rel="nofollow">pause()
</a> after that block of code.
Inside the infinite loop, the code:
if (secondSel == 1)
{
pipe1 = pipe(fd);
}
if (pipe1 < 0)
{
printf("Error creating pipe 1 \n");
exit(1);
}
is sub-optimal and/or confusing. Since pipe1
is only set when pipe()
is called, there's no need to test it on each iteration. Error messages should be reported on standard error, and should not have trailing blanks. The code should be:
if (secondSel == 1)
{
if (pipe(fd) < 0)
{
fprintf(stderr, "Error creating pipe 1\n");
exit(1);
}
}
There's a similar test on the same variable protecting the fork()
.
Your code carefully closes the pipe after the first cycle, but never reopens it. That is ultimately why the second and subsequent iterations fail. Your code would be better if you didn't try to do everything on each cycle. Also, using standard output for debugging information is subject to various problems; it is better to use standard error instead — especially when standard output isn't working properly.
<h3>Instrumented code</h3>Here's an instrumented version of your code. The err_syserr()
function is almost generic; the condition using usleep()
is peculiar to this code and ensures that the output of the terminated error message is normally sequenced. I put the tests on the function calls that would be failing — the first close failed on the second cycle because the pipe descriptors are all closed at the end of the first cycle. (Note that reusing pipe()
is no help after the fork()
— the pipes in the parent and child would not be connected to each other.)
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static void err_syserr(const char *fmt, ...);
int selection = 0;
int secondSel = 0;
int fd[2];
int pipe1 = 0;
pid_t fork1 = 0;
static void handleSignal(int sig)
{
if (sig == SIGINT)
{
selection = 1;
secondSel = 1;
}
if (sig == SIGTSTP)
{
selection = 1;
}
}
int main(void)
{
// wait for control+C
if (signal(SIGINT, handleSignal) == SIG_ERR)
{
printf("Error catching signal C\n");
exit(1);
}
//printf("Waiting for interrupt\n");
//pause();
while (1)
{
fprintf(stderr, "Looping: %d (%d)\n", (int)getpid(), selection);
// wait till control c is pressed
if (selection == 1)
{
signal(SIGINT, SIG_IGN);
if (secondSel == 1)
{
pipe1 = pipe(fd);
fprintf(stderr, "Created pipe: %d (%d, %d)\n", pipe1, fd[0], fd[1]);
}
if (pipe1 < 0)
{
printf("Error creating pipe 1\n");
exit(1);
}
if (secondSel == 1)
{
fork1 = fork();
fprintf(stderr, "Forked: %d (%d, %d)\n", fork1, (int)getpid(), (int)getppid());
}
if (fork1 < 0)
{
printf("Error with first fork.\n");
exit(1);
}
else if (fork1 == 0) // first child process
{
signal(SIGTSTP, handleSignal);
fprintf(stderr, "Pausing C: %d\n", (int)getpid());
pause();
fprintf(stderr, "Unpaused C: %d\n", (int)getpid());
printf("a\n");
if (close(fd[0]) != 0)
err_syserr("close(fd[0]=%d) failed", fd[0]);
if (dup2(fd[1], 1) < 0)
err_syserr("dup2(fd[1]=%d, 1) failed", fd[1]);
int randNum1 = rand() % 9 + 1;
fprintf(stderr, "Print C: %d\n", randNum1);
if (printf("%d\n", randNum1) < 2)
{
fprintf(stderr, "Print C: failed\n");
clearerr(stdout);
}
fflush(stdout);
if (close(fd[1]) != 0)
err_syserr("close(fd[1]=%d) failed", fd[1]);
}
else // parent
{
signal(SIGTSTP, handleSignal);
fprintf(stderr, "Pausing P: %d\n", (int)getpid());
pause();
fprintf(stderr, "Unpaused P: %d\n", (int)getpid());
printf("b\n");
if (close(fd[1]) != 0)
err_syserr("close(fd[1]=%d) failed", fd[1]);
if (dup2(fd[0], 0) < 0)
err_syserr("dup2(fd[0]=%d, 0) failed", fd[0]);
int f = 99;
if (scanf("%d", &f) != 1)
{
fprintf(stderr, "Scanf P: failed\n");
clearerr(stdin);
}
printf("Parent: %d\n", f);
if (close(fd[0]) < 0)
err_syserr("close(fd[0]=%d) failed", fd[0]);
}
secondSel = 0;
}
}
return 0;
}
#include <errno.h>
#include <string.h>
#include <stdarg.h>
static void err_syserr(const char *fmt, ...)
{
int errnum = errno;
va_list args;
if (fork1 != 0) /* Parent waits 1/4 second */
usleep(250000);
fprintf(stderr, "%d: ", (int)getpid());
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
if (errnum != 0)
fprintf(stderr, ": %d %s", errnum, strerror(errnum));
fputc('\n', stderr);
exit(EXIT_FAILURE);
}
Example output:
$ ./sigbug
Looping: 23528 (0)
Looping: 23528 (0)
…
…huge numbers of 'looping' messages omitted…
…
Looping: 23528 (0)
Looping: 23528 (0)
Looping: 23528 (0)
Looping: 23528 (0)
^CLooping: 23528 (0)
Looping: 23528 (0)
Looping: 23528 (0)
Looping: 23528 (0)
Created pipe: 0 (3, 4)
Forked: 23529 (23528, 45428)
Pausing P: 23528
Forked: 0 (23529, 23528)
Pausing C: 23529
^ZUnpaused C: 23529
Unpaused P: 23528
a
b
Print C: 5
Looping: 23529 (1)
Pausing C: 23529
Parent: 5
Looping: 23528 (1)
Pausing P: 23528
^ZUnpaused P: 23528
b
Unpaused C: 23529
23529: close(fd[0]=3) failed: 9 Bad file descriptor
23528: close(fd[1]=4) failed: 9 Bad file descriptor
$
<h3>Bug fixed code</h3>
Here's a revised version of the code with what seems to me more appropriate logic. It doesn't include err_syserr()
because the function calls aren't failing. The selection
and secondSel
variables aren't needed; the signal handler becomes a stub that just contains return;
(which could be omitted). It doesn't loop like mad at the start because I put a pause in place to wait for the interrupt.
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static void handleSignal(int sig)
{
return;
}
int main(void)
{
int fd[2];
int pipe1 = 0;
pid_t fork1 = 0;
if (signal(SIGINT, handleSignal) == SIG_ERR)
{
printf("Error catching signal C\n");
exit(1);
}
printf("Waiting for interrupt\n");
pause();
signal(SIGINT, SIG_IGN);
pipe1 = pipe(fd);
if (pipe1 < 0)
{
fprintf(stderr, "Error creating pipe 1\n");
exit(1);
}
fprintf(stderr, "Created pipe: %d (%d, %d)\n", pipe1, fd[0], fd[1]);
fork1 = fork();
if (fork1 < 0)
{
fprintf(stderr, "Error with fork.\n");
exit(1);
}
fprintf(stderr, "Forked: %d (%d, %d)\n", fork1, (int)getpid(), (int)getppid());
signal(SIGTSTP, handleSignal);
if (fork1 == 0)
{
dup2(fd[1], 1);
close(fd[0]);
close(fd[1]);
while (1)
{
fprintf(stderr, "Pausing C: %d\n", (int)getpid());
pause();
fprintf(stderr, "Unpaused C: %d\n", (int)getpid());
int randNum1 = rand() % 9 + 1;
fprintf(stderr, "Print C: %d\n", randNum1);
if (printf("%d\n", randNum1) < 2)
{
fprintf(stderr, "Print C: failed\n");
clearerr(stdout);
}
fflush(stdout);
}
}
else
{
dup2(fd[0], 0);
close(fd[0]);
close(fd[1]);
while (1)
{
fprintf(stderr, "Pausing P: %d\n", (int)getpid());
pause();
fprintf(stderr, "Unpaused P: %d\n", (int)getpid());
int f = 99;
if (scanf("%d", &f) != 1)
{
fprintf(stderr, "Scanf P: failed\n");
clearerr(stdin);
}
else
printf("Parent: %d\n", f);
}
}
return 0;
}
Example output:
$ ./sigbug-jl1
Waiting for interrupt
^CCreated pipe: 0 (3, 4)
Forked: 23554 (23553, 45428)
Pausing P: 23553
Forked: 0 (23554, 23553)
Pausing C: 23554
^ZUnpaused C: 23554
Print C: 5
Unpaused P: 23553
Pausing C: 23554
Parent: 5
Pausing P: 23553
^ZUnpaused C: 23554
Print C: 8
Unpaused P: 23553
Pausing C: 23554
Parent: 8
Pausing P: 23553
^ZUnpaused P: 23553
Unpaused C: 23554
Print C: 6
Pausing C: 23554
Parent: 6
Pausing P: 23553
^\Quit: 3
$
I used SIGQUIT to terminate the program since interrupts are disabled.
Answer2:The issue you are having is due to closing the pipe after the first number is sent from the child to the parent.
However, the main problem is that your code is disorganized, which makes it harder to figure out what is going on and increases the chance of mistakes. Rather than having everything in one big loop, I would suggest restructuring your code as follows:
wait for SIGINT
create pipe
fork
if child:
set up stdout using dup2()
while true:
wait for SIGTSTP
write to pipe
else:
set up stdin using dup2()
while true:
wait for SIGTSTP
read from pipe
etc.