SIGKILL to a subprocess tree on parent termination - c

I have a daemon application that starts several 3rd party executables (all closed-sources and non modifiable).
I would like to have all the child processes to automatically terminate when the parent exits for any reason (including crashes).
Currently, I am using prctl to achieve this (see also this question):
int ret = fork();
if (ret == 0) {
//Setup other stuff
prctl (PR_SET_PDEATHSIG, SIGKILL);
if (execve( "childexecutable" ) < 0) { /*signal error*/}
}
However, if "childexecutable" also forks and spawns "grandchildren", then "grandchildren" is not killed when my process exits.
Maybe I could create an intermediate process that serves as subreaper, that would then kill "someexecutable" when my process dies, but then wait for SIGCHLD and continue to kill child processes until none is left, but it seems very brittle.
Are there better solutions?

Creating a subreaper is not useful in this case, your grandchildren would be reparented to and reaped by init anyway.
What you could do however is:
Start a parent process and fork a child immediately.
The parent will simply wait for the child.
The child will carry out all the work of your actual program, including spawning any other children via fork + execve.
Upon exit of the child for any reason (including deathly signals e.g. a crash) the parent can issue kill(0, SIGKILL) or killpg(getpgid(0), SIGKILL) to kill all the processes in its process group. Issuing a SIGINT/SIGTERM before SIGKILL would probably be a better idea depending on what child processes you want to run, as they could handle such signals and do a graceful cleanup of used resources (including children) before exiting.
Assuming that none of the children or grandchildren changes their process group while running, this will kill the entire tree of processes upon exit of your program. You could also keep the PR_SET_PDEATHSIG before any execve to make this more robust. Again depending on the processes you want to run a PR_SET_PDEATHSIG with SIGINT/SIGTERM could make more sense than SIGKILL.
You can issue setpgid(getpid(), 0) before doing any of the above to create a new process group for your program and avoid killing any parents when issuing kill(0, SIGKILL).
The logic of the "parent" process should be really simple, just a fork + wait in a loop + kill upon the right condition returned by wait. Of course, if this process crashes too then all bets are off, so take care in writing simple and reliable code.

Related

Kill child process spawned with execl without making it zombie

