[英]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()
。
我已經讀過這本書了。 雖然這本書談到這個機制是:
引自本書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;
}
這是示例代碼的作用:
man pause
signal_handler
函數 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 (=在Ubuntu
或init
)成為它們的父級。 你可以嘗試刪除pause()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.