I am trying to build a shell, and I've managed to code most of the functionality in, however I have a small problem.
Say I type firefox &
. Firefox will open up as a background process. The &
activates a BG flag that makes the parent not wait for the child process.
Then I type gedit
. Gedit will open as a foreground process. Meaning that currently the parent is waiting for the process to close.
At this point, the parent has two processes - firefox
and gedit
. Firefox hasn't been waited on, and is currently in the background, whereas we are currently waiting for Gedit to finish. So far so good.
However, if I decide to send a SIGINT signal by pressing ctrl-c
, both firefox
and gedit
will close. Not good, only gedit
should be closing.
Here is my signal handler function:
pid_t suspended_process[10];
int last_suspended = -1;
void signal_handler(int signo){
pid_t process = currentpid();
// Catches interrupt signal
if(signo == SIGINT){
int success = kill(process, SIGINT);
}
// Catches suspend signal
else if(signo == SIGTSTP){
int success = kill(process, SIGTSTP);
resuspended = 1;
suspended_process[last_suspended+1] = process;
last_suspended++;
}
}
And here's the part in fork-exec code that either waits on a process, or keeps on going.
else if(pid > 0){ //Parent
current_pid = pid;
// Waits if background flag not activated.
if(BG == 0){
// WUNTRACED used to stop waiting when suspended
waitpid(current_pid, &status, WUNTRACED);
if(WIFEXITED(status)){
setExitcode(WEXITSTATUS(status));
}
else if(WIFSIGNALED(status)){
printf("Process received SIGNAL %d\n", WTERMSIG(status));
}
}
}
This also happens if I suspend a process beforehand. For example, I run firefox
and then press ctrl-z
to suspend it. Then I run gedit
and press ctrl-c
to close it. Right after, if I press fg
to restore the suspended firefox
, it closes immediately.
I cannot find a way to only send the SIGINT signal to the foreground process, it always sends the signal to ALL children other than the parent, no matter if they are in the background, or suspended.
Just in case, this is the function that initialises the signal handler:
void init_handler(){
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
// If conditions for signal handling.
// Also creates 2 signal handlers in memory for the SIGINT and SIGTSTP
if(sigaction(SIGINT, &sa, NULL) == -1)
printf("Couldn't catch SIGINT - Interrupt Signal\n");
if(sigaction(SIGTSTP, &sa, NULL) == -1)
printf("Couldn't catch SIGTSTP - Suspension Signal\n");
}
This is rather simple, but it isn't done with signals. Instead you must use a feature called process groups. Each single job (executable or pipeline or so) will be a separate process group. You can create process groups with setpgid
(or on some systems with setpgrp
). You can simply set the process group of the child process after fork
but before exec
, and then store the process group id of this job into the job table.
Now, the process group that is in the foreground is set as the active process group for the terminal (the /dev/tty
of the shell) with tcsetpgrp
- this is the process group that will receive CTRL+C. Those process groups that belong to the same session, but not to the group set to foreground with tcsetpgrp
will be completely oblivious to CTRL+C.