I need to spawn a long-running child process and then kill it from the parent code. At the moment I do like this:
int PID = fork();
if (PID == 0) {
execl("myexec", "myexec", nullptr);
perror("ERROR");
exit(1);
}
// do something in between and then:
kill(PID, SIGKILL);
This does the job in the sense that the child process is stopped, but then it remains as a zombie. I tried to completely remove it by adding:
kill(getpid(), SIGCHLD);
to no avail. I must be doing something wrong but I can't figure out what, so any help would be greatly appreciated. Thanks.
signal(SIGCHLD, SIG_IGN);
kill(getpid(), SIGCHLD);
Presto. No zombie.
By ignoring SIGCHLD we tell the kernel we don't care about exit codes so the zombies just go away immediately.
You have been answered with:
signal(SIGCHLD, SIG_IGN);
to ignore the signal sent to the parent when a child dies. This is an old mechanism to avoid zombies, but zombies are your friends, as my answer will explain.
The zombies are not a bug, but a feature of the system. They are there to complete the fork(2), wait(2), exit(2), kill(2) group of system calls.
When you wait(2) for a child to die, the kernel tests if there's a child running with the characteristics you state in the wait(2). If it exists, the wait(2) will block, because the wait(2) system call is the one used in unix to give the parent the exit status of the waited child. If you use wait() and you have done no fork() a new child previously, wait() should give you an error, because you are calling wait with no fork (i'll stop boldening the system calls in this discussion from here on) but what happens if the parent did a fork but the child died before the parent was capable of making a wait. Should this be taken as an error? No. The system maintains the process table entry for the child proces, until one of two things happen: The parent dies (then all children processess get orphaned, being adopted by process id 1 ---init/systemd--- which is continously blocked in wait calls; or the parent does a wait, in which case the status of one (or the one requested) of the children is reported.
So in a proper usage of the system, it is possible (or necessary) to make a wait call for each fork you make. if you do more waits than forks, you get errors... if you make more forks than waits, you get zombies. In order to compensate this, your code should be changed to make the appropiate wait call.
kill(PID, SIGINT); /* don't use SIGKILL in the first time, give time to your process to arrange its last will before dying */
res = waitpid(PID, &status, 0);
And this will allow the child to die normally. The child is going to die, because you killed it (except if the child has decided to ignore the signal you send to it)
The reason for no race condition here (the child could die before is is wait()ed for) is the zombie process. Zombie processes are not proper processes, they don't accept signals, it is impossible to kill them, because they are dead already (no pun here ;)). They only occupy the process table slot, but no resource is allocated to them. When a parent does a wait call, if there's a zombie, it will be freed and the accounting info will be transferred to the parent (this is how the accounting is done), including the exit status, and if there isn't (because it died prematurely and you had invoked the above behaviour) you will get an error from wait, and the accounting info will be tranferred to init/systemd, which will cope for this. If you decide to ignore the SIGCHLD signal, you are cancelling the production of zombies, but the accounting is being feed in the wron way to init/systemd, and not accounted in the parent. (no such process can be waited for) you cannot distinguish if the wait fails because the child process died or because you didn't spawn it correctly. More is to come.
Let's say that the child cannot exec a new program and it dies (calling exit()). When you kill it, nothing happens, as there's no target process (well, you should receive an error from kill call, but I assume you are not interested in the kill result, as it is irrelevant for this case, you are interested in the status of the child, or how did the child died. This means you need to wait for it. if you get a normal exit, with a status of 1, (as you did an exit in case exec() fails) you will know that the child was not able to exec (you still need to distinguish if the 1 exit code was produced by the child or by the program later run by the child). If you successfully killed the child, you should get a status telling you that the child was killed with signal (the one you sent) and you will know that your code is behaving properly.
In case you don't want to block your parent process in the wait system call (well, your child program could have decided to ignore signals and the kill had no effect), then you can substitute the above by this:
kill(PID, SIGINT);
res = waitpid(PID, &status, WNOHANG);
that will not block the parent, in the case the child program has decided to ignore the signal you send to it. In this case, if wait returns -1 and errno value EINTR, then you know that your child has decided to ignore the signal you sent to it, and you need help from the operator (or be more drastic, e.g. killing it with SIGKILL).
A good approach should be
void alarm_handler()
{
}
...
kill(PID, SIGINT); /* kill it softly (it's your child, man!!) */
void *saved = signal(SIGALRM, alarm_handler);
alarm(3); /* put an awakener, you will be interrupted in 3s. */
res = waitpid(PID, &status, 0);
signal(SIGALRM, saaved); /* restore the previous signal handler */
if (res == -1 && errno == EINTR) {
/* we where interrupted by the alarm, and child didn't die. */
kill(PID, SIGKILL); /* be more rude */
}

terminal goes down with all children even when SIGKILLed but normal processes don't do the same

I would like to have the same effect in my program as the bash(terminal) does when we kill it using SIGKILL. As we know that we cannot handle SIGKILL in our progams so when ever I kill my program its children are assigned to init process, there is no way to handle it so that I can kill all my child processes and then kill the parent itself. Though when ever we kill terminal all the process created through it are killed even if we kill our terminal by SIGKILL.
For this I did some research and found the below post:
[https://unix.stackexchange.com/questions/54963/how-can-terminal-emulators-kill-their-children-after-recieving-a-sigkill][1]
The post is bit confusing, still what I got out of the post is that if the process you are killing is the process leader of the process group then all its children will be killed.
So for simplicity, I implemented below program to test if it is so:
int main()
{
printf("Curent PID: %u\n", getpid());
// make a new session
pid_t pid = setsid();
printf("New session ID: %u\n", pid);
pid = fork();
switch(pid)
{
case -1:
perror("UNable to fork the process\n");
exit(EXIT_FAILURE);
case 0:
// child process
while (1)
{
sleep(1);
}
break;
}
while (1)
{
printf("Process Leader running\n");
sleep(1);
}
return 0;
}
After running the above program when I killed the parent process the child process didn't got killed. I also modified the above program so that it does not belong to any tty, I thought might be the process leader should not be associated with any tty. I did that by the following way:
Create a normal process (Parent process)
Create a child process from within the above parent process
The process hierarchy at this stage looks like : TERMINAL -> PARENT PROCESS -> CHILD PROCESS
Terminate the the parent process.
The child process now becomes orphan and is taken over by the init process.
Call setsid() function to run the process in new session and have a new group.
then the same above code repeats.
Still when i killed the process leader children were there.
Maybe I didn't got that post on unix.stackexchange or is it the deafult behaviour in LINUX.
One way that I can implement to make all children kill is by catching each TERMINATING SIGNAL like SIGTERM, SIGHUP etc. handle them and write logic inside those signal handlers to kill child first.But still on SIGKILL I can't do anything.
Also I am interested to know that if killing parent process doesn't affect child process even if the parent is process leader or whatever then how bash(terminal) manages to kill all the child processes even if we send SIGKILL to it. Is there some extra logic written for terminals in LINUX kernel.
If there is a way to kill all child processes when parent is killed even using SIGKILL signal, I would be happy to know that too.
Manual page of kill says:
Negative PID values may be used to choose whole process groups
In my understanding to kill the whole group of processes, you have to send negative PID.
Another mechanism is causing that killing a terminal kills its child processes. Processes running from a terminal have their stdin/stdout attached to the terminal. When killing the terminal, those connections are closed and a signal (SIG_HUP) is sent to those processes. A usual program does not handle this signal and is terminated by default.
The advice from Marian is quite correct and well worth researching, but if you choose to follow that route you will likely end up with an implementation of what might be called the "hostage trick".
The hostage trick consists of your root process spawning an artificial child process which spends all its time in the stopped state. This "hostage" will be spawned immediately before the first child process which does real work in your (multi-process) program.
The hostage process is made the leader of its own process group and then enters a loop in which it stops itself with "raise(SIGSTOP)". If it is ever continued, it checks to see whether its parent has terminated (i.e. whether it has been re-parented or cannot signal its parent with the null signal (ESRCH)). If the parent has terminated, then the hostage should terminate, otherwise it should re-suspend with another "raise(SIGSTOP)".
You need to be careful about race conditions: e.g. for the re-parenting test take care to cache the parent-process-id for the hostage as the return value from "getpid()" before "fork()"-ing the hostage and also make "setpgid()" calls downstream of "fork()" in both parent and child. You then need to consider what you do if someone "kill(., SIGKILL)"s the hostage!
True, you can put a SIGCHLD handler in the parent to re-spawn it, but that requires considerable care to preserve the continuity of the identity of the hostage's process group; maybe there were other child processes at the time of the SIGKILL and the replacement hostage should go in the original process group, maybe there weren't and the original process group has evaporated.
Even if you get that right, the fact that you have put a "fork()" call in a handler for an asynchronous signal (SIGCHLD) will likely open another can of worms if your main process uses multiple threads.
Because of these difficulties I would advise against using the hostage trick unless the child processes run code over which you have no control (and to think seriously about the costs in complexity and maintainability even then). If you have control of the code of the child processes, then it is much simpler to use a "pipe()".
You create the pipe in the parent process and manage the file descriptors to ensure that the parent process is the sole writer and that each child process allocates one file descriptor to the read-side. If you do this, then the termination of the parent process (whether due to SIGKILL or any other cause) is communicated to the child processes by the EoF condition on the read side of the pipe as the last writer terminates.
If you want to treat SIGKILL specially, then you can use a protocol on the pipe whereby the parent process sends a termination message advising the children of its termination status on all normal terminations and on catchable fatal signals, and leaves the children to infer that the parent was killed by SIGKILL in the event that the read-side of the pipe delivers an EoF without a preceding termination message.
On Linux prctl(PR_SET_PDEATHSIG... will arrange for a process to receive a signal when it's parent dies, this setting is preserved over exec but not inherited by child processes.

Using waitpid or sigaction?

I have understood that:
1) waitpid is used to wait for a child's death and then collect the SIGCHLD and the exit status of the child etc.
2) When we have a signal handler for SIGCHLD, we do some more things related to cleanup of child or other stuff (upto the programmer) and then do a waitpid so that the child will not go zombie and then return.
Now, do we need to have both 1 and 2 in our programs when we do a fork/exec and the child returns ?
If we have both, the SIGCHLD is obtained first, so the signal handler is called first and thus its waitpid is called successfully and not the waitpid in the parent process code as follows:
my_signal_handler_for_sigchld
{
do something
tmp = waitpid(-1,NULL,0);
print tmp (which is the correct value of the child pid)
}
int main ()
{
sigaction(SIGCHLD, my_signal_handler_for_sigchld)
fork()
if (child) //do something, return
if parent // waitpid(child_pid, NULL,0); print value returned from this waitpid - it is -1
}
Appreciate if someone helps me understand this.
You really don't need to handle SIGCHLD if your intent is to run a child process, do some stuff, then wait for it to finish. In that case, you just call waitpid when you're ready to synchronize. The only thing SIGCHLD is useful for is asynchronous notification of child termination, for example if you've got an interactive (or long-running daemon) application that's spawning various children and needs to know when they finish. However, SIGCHLD is really bad/ugly for this purpose too, since if you're using library code that creates child processes, you might catch the events for the library's children terminating and interfere with its handling of them. Signal handlers are inherently process-global and deal with global state, which is usually A Bad Thing(tm).
Here are two better approaches for when you have child processes that will be terminating asynchronously:
Approach 1 (select/poll event-based): Make sure you have a pipe to/from each child process you create. It can be either their stdin/stdout/stderr or just an extra dummy fd. When the child process terminates, its end of the pipe will be closed, and your main event loop will detect the activity on that file descriptor. From the fact that it closed, you recognize that the child process died, and call waitpid to reap the zombie.
Approach 2 (thread based): For each child process you create, also create a thread that will immediately call waitpid on the child process's pid. When waitpid returns successfully, use your favorite thread synchronization primitives to let the rest of the program know that the child terminated, or simply take care of everything you need to do in this waiter thread before it terminates.
Both of these approaches are modular and library-friendly (they avoid interfering with any other parts of your code or library code which might be making use of child processes).
You need to call the waiting syscalls like waitpid or friends -eg wait4 etc- othewise you could have zombie processes.
You could handle SIGCHLD to be notified that some child ended (or stopped, etc...) but you'll need to wait for it later.
Signal handlers are restricted to call a small set of async-signal-safe-functions (see signal(7) for more). Good advice is to just set a volatile sig_atomic_t flag inside, and test it at later and safer places.

kill - does it kill the process right away?

what does kill exactly do?
I have a parent process which is creating 100 (as an example) child processes one after another. At the end of any child's job, I kill the child with kill(pid_of_child, SIGKILL) and I cannot see that in ps output. But if something goes wrong with the parent process and I exit from the parent process with exit(1) (at this point only 1 child is there - I can check tht in ps), at that point I see a lot of <defunct> processes whose ppid is pid of parent process.
How is that possible? did kill not kill the child processes entirely?
kill doesn't kill anything. It sends signals to the target process. SIGKILL is just a signal. Now, the standard action for SIGKILL -- the only action, actually, since SIGKILL can't be handled or ignored by a process -- is to exit, that's true.
The "<defunct>" process is a child that hasn't been reaped, meaning that the parent hasn't called wait() to retrieve the exit status of the child. Until the parent calls wait(), the defunct (or "zombie") process will hang around.
Whenever a process ends, no matter how it ends (kill or otherwise), it will stay in the kernel's process table until its parent process retrieves its exit status (with wait and friends). Leaving it in the process table avoids a number of nasty race conditions.
If your parent process has exited, the children should get reassigned to init, which periodically reaps its children.
Yes, SIGKILL terminates the process, but in any case (either normal exit or terminated), processes have an exit status, which needs to be kept around for potential readers - as such an entry in the process table may remain until this is done. See http://en.wikipedia.org/wiki/Zombie_process .

C: Exec/fork > Defunct processes

I'm want to create a lot of child processes using the fork > exec procedure. Many processes are ending very fast (in less than two minutes, some even earlier).
My first problem is, I put the spawn process into the background with
./spawnbot > logging.txt
[CTRL+Z]
bg 1
disown
So far so good. Now I don't see any of the spawnbot's messages anymore and they go straight into the logging.txt. However, whenever a new child is created I see all the info about that child in my console again.. I now wanted to start each child with it's own pipe - is there a better way to not have children post their output messages all over the console? Should I just redirect it to /dev/null or is this done with some flag in C?
Secondly, all the children don't really get killed. I have a lot of processes in my ps -ef. What can I do about that? How do I d
First your second question!
Your children stay in 'zombie' mode because the kernel thinks you might still want to retrieve a return value from them..
If you have no intention to get return values from your child processes, you should set the SIGCHLD signal handler in the parent process to SIG_IGN to have the kernel automatically reap your children.
signal(SIGCHLD, SIG_IGN);
The first question depends a it on your implementation..
But general speaking, just after you fork() you should use close() to close the old file descriptors for 0 and 1 and then use dup2() to set them to your wanted values.. No time for an example right now, but hope this pushes you in the right direction..
Your child processes are getting killed. Defunct processes are also called zombie processes; zombies are dead! A zombie process is nothing but an entry in the process table, it doesn't have any code or memory.
When a process dies (by calling _exit, or killed by a signal), it must be reaped by its parent. Every resource used by the process other than the entry in the process table disappears. The parent must call wait or waitpid. Once the parent has been notified of the child process's death, and has had the opportunity to read the child's exit status, the child's entry in the process table disappears as well: the zombie is reaped.
If you never want to be notified of your children's death, ignore the SIGCHLD signal; this tells the kernel that you're not interested in knowing the fate of your children and the zombie will be reaped automatically.
signal(SIGCHLD, SIG_IGN)
If you only want to be notified of your children's deaths in specific circumstances, call sigaction with the SA_NOCLDWAIT flag. When a child dies, if the parent is executing one of the wait family of functions, it'll be notified of the child's death and be told the exit status; otherwise the child's exit status will be discarded.
struct sigaction sa;
sa.sa_handler = &my_sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_NOCLDWAIT;
sigaction(SIGCHLD, &sa, NULL);
Regarding the output, your children write to the same places as the parent unless you've explicitly redirected them (with close and open, or dup, or a number of other possibilities). Your children are probably printing diagnostic messages to standard error (that's what it's for, after all).
./spawnbot >logging.txt 2>&1
In addition, since you seem to want to detach the children from the terminal, you probably want to make sure they don't receive a SIGHUP if you kill the terminal. So use nohup:
nohup ./spawnbot >logging.txt 2>&1 &
disown

Resources