Cleaning up children happens asynchronously

This is an example from Advanced Linux Programming > chapter 3.4.4. The fork () and exec () programs are a child process. Instead of waiting for the process to complete, I want the parent process to clean up the child process (otherwise the process with children becomes a zombie process) asynchronously. This can be done using the SIGCHLD signal. By setting up signal_handler, we can do the cleanup when the child exits. And the code is as follows:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>

int spawn(char *program, char **arg_list){
    pid_t child_pid;

     child_pid = fork();
     if(child_pid == 0){    // it is the child process
        execvp(program, arg_list);
        fprintf(stderr, "A error occured in execvp\n");
        return 0;
     }
     else{
        return child_pid;
     }
}

int child_exit_status;

void clean_up_child_process (int signal_number){
    int status;
    wait(&status);
    child_exit_status = status;     // restore the exit status in a global variable
    printf("Cleaning child process is taken care of by SIGCHLD.\n");
};

int main()
{
    /* Handle SIGCHLD by calling clean_up_process; */
    struct sigaction sigchld_action;
    memset(&sigchld_action, 0, sizeof(sigchld_action));
    sigchld_action.sa_handler = &clean_up_child_process;
    sigaction(SIGCHLD, &sigchld_action, NULL);

    int child_status;
    char *arg_list[] = {    //deprecated conversion from string constant to char*
        "ls", 
        "-la",
        ".",
        NULL
    };

    spawn("ls", arg_list);

    return 0;
}

      

However, when I run the program in the terminal, the parent process never ends. And it doesn't seem to be executing the clean_up_child_process function (since it doesn't print "The child process is cleaned up by SIGCHLD."). What is the problem with this piece of code?

+3


source to share


3 answers


for GNU / Linux users

I've already read this book. Although the book spoke of this mechanism as:

a quote from 3.4.4 p. 59 of the book:

A more elegant solution is to notify the parent process when the child exits.

but he just said that you can use sigaction

to handle this situation.


Here is a complete example of how to handle processes this way.

First, why are we using this mechanism? Well, since we don't want to sync all processes together.

real example
Imagine you have 10 .mp4

files and you want to convert them to files .mp3

. Well, I'm a junior user doing this:

ffmpeg -i 01.mp4 01.mp3 

      

and repeats this command 10 times. Slightly taller users do this:

ls *.mp4 | xargs -I xxx ffmpeg -i xxx xxx.mp3

      

This time, this command transfers all 10 mp4

files on a line, each one by one to xargs

, and then they are converted one by one to mp3

.

But I'm a senior user doing this:

ls *.mp4 | xargs -I xxx -P 0 ffmpeg -i xxx xxx.mp3

      

and that means if I have 10 files, create 10 processes and run them at the same time. And there are BIG different. In the two previous teams, we only had 1 process; it was then completed and then continued to another. But with the option, -P 0

we create 10 processes at the same time and actually execute 10 commands ffmpeg

.


The goal of cleaning up async children is now cleaner. In fact, we want to start several new processes , but the order of these processes and, possibly, the exit status for them does not matter to us. This way we can launch them as quickly as possible and reduce the time.




You can see first man sigaction

for more details.

Second observation of the number of this signal:

T ❱ kill -l | grep SIGCHLD
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP

      

sample code

purpose: use SIGCHLD

to clean up child process

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <wait.h>
#include <unistd.h>

sig_atomic_t signal_counter;

void signal_handler( int signal_number )
{
    ++signal_counter;
    int wait_status;
    pid_t return_pid = wait( &wait_status );
    if( return_pid == -1 )
    {
        perror( "wait()" );
    }
    if( WIFEXITED( wait_status ) )
    {
        printf ( "job [ %d ] | pid: %d | exit status: %d\n",signal_counter, return_pid, WEXITSTATUS( wait_status ) );
    }
    else
    {
        printf( "exit abnormally\n" );
    }

    fprintf( stderr, "the signal %d was received\n", signal_number );
}

int main()
{
    // now instead of signal function we want to use sigaction
    struct sigaction siac;

    // zero it
    memset( &siac, 0, sizeof( struct sigaction ) );

    siac.sa_handler = signal_handler;
    sigaction( SIGCHLD, &siac, NULL );

    pid_t child_pid;

    ssize_t read_bytes = 0;
    size_t  length = 0;
    char*   line = NULL;

    char* sleep_argument[ 5 ] = { "3", "4", "5", "7", "9" };

    int counter = 0;

    while( counter <= 5 )
    {
        if( counter == 5 )
        {
            while( counter-- )
            {
                pause();
            }

            break;
        }

        child_pid = fork();

        // on failure fork() returns -1
        if( child_pid == -1 )
        {
            perror( "fork()" );
            exit( 1 );
        }

        // for child process fork() returns 0
        if( child_pid == 0 ){
            execlp( "sleep", "sleep", sleep_argument[ counter ], NULL );
        }

        ++counter;
    }

    fprintf( stderr, "signal counter %d\n", signal_counter );

    // the main return value
    return 0;
}

      

This is the example code:

  • create 5 child processes
  • then enters the inner loop and suspends signal reception. Cm.man pause

  • then when the child exits, the parent process wakes up and calls the function signal_handler

  • continue to last: sleep 9

output: (17 means SIGCHLD

)

ALP ❱ ./a.out 
job [ 1 ] | pid: 14864 | exit status: 0
the signal 17 was received
job [ 2 ] | pid: 14865 | exit status: 0
the signal 17 was received
job [ 3 ] | pid: 14866 | exit status: 0
the signal 17 was received
job [ 4 ] | pid: 14867 | exit status: 0
the signal 17 was received
job [ 5 ] | pid: 14868 | exit status: 0
the signal 17 was received
signal counter 5

      


when you run this example code, on another terminal, try this:

ALP ❱ ps -o time,pid,ppid,cmd --forest -g $(pgrep -x bash)
    TIME   PID  PPID CMD
00:00:00  5204  2738 /bin/bash
00:00:00  2742  2738 /bin/bash
00:00:00  4696  2742  \_ redshift
00:00:00 14863  2742  \_ ./a.out
00:00:00 14864 14863      \_ sleep 3
00:00:00 14865 14863      \_ sleep 4
00:00:00 14866 14863      \_ sleep 5
00:00:00 14867 14863      \_ sleep 7
00:00:00 14868 14863      \_ sleep 9

      

As you can see, the process a.out

has 5 children. And they work at the same time. Then, whenever the ends, the kernel sends a signal SIGCHLD

to its parent, which is:a.out

Note

If we do not use pause

or some mechanism for the parent to be able wait

for its children, we discard the created processes and the upstart (= on Ubuntu

or init

) becomes the parent. You can try if you removepause()

0


source


The parent process returns immediately from main()

after the child pid returns from fork()

, it never has a chance to wait until it finishes.



+2


source


I am using a Mac, so my answer may not be entirely relevant, but nonetheless. I am compiling without any parameters, hence the executable name a.out

.

I have the same experience with the console (the process doesn't seem to end), but I noticed that it just crashes the terminal, because you can actually just hit Enter and your command line will return, and in fact ps

, executed from another the terminal window does not show a.out

, not ls

which one it launched.

Also if I run ./a.out >/dev/null

it will exit immediately.

So the point above is that everything actually ends, only the terminal freezes for some reason.

Then why he never prints Cleaning child process is taken care of by SIGCHLD.

. Just because the parent process ends before the child. The signal SIGCHLD

cannot be delivered to an already terminated process, so the handler is never called.

The book says that the parent contiunes process does other things and if it does, everything works fine, for example if you add sleep(1)

after spawn()

.

0


source







All Articles