I don't know if I'm asking the right question, but after several hours of searching, I can't find my answer. So I explain what I need.
I would like to create a child process with fork and that child process will wait until it receives a signal or changes a variable value. I mean I don't want to be creating a process every time I need it, I want it to wait and run when I tell it to, as many times as it needs to.
I have tried several things, but they have not worked, I put the code, I really do not know what to look for to do what I need without thread, or is it only possible to do it with thread?
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
volatile sig_atomic_t runChild = 0;
static void handler(int signum, siginfo_t *sinfo, void *unused){
if (signum == SIGUSR1) {
printf("SIGUSR1 received\n");
runChild = 1;
}
}
int main(int argc, char *argv[]) {
char *newArgv[] = {"myecho", "hello", "world", NULL};
char *newEnviron[] = { "MY_NAME=jcarlosweb", NULL };
struct sigaction sa;
sa.sa_flags =0;
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = handler;
if (sigaction(SIGUSR1, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
while (!runChild) {
usleep(200);
}
runChild = 0;
execve("./process-pipe-fork/execve/myecho", newArgv, newEnviron);
/* The execve function returns only if an error occurs. */
perror("An error occurred in execve");
abort();
}
// continue execution of parent process without waiting for child process to finish
waitpid(-1, NULL, WNOHANG);
printf("Run myecho:\n");
kill(pid,SIGUSR1);
sleep(3);
printf("Run myecho:\n");
kill(pid,SIGUSR1);
printf("Done\n");
exit(EXIT_SUCCESS);
}
The execve of my myecho program only runs once, and I would like to run it when I say so throughout the main program.
In this case I'm using the exec in the child process, but I could have just put a function that prints a message, for example. The idea I am looking for is the same
Block on a pipe. This might look something like...
#include <err.h>
#include <errno.h>
#include <unistd.h>
int main(void) {
char ch;
int fd[2];
pid_t pid;
ssize_t ret;
pipe(fd); /* to block on */
pid = fork();
if (pid < 0) {
err(1, "fork failed");
} else if (pid == 0) { /* child */
close(fd[1]);
warnx("child %d start", getpid());
/* block until parent goes away */
ret = read(fd[0], &ch, 1);
warnx("child %d is go", getpid());
close(fd[0]);
/* child now does things here ... */
} else { /* parent */
sleep(3);
close(fd[0]);
close(fd[1]);
/* parent does more things here ... */
}
return 0;
}
You cannot wait on a child process for a variable change because the parent process and the child are different processes, and so, they have each their own virtual address space (with no intersections)
There are several ways to do what you want. One has been suggested in another answer, so I will not extend on it, as it looks fine to me.
You can create a thread. A thread runs in a separate stack (separate from main thread) but the whole virtual address space is the same. Both running threads (main and the one you create) share the same addresses and so, one can modify a variable and the other can be looking at it for a change. This solution is feasible, but not very efficient, as while the waiting process is waiting for the variable to change (the variable needs to be defined as volatile, so the compiler doesn't cache the value anywhere and the true value is the one the other thread stores in it) it is consuming cpu making your resources wasted for no reason.
You can use (in a different process) a semaphore, (man semop) for the parent process to change it.
You can use shared memory between processes (see man shmop or man mmap) and so, only the memory corresopnding to the shared memory segment is shared between both processes.
You can use a socket. A socket is a bidirectional communication channel between parent and child, and so it allows you to use a complex protocol to do the most variable communication messages between both processes. You can use reliable unix sockets for this (they are fast, reliable, but require both processes to be in the same machine) or network sockets (the API is the same, which gives you a versatile approach that you can later change in case your needs change)
I recommend you to read:
pthread_create(3) and similar manual pages. A reference of threads.
pthread_mutex(3) and similar manual pages. A reference to synchronization between threads.
semop(2) and friends. Reference to sysv5 semaphores.
mmap(2) and friends. Reference to shared memory segments.
pipe(2) and fifo related documentation. This will show you how a pipe can be used, as it blocks readers and writers when the internal buffer it has is full/empty. This can be a good starting point.
The following code fork()s a child and then it feeds its child with all its own input, while the parent is writing to standard output its own input, it also feeds to the child its input, so the child will work on it and write to a file output.txt:
###pru.c
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int fd[2];
/* we need to get the pipe before fork() so we have it in both,
* parent and child. */
int res = pipe(fd);
if (res < 0) {
perror("pipe");
exit(1);
}
int child_pid = fork();
if (child_pid < 0) {
perror("fork");
exit(1);
}
/* both processes get here. */
if (child_pid == 0) { /* we are the child only */
close(fd[1]); /* we are not using the writing end */
FILE *out = fopen("output.txt", "a+");
FILE *f = fdopen(fd[0], "r");
int c;
while ((c = fgetc(f)) != EOF) {
/* print an hex representation of char */
fprintf(out, "<%02x>", c);
}
/* let's close both */
fclose(out); fclose(f);
/* remember the child gets 0 from fork, so it needs to
* call getpid() syscall to get its pid. */
printf("Child (pid=%d) exiting.\n", getpid());
exit(0);
/* NOTREACHED */
} else {
close(fd[0]); /* we don't use the reading end */
/* we are writing on the writing side of the pipe */
FILE *out = fdopen(fd[1], "w");
int c;
while ((c = fgetc(stdin)) != EOF) {
/* make a copy for the child */
fputc(c, out);
/* and write to stdout */
fputc(c, stdout);
}
fclose(out); /* signal the child that there's no more output */
fprintf(stderr,
"Waiting for the child (whose pid should be %d)"
" to finish\n",
child_pid);
int status;
pid_t exited_pid = wait(&status);
if (exited_pid < 0) {
perror("wait");
exit(1);
}
fprintf(stderr,
"%d exited with status %d\n",
exited_pid, status);
exit(0);
/* NOTREACHED */
} /* else */
} /* main */
whose output is:
$ echo "Hello, world" | ./a.out
Hello, world
Waiting for the child (whose pid should be 2833) to finish
Child (pid=2833) exiting.
2833 exited with status 0
$ _
and the contents of output.txt:
$ cat output.txt
<48><65><6c><6c><6f><2c><20><77><6f><72><6c><64><0a>$ _
I suppose your complaint is that myecho runs after the first kill(pid, SIGUSR1) but not the second time? But as you said yourself, execve doesn't return if it succeeds!
Upon successful execve the child process is no longer running your program at all, it's running the myecho program, which doesn't handle signals, and when myecho calls _exit() the process exits. So by the time the parent calls kill(pid, SIGUSR1) the second time, your child process is a zombie. The second kill won't fail, but it also won't have any effect; a zombie can't respond to signals.
If you want the child to stick around and keep responding to signals, then the child cannot call execve itself to run myecho. Instead, each time the signal is received, the child must fork again, creating a grandchild process to do the exec. The original child doesn't call execve but just returns to its sleep loop to wait for another signal.
As a side remark, I think you've misunderstood the role of waitpid. Your waitpid(-1, NULL, WNOHANG); effectively does nothing at all. It would be used to test whether the child had already exited and reap it if so, immediately returning 0 if yes and -1 if no, but you ignore the return value. You don't need any system call for the parent and child to run independently; that's what happens by default when you fork. You call waitpid or one of its relatives when you want the parent to wait instead of going on running. I think you may have misunderstood something in the documentation.
Which raises the question of who is going to clean up all those zombies. They will stay around and pollute the process table until their parent calls waitpid. You could have the child process, on every wakeup, call waitpid(WNOHANG) in a loop until it returns -1 to reap all grandchildren that have exited. Or you could use a SIGCHLD handler. Likewise, the parent should really be arranging somehow for the child to terminate before the parent does (kill it with another signal, for instance), and then the parent should waitpid for the child, without WNOHANG, before it itself exits.
Finally, instead of usleep in a loop, the preferred way to wait for a signal is with sigwait(). In that case you don't actually need the runChild flag. You still have to install a signal handler for SIGUSR1 but it can just do nothing.
I can try to come back later and post a fixed version of your program as an example.
Thanks to the answers of Nate Eldredge and Luis Colorado I have been able to create 3 examples. I'm going to post them in case it helps other people, I've really done this as an exercise to learn C, but these the examples are nothing without the help of Nate Eldredge and Luis Colorado. I also did another example with sigwait, but I've seen that with Semaphore it's simpler.
1 - Posix Semaphore:
struct sigaction sa; // signal
short int *runChildProcess; // mmap shared memory
sem_t *sem; // semaphore
static void handleChildTerm(int signum, siginfo_t *sinfo, void *unused) {
printf("Child %d exited\n", (int)sinfo->si_pid);
}
static void runChild(pid_t pid) {
if (sem_post(sem) == -1) {
perror("sem_post run child");
exit(EXIT_FAILURE);
}
// When SIGCHLD is caught, sleep(3) is interrupted
// https://stackoverflow.com/questions/14266485/understanding-sigchld-when-the-child-process-terminates
unsigned short int sleepTime = 3;
while ((sleepTime = sleep(sleepTime)) > 0)
;
}
static void termChild(pid_t pid) {
*runChildProcess = 0; // set to 0 to exit the loop
runChild(pid);
if(munmap(runChildProcess, sizeof *runChildProcess) == -1) {
perror("Munmap");
exit(EXIT_FAILURE);
}
if (sem_destroy(sem) == -1) {
perror("sem_destroy");
exit(EXIT_FAILURE);
}
}
int main(int argc, char *argv[]) {
setbuf(stdout, NULL); // make stdout unbuffered
// mmap: memory mapping file, shared with child process with the flag MAP_ANONYMOUS | MAP_SHARED
runChildProcess = mmap(NULL, sizeof *runChildProcess, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if (runChildProcess == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
*runChildProcess = 1;
// semaphore
sem = mmap(0, sizeof(*sem), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (sem_init(sem, 1 /*shared*/, 0 /*init val*/) == -1) {
perror("sem_init");
exit(EXIT_FAILURE);
}
// signal handler for SIGCHLD (exit child process)
sa.sa_flags = SA_SIGINFO | SA_RESTART | SA_NOCLDWAIT;
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = handleChildTerm;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction SIGCHLD");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) { // child process
while (*runChildProcess) {
sem_wait(sem);
if (*runChildProcess == 0) {
break;
}
printf("Child process started: %d\n", getpid());
}
_exit(EXIT_SUCCESS);
}
printf("Parent process: %d\n", getpid());
runChild(pid);
runChild(pid);
runChild(pid);
termChild(pid);
printf("Done\n");
exit(EXIT_SUCCESS);
}
2 - Pipes
#include <errno.h> // for errno
#include <signal.h> // sigaction
#include <stdbool.h> // bool
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <string.h> // strlen
#include <sys/mman.h> // mmap
#include <sys/types.h> // pid_t
#include <unistd.h> // fork
bool *runChildProcess; // mmap shared memory
int fd[2]; // pipe
static void runTermChildHandler(int signum, siginfo_t *sinfo, void *unused) {
*runChildProcess = 0;
}
static void termChildHandler(int signum, siginfo_t *sinfo, void *unused) {
printf("Child %d exited\n", (int)sinfo->si_pid);
}
static void runChild(int pipeFd, char *buffer) {
if (write(pipeFd, buffer, strlen(buffer) + 1) == -1) {
perror("write");
exit(EXIT_FAILURE);
}
// When SIGCHLD is caught, sleep(3) is interrupted
// https://stackoverflow.com/questions/14266485/understanding-sigchld-when-the-child-process-terminates
unsigned short int sleepTime = 3;
while ((sleepTime = sleep(sleepTime)) > 0)
;
}
static void termChild(pid_t pid, int pipeFd) {
kill(pid, SIGUSR1);
close(pipeFd);
unsigned short int sleepTime = 1;
while ((sleepTime = sleep(sleepTime)) > 0);
munmap(runChildProcess, sizeof *runChildProcess);
}
int main(int argc, char *argv[]) {
setbuf(stdout, NULL); // make stdout unbuffered
// mmap: memory mapping file, shared with child process with the flag MAP_ANONYMOUS | MAP_SHARED
runChildProcess = mmap(NULL, sizeof *runChildProcess, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if (runChildProcess == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
*runChildProcess = 1; // set to 1 to run the loop
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_sigaction = runTermChildHandler;
if (sigaction(SIGUSR1, &sa, NULL) == -1) {
perror("sigaction SIGUSR1");
exit(EXIT_FAILURE);
}
sa.sa_flags = SA_RESTART | SA_SIGINFO | SA_NOCLDSTOP;
sa.sa_sigaction = termChildHandler;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction SIGCHLD");
exit(EXIT_FAILURE);
}
if (pipe(fd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
char buffer[100];
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
close(fd[1]); // close write end of pipe
printf("Child process: %d\n", getpid());
while (*runChildProcess) {
ssize_t nbytes = read(fd[0], buffer, sizeof(buffer)); // block until data is available from parent process
if (nbytes == -1) { // EINTR: interrupted by a signal, although SA_RESTART is set no is no need to check for EINTR
if (errno == EINTR) {
continue;
}else{
perror("read");
exit(EXIT_FAILURE);
}
}
if (nbytes == 0) {
continue;
}
printf("Buffer: %s\n", buffer);
}
close(fd[0]); // close read end of pipe
_exit(EXIT_SUCCESS);
}
printf("Parent process: %d\n", getpid());
close(fd[0]); // close read end of pipe
runChild(fd[1], "Thanks Nate Eldredge");
runChild(fd[1], "Thanks Luis Colorado");
runChild(fd[1], "Thanks www.stackoverflow.com");
termChild(pid, fd[1]);
printf("Done\n");
exit(EXIT_SUCCESS);
}
3 - Semaphore with execve
#include <errno.h>
#include <semaphore.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/stat.h>
struct sigaction sa; // signal
short int *runChildProcess; // mmap shared memory
sem_t *sem; // semaphore
static void handleChildTerm(int signum, siginfo_t *sinfo, void *unused) {
printf("Child %d exited\n", (int)sinfo->si_pid);
}
static void runChild(pid_t pid) {
if (sem_post(sem) == -1) {
perror("sem_post");
exit(EXIT_FAILURE);
}
// When SIGCHLD is caught, sleep(3) is interrupted
// https://stackoverflow.com/questions/14266485/understanding-sigchld-when-the-child-process-terminates
unsigned short int sleepTime = 3;
while ((sleepTime = sleep(sleepTime)) > 0)
;
}
static void termChild(pid_t pid) {
*runChildProcess = 0; // set to 0 to exit the loop
runChild(pid);
signal(SIGCHLD, SIG_DFL); // restore default signal handler
if(munmap(runChildProcess, sizeof *runChildProcess) == -1) {
perror("Munmap");
exit(EXIT_FAILURE);
}
if (sem_destroy(sem) == -1) {
perror("sem_destroy");
exit(EXIT_FAILURE);
}
}
int main(int argc, char *argv[]) {
setbuf(stdout, NULL); // make stdout unbuffered
// mmap: memory mapping file, shared with child process with the flag MAP_ANONYMOUS | MAP_SHARED
runChildProcess = mmap(NULL, sizeof *runChildProcess, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if (runChildProcess == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
*runChildProcess = 1;
// semaphore
sem = mmap(0, sizeof(*sem), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (sem_init(sem, 1 /*shared*/, 0 /*init val*/) == -1) {
perror("sem_init");
exit(EXIT_FAILURE);
}
// signal handler for SIGCHLD (exit child process)
sa.sa_flags = SA_SIGINFO | SA_RESTART | SA_NOCLDWAIT;
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = handleChildTerm;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction SIGCHLD");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) { // child process
char rootChildPidParam[10];
snprintf(rootChildPidParam, 10, "%d", getpid());
char *childArgv[] = {"process-pipe-fork/execve/myecho", "hello", "world", rootChildPidParam, NULL};
char *childEnviron[] = {"MY_NAME=jcarlosweb", NULL};
printf("Root Child started: %s\n", rootChildPidParam);
while (*runChildProcess) {
sem_wait(sem);
// check again
if (*runChildProcess == 0) {
break;
}
pid_t leafChildPid = fork();
if (leafChildPid == -1) {
perror("fork sibling");
_exit(EXIT_FAILURE);
}
if (leafChildPid == 0) { // child process
printf("Leaf process with execve started: %d\n", getpid());
// TODO: make child process in background
execve("process-pipe-fork/execve/myecho", childArgv, childEnviron);
perror("execve myecho");
_exit(EXIT_FAILURE);
}
}
_exit(EXIT_SUCCESS);
}
printf("Parent process: %d\n", getpid());
runChild(pid);
runChild(pid);
runChild(pid);
termChild(pid);
printf("Done\n");
exit(EXIT_SUCCESS);
}
I'm trying to handle multiple signals with one signal handler, the expected result is for ctrlc to exit the child process and also exit the parent process while ctrlz prints a random number everytime ctrlz is pressed but it doesn't seem to work after the first signal is handled.The other part of the code is a child process that loops until ctrl-c is called.
This is the code.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <time.h>
#include <string.h>
int sig_ctrlc = 0;
int sig_ctrlz = 0;
//main signal handler to handle all signals
void SIGhandler(int sig) {
switch (sig) {
case SIGINT:
sig_ctrlc = SIGINT;
break;
case SIGTSTP:
sig_ctrlz = SIGTSTP;
break;
case SIGCHLD:
default: break;
}
}
int main() {
int fd[2]; //to store the two ends of the pipe
char get_inode[] = "ls -il STATUS.TXT";
FILE *fp;
FILE *log;
FILE *command;
time_t t;
time(&t);
int rv = 0;
log = fopen("file_log.txt", "w");
struct sigaction act;
memset (&act, 0, sizeof(struct sigaction));
act.sa_handler = SIGhandler;
//if pipe can't be created
if (pipe(fd) < 0) {
fprintf(log, "Pipe error");
}
int pid = fork();
switch(pid) {
case -1:
fprintf(stderr, "fork failed\n");
exit(1);
case 0:
/*child process */
// maps STDOUT to the writing end of the pipe
// if (dup2(fd[1], STDOUT_FILENO) == -1) {
// fprintf(log, "error in mapping stdout to the writing pipe\n");
// }
act.sa_flags = SA_RESTART;
sigemptyset(&act.sa_mask);
sigaction(SIGINT, &act, NULL);
sigaction(SIGTSTP, &act, NULL);
for (size_t i = 1;; i++) {
/* code */
printf(" running in child\n");
sleep(1);
if (sig_ctrlc != 0) {
printf("ctrlc handled\n");
printf("exiting...\n");
sig_ctrlc = 0;
break;
}
if (sig_ctrlz != 0) {
printf("ctlrz handled.\n");
/* random generator, the problem with this is it runs with time if ctrlz
is handled within a second it returns the same number
*/
srand(time(0));
int rand_num;
rand_num = rand() % (50 - 10 + 1) + 10;
printf("random number: %d\n", rand_num);
sig_ctrlz = 0;
sigaction(SIGINT, &act, NULL);
sigaction(SIGTSTP, &act, NULL);
}
}
default:
/* parent process */
close(fd[1]);
//maps STDIN to the reading end of the pipe
// if (dup2(fd[0], STDIN_FILENO) < 0) {
// fprintf(log, "can't redirect");
// exit(1);
// }
// //checks for fopen not working and writes to STATUS.TXT with a redirect
// if ((fp = freopen("STATUS.TXT", "w", stdout)) != NULL) {
// printf("start time of program: %s\n", ctime(&t));
// printf("Parent process ID: %d\n", getppid());
// printf("Child process ID: %d\n", getpid());
//
// //gets inode information sends the command to and receives the info from the terminal
// command = popen(get_inode, "w");
// fprintf(command, "STATUS.TXT");
// fclose(command);
//
//// map STDOUT to the status file
// if(freopen("STATUS.TXT", "a+ ", stdout) == NULL) {
// fp = fopen("file_log.txt","w");
// fprintf(log, "can't map STATUS.TXT to stdout\n");
// exit(1);
// }
//
printf("parent has started\n");
wait(NULL);
time(&t);
printf("PARENT: My child's termination status is: %d at: %s\n", WEXITSTATUS(rv), ctime(&t));
// fprintf(fp, "PARENT: My child's termination status is: %d at: %s\n", WEXITSTATUS(rv), ctime(&t));
// fclose(fp);
// fclose(log);
sigaction(SIGINT, &act, NULL);
for (size_t i = 1;; i++) {
/* code */
printf("PARENT: in parent function\n");
sleep(1);
if (sig_ctrlc != 0)
exit(0);
}
}
return 0;
}
There are some good comments on the original post that help make minor fixes. I think there is also an issue of the static variables sig_ctrlc and sig_ctrlz maybe not being async-signal safe. Other than that though, I think your signal handling setup should work in a case where you repeatedly send SIGTSTP and then SIGINT after. I think how you are going about testing your program may be the issue.
Based on some clues you've given:
"ctrlz is pressed but it doesn't seem to work after the first signal is handled"
"doesn't handle both ctrlc and ctrlz after the first ctrlz"
It leads me to believe that what you are experiencing is actually the terminal's job control getting in your way. This sequence of events may explain it:
parent (process A) is started in terminal foreground in group %1
child (process B) is forked and also in terminal foreground in group %1
signal handlers are set up within child
in an attempt to signal the child, press ctrl-z to send SIGTSTP
owning terminal (grandparent of child (process B) in this case) receives the request
owning terminal broadcasts the signal to all processes in the foreground group
owning terminal removes group %1 from foreground
parent (process A) receives SIGTSTP and is suspended (default action)
child (process B) receives SIGTSTP and the signal handler is invoked
the random number is generated and printed on next iteration of child loop
subsequent attempts to signal the child via ctrl-z or ctrl-c are not forwarded to the child (or parent) by the terminal because nothing is in the terminal foreground
If that was indeed the case, at that point, you should be able to bring the processes back to the foreground by manually typing in fg and hitting enter. You could then try and signal again. However, a better way to test a program like this would be to run it in one terminal, then send the signals via kill(...) using their pid's from another terminal.
One extra note: unlike signal(...), sigaction(...) does not require "re-installation" after each disposition. A good explanation by Jonathan here https://stackoverflow.com/a/232711/7148416
I am working on a ncurses based file manager in C. The problem is that some child processes can take some time to complete and till that happens it remains stuck due to waitpid.
I can't use the WNOHANG flag because the next block of code is dependent on the output of the child process.
void getArchivePreview(char *filepath, int maxy, int maxx)
{
pid_t pid;
int fd;
int null_fd;
// Reallocate `temp_dir` and store path to preview file
char *preview_path = NULL;
allocSize = snprintf(NULL, 0, "%s/preview", cache_path);
preview_path = malloc(allocSize+1);
if(preview_path == NULL)
{
endwin();
printf("%s\n", "Couldn't allocate memory!");
exit(1);
}
snprintf(preview_path, allocSize+1, "%s/preview", cache_path);
// Create a child process to run "atool -lq filepath > ~/.cache/cfiles/preview"
pid = fork();
if( pid == 0 )
{
remove(preview_path);
fd = open(preview_path, O_CREAT | O_WRONLY, 0755);
null_fd = open("/dev/null", O_WRONLY);
// Redirect stdout
dup2(fd, 1);
// Redirect errors to /dev/null
dup2(null_fd, 2);
execlp("atool", "atool", "-lq", filepath, (char *)0);
exit(1);
}
else
{
int status;
waitpid(pid, &status, 0);
getTextPreview(preview_path, maxy, maxx);
free(preview_path);
}
}
In this case, I would like to carry forward with the rest of the program if the user decides to go to some other file. In what way can I change the architecture of the program?
If I have understood the question correctly then you want to unblock parent on either completion of child or any user input.
As suggested in this comment, you could handle SIGCHLD and one more signal say SIGUSR1. SIGUSR1 will be raised when you get user input. Following is the example where both SIGCHLD and 'SIGUSR1' is handled. If use inputs any number then it raises SIGUSR1 to parent and parent kill child. Else child will raise SIGCHLD on exit.
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int raised_signal = -1;
static void cb_sig(int signal)
{
if (signal == SIGUSR1)
raised_signal = SIGUSR1;
else if (signal == SIGCHLD)
raised_signal = SIGCHLD;
}
int main()
{
int pid;
int i, a;
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_handler = cb_sig;
if (sigaction(SIGUSR1, &act, NULL) == -1)
printf("unable to handle siguser1\n");
if (sigaction(SIGCHLD, &act, NULL) == -1)
printf("unable to handle sigchild\n");
pid = fork();
if (pid == 0) {
/* child */
for (i = 0; i < 10; i++) {
sleep(1);
printf("child is working\n");
}
exit(1);
} else {
/* parent */
if (-1 == scanf("%d", &a)) {
if (errno == EINTR)
printf("scanf interrupted by signal\n");
} else {
raise(SIGUSR1);
}
if (raised_signal == SIGUSR1) {
printf("user terminated\n");
kill(pid, SIGINT);
} else if (raised_signal == SIGCHLD) {
printf("child done working\n");
}
exit(1);
}
return 0;
}
I have a C server. This server has to handle multiple connections and user's input (through a simple ncurses GUI). So I created two childs.
My problem comes when from the main menu of the user interface, I need to exit the program (then terminate the second child process -which handles the connections- from the first child process).
I'll try to explain myself with a little example:
int main(){
pid_t pid;
int status1, status2;
if((pid = fork()) < 0){
perror("main fork failure:");
exit(1);
}
if(pid == 0){
pid = fork();
if(pid == 0){
/*
some stuff the second child does while
the first child is already running
*/
}
/* this is the first child */
int choice;
choice = menu();
switch(choice){
case 1:
break;
case 2:
/*
HERE I have to exit (from the first child first,
and from the program then): how can I kill the
second child that is running to prevent
zombie processes?
*/
// kill() which pid?
exit(2);
break;
}
wait(&status2);
}
wait(&status1);
return 0;
}
So, how can I kill it if I don't know the second child pid from the first child?
In your code, you reuse the variable pid, but fortunately, the non-zero pid is the one you need to signal.
Hence:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
extern int menu(void);
static void wait_for_pid(int pid)
{
int status;
int corpse;
while ((corpse = wait(&status)) >= 0 && corpse != pid)
printf("Unexpected child %d exited with status 0x%.4X\n", corpse, status);
if (corpse == pid)
printf("Child %d exited with status 0x%.4X\n", corpse, status);
else
printf("Child %d died without its death being tracked\n", pid);
}
int main(void)
{
pid_t pid;
if ((pid = fork()) < 0)
{
perror("main fork failure:");
exit(1);
}
if (pid == 0)
{
if ((pid = fork()) < 0)
{
perror("child fork failure:");
exit(1);
}
if (pid == 0)
{
pause(); /* Do nothing until signalled */
exit(0);
}
/* this is the first child */
int choice = menu();
switch (choice)
{
case 1:
/* action 1 */
break;
case 2:
kill(pid, SIGTERM);
exit(2);
/*NOTREACHED*/
}
wait_for_pid(pid);
exit(0);
}
wait_for_pid(pid);
return 0;
}
The loop in the wait_for_pid() function should be overkill for the child, but the parent process could have children it doesn't know about under some circumstances — unlikely but not impossible circumstances.
The use of pause() in the second child is simply writing some code; it is not useful and would not therefore be what you'd write there. Writing the comment /* action 1 */ is likewise dummy code; you'd replace it with code that does something useful. I'd probably have functions to call for the first child and the second child, rather than embedding much code in main(). I assume that it's written as shown to create an MCVE (Minimal, Complete, Verifiable Example); thank you for keeping the code small.
The code above was untested because there was no menu() function. The code below has a menu function — not that it is very interactive.
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
extern int menu(void);
int menu(void)
{
printf("Dozing...\n");
sleep(1);
printf("Menu option 2 chosen\n");
return 2;
}
static void wait_for_pid(int pid)
{
int status;
int corpse;
int curpid = getpid();
printf("%d: waiting for children to die\n", curpid);
while ((corpse = wait(&status)) >= 0 && corpse != pid)
printf("%d: Unexpected child %d exited with status 0x%.4X\n", curpid, corpse, status);
if (corpse == pid)
printf("%d: Child %d exited with status 0x%.4X\n", curpid, corpse, status);
else
printf("%d: Child %d died without its death being tracked\n", curpid, pid);
}
int main(void)
{
pid_t pid;
if ((pid = fork()) < 0)
{
perror("main fork failure:");
exit(1);
}
if (pid == 0)
{
if ((pid = fork()) < 0)
{
perror("child fork failure:");
exit(1);
}
if (pid == 0)
{
printf("Second child (%d) - pausing\n", (int)getpid());
pause(); /* Do nothing until signalled */
printf("Second child (%d) - awake despite no signal handling\n", (int)getpid());
exit(0);
}
/* this is the first child */
printf("First child (%d) - menuing\n", (int)getpid());
int choice = menu();
switch (choice)
{
case 1:
/* action 1 */
break;
case 2:
printf("kill(%d, SIGTERM)\n", pid);
kill(pid, SIGTERM);
wait_for_pid(pid);
exit(2);
/*NOTREACHED*/
}
/* Reached on menu choices != 2 */
/* Probably needs a loop around the menu() - end loop before wait_for_pid() */
wait_for_pid(pid);
exit(0);
}
wait_for_pid(pid);
return 0;
}
When run, a sample output sequence was:
19489: waiting for children to die
First child (19490) - menuing
Dozing...
Second child (19491) - pausing
Menu option 2 chosen
kill(19491, SIGTERM)
19490: waiting for children to die
19490: Child 19491 exited with status 0x000F
19489: Child 19490 exited with status 0x0200
All of which looks as would be expected. You can see the death from SIGTERM in the status 0x000F (SIGTERM is normally 15, and is 15 on macOS Sierra, though AFAIK no standard demands that it is 15). You can see the first child exited normally with status 2 from the 0x0200. You can see that the parent started waiting before the children did anything. And you can see the debugging techniques — copious printing and including the PID most of the time.
I'm writing a Unix program where the parent process has to send signals to children and a grandson. How could I know if all processes have been already created before sending signals? Because sometimes they don't exist yet. Thanks a lot!
void t(int sig)
{
kill(SIGKILL, pidc1);
kill(SIGKILL, pidc2);
kill(SIGKILL, pidg2);
kill(SIGKILL, pidc3);
}
void handler()
{
write(1, "Signal SIGUSR1\n", 15);
}
pid_t pidc1, pidc2, pidc3, pidg2;
int main(int argc, char **argv)
{
struct sigaction action;
int status;
action.sa_flags = 0;
action.sa_handler = handler;
sigaction(SIGUSR1, &action, NULL);
pidc1 = fork();
if(pidc1 == 0)
{
printf("Child 1\n");
}
pidc2 = fork();
if(pidc2 == 0)
{
printf("Child 2\n");
pidg2 = fork();
if(pidg2 == 0)
{
printf("Grandson 2\n");
}
wait(&status);
}
pidc3 = fork();
if(pidc3 == 0)
{
printf("Child 3\n");
}
kill(pidg2, SIGUSR1);
kill(pidc3, SIGUSR1);
signal(SIGALRM, t);
alarm(10);
wait(&status);
}
Preliminary note: The child code parts in your example program fall through to their parent's code, which is certainly not intended; I'll assume something like return sleep(5); at the end of each block. Also note that the printf()s may malfunction with fork()s and buffered output.
Barmar wrote:
If you need to wait for the grandchild processes to be created, you need some kind of communication from the child to the parent, so it can send the grandchild's PID. Shared memory and a mutex would be a way to do this.
That's absolutely correct. (The direct children are no problem, since the parent knows their PIDs.) Another way to communicate the grandchild's PID is a pipe; your example main() could become:
int main(int argc, char **argv)
{
int status;
sigaction(SIGUSR1, &(struct sigaction){.sa_handler = handler}, NULL);
setbuf(stdout, NULL); // printf() may malfunction without this
pidc1 = fork();
if (pidc1 == 0)
{
printf("Child 1\n"); return sleep(5);
}
int pipefd[2];
pipe(pipefd); // for communicating the grandson's PID
pidc2 = fork();
if (pidc2 == 0)
{
printf("Child 2\n");
pidg2 = fork();
if (pidg2 == 0)
{
printf("Grandson 2\n"); return sleep(5);
}
write(pipefd[1], &pidg2, sizeof pidg2); // write pidg2 to write end
wait(&status); return sleep(5);
}
pidc3 = fork();
if(pidc3 == 0)
{
printf("Child 3\n"); return sleep(5);
}
read(pipefd[0], &pidg2, sizeof pidg2); // read pidg2 from pipe's read end
kill(pidg2, SIGUSR1);
kill(pidc3, SIGUSR1);
}