I want to simulate the signals (Ctrl+\ and Ctrl+C) in bash but using C.
I want to kill a child process using signal function void (*signal(int sig, void (*func)(int)))(int);
.
I reached so far:
#include <assert.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
pid_t cpid = 0;
void signal_handler(int sig) {
// char parent_str[] = "this is ctrl + c in the parent prosess\n";
char child_str[] = "this is ctrl + c in the child prosess\n";
// signal(sig, signal_handler);
if (sig == SIGINT) {
write(STDOUT_FILENO, child_str, sizeof(child_str) - 1);
kill(cpid, SIGINT);
}
else if (sig == SIGQUIT)
{
write(STDOUT_FILENO, "this is ctrl + \\ in the child prosess\n", sizeof("this is ctrl + \\ in the child prosess\n") - 1);
kill(cpid, SIGQUIT);
}
}
int main(int argc, char **argv) {
pid_t pid, pgid;
(void)argv;
signal(SIGINT, signal_handler);
signal(SIGQUIT, signal_handler);
pid = fork();
cpid = pid;
if (pid == 0) {
cpid = 1;
while(1);
exit(EXIT_SUCCESS);
}
wait (NULL);
printf("pid = %d\ncpisd = %d", pid, cpid);
while (1);
}
but when I'm compiling this code, the process won't kill, why?
When you fork
a process almost everything that has occurred thus far is duplicated in the child process - that is to say, the child process has a nearly identical environment to its parent at the very moment fork
successfully returns. While there are some exceptions to this (as detailed in the man page for fork
) signal handlers are retained.
What happens in your terminal when you type ^C is that a SIGINT
is sent to both your parent and child processes. This is handled in the same way, since the signal handling function is the same in parent and child, but with different results because of the value of cpid
.
In the parent, cpid
is 0
and thus kill
has this effect:
If pid equals 0, then sig is sent to every process in the process group of the calling process.
so the parent "forwards" that signal to the child, as it belongs to the parent's process group.
The child process thus receives two signals; one directly from the terminal, and one from the parent process. Using the same handler, but this time cpid
is 1
, it calls kill
and fulfills this criteria:
If pid is positive, then signal sig is sent to the process with the ID specified by pid.
So this calls
kill(1, SIGINT)
wherein PID 1 is almost certainly init, the root process responsibly for your entire system. It's probably a good thing this had no effect, as an alternative result could be your system shutting down.
In the end, nothing really happens as each process pushes the signal along this chain, but this is why we see three lines of text: one from the parent, two from the child.
We'll want to handle our signals differently in the parent and child, so it would be best to use two different handlers.
Because of the overlap in signal as a result of sending it from the terminal, we will have to determine who sent the signal in the child if we want to only accept signals from the parent process directly. signal
handlers can not provide this level of detail, so instead we need sigaction
handlers.
Here is a small example that splits signal handling between the parent and child. In the child, we setup our struct sigaction
with the SA_SIGINFO
flag, which indicates we want to use the sa_sigaction
handler. We use getppid
and compare it with info->si_pid
to determine if the signal came from the parent directly.
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
static pid_t child_id;
void sigparent(int sig) {
const char msg[] = "The parent will kill the child.\n";
switch (sig) {
case SIGINT:
case SIGQUIT:
write(STDOUT_FILENO, msg, (sizeof msg) - 1);
kill(child_id, SIGQUIT);
break;
default:
break;
}
}
void sigchild(int sig, siginfo_t *info, void *ctx) {
const char msg[] = "The child will now die.\n";
if (getppid() == info->si_pid)
switch (sig) {
case SIGINT:
case SIGQUIT:
write(STDOUT_FILENO, msg, (sizeof msg) - 1);
_exit(EXIT_SUCCESS);
break;
default:
break;
}
}
void parent(void) {
signal(SIGINT, sigparent);
signal(SIGQUIT, sigparent);
puts("Parent will wait...");
waitpid(child_id, NULL, WUNTRACED);
puts("Parent is done waiting.");
}
void child(void) {
struct sigaction action = { 0 };
sigemptyset(&action.sa_mask);
action.sa_flags = (SA_SIGINFO);
action.sa_sigaction = sigchild;
sigaction(SIGINT, &action, NULL);
sigaction(SIGQUIT, &action, NULL);
while (1) {
puts("Child is sleeping...");
sleep(2);
}
}
int main(void) {
if ((child_id = fork()))
parent();
else
child();
}