簡體   English   中英

異步清理子進程

[英]Cleaning up children processes asynchronously

這是< 高級Linux編程 >第3.4.4節中的示例。 程序fork()和exec()是一個子進程。 我希望父進程能夠異步清理子進程(否則子進程將成為一個僵屍進程),而不是等待進程的終止。 可以使用信號SIGCHLD完成。 通過設置signal_handler,我們可以在子進程結束時完成清理工作。 代碼如下:

#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;
}

但是,當我在終端中運行程序時,父進程永遠不會結束。 而且它似乎沒有執行函數clean_up_child_process(因為它沒有打印出“清潔子進程由SIGCHLD處理。”)。 這段代碼有什么問題?

父進程在從fork()返回子pid后立即從main()返回,它永遠不會有機會等待子進程終止。

我正在使用Mac,所以我的答案可能不太相關,但仍然如此。 我沒有任何選項編譯,所以可執行名稱是a.out

我對控制台有相同的經驗(過程似乎沒有終止),但我注意到它只是終端故障,因為你實際上只需按Enter鍵,你的命令行就會回來,實際上是ps從其他終端執行窗口不顯示a.out ,也不顯示它啟動的ls

此外,如果我運行./a.out >/dev/null它會立即完成。

所以上述觀點是一切都實際終止,只是終端由於某種原因凍結。

接下來,為什么它從不打印Cleaning child process is taken care of by SIGCHLD. 僅僅因為父進程在子進程之前終止。 SIGCHLD信號無法傳遞到已經終止的進程,因此永遠不會調用處理程序。

在書中,它表示父進程繼續做其他一些事情,如果確實如此,那么一切正常,例如,如果你在spawn()之后添加sleep(1) spawn()

適用於GNU / Linux用戶

我已經讀過這本書了。 雖然這本書談到這個機制是:

引自本書3.4.4第59頁:

更優雅的解決方案是在子進程終止時通知父進程。

但它只是說你可以使用sigaction來處理這種情況。


以下是如何以這種方式處理流程的完整示例。

首先為什么我們使用這種機制? 好吧,因為我們不希望將所有進程同步在一起。

真實的例子
想象一下,你有10個.mp4文件,你想將它們轉換為.mp3文件。 好吧,我的初級用戶這樣做:

ffmpeg -i 01.mp4 01.mp3 

並重復此命令10次。 更高的用戶這樣做:

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

這一次,這個命令管道每行10個mp4文件,每個文件一個一個地連接xargs ,然后逐個轉換為mp3

但我的高級用戶這樣做:

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

這意味着如果我有10個文件,則創建10個進程並同時運行它們。 而有的不同。 在之前的兩個命令中,我們只有一個進程; 它被創建然后終止然后繼續到另一個。 但是在-P 0選項的幫助下,我們同時創建了10個進程,實際上正在運行10個ffmpeg命令。


現在, 異步清理兒童的目的變得更加清潔了。 事實上,我們想要運行一些新流程,但這些流程的順序以及它們的退出狀態對我們來說無關緊要。 通過這種方式,我們可以盡可能快地運行它們並減少時間。


首先,你可以看到man sigaction以獲得你想要的更多細節。

第二次看到這個信號號碼:

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

示例代碼

目標:使用SIGCHLD清理子進程

#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;
}

這是示例代碼的作用:

  1. 創建5個子進程
  2. 然后進入內部循環並暫停接收信號。 man pause
  3. 然后當子進程終止時,父進程喚醒並調用signal_handler函數
  4. 繼續到最后一個: sleep 9

輸出:(17表示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

當你運行這個示例代碼時,在另一個終端上試試這個:

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

正如你所看到的, a.out進程有5個孩子。 它們同時運行。 然后, 每當它們終止時, 內核將信號SIGCHLD發送到它們的父節點: a.out

注意

如果我們不使用pause或任何機制以便父級可以wait其子級,那么我們將放棄創建的進程,並且upstart (=在Ubuntuinit )成為它們的父級。 你可以嘗試刪除pause()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM