[英]How to properly wait for foreground/background processes in my own shell in C?
在此之前的問題,我最貼出我自己的shell代碼。 我的下一步是實現前台和后台進程執行並正確等待它們終止,這樣它們就不會像“僵屍”那樣停留。
在添加在后台運行它們的可能性之前,所有進程都在前台運行。 為此,我在使用execvp()執行任何進程后簡單地調用wait(NULL)。 現在,我檢查'&'字符作為最后一個參數,如果它在那里,通過不調用wait(NULL)在后台運行該進程,並且進程可以在后台運行愉快我將返回到我的shell。
這一切都正常(我認為),現在的問題是,我還需要以某種方式調用wait()(或waitpid()?),以便后台進程不會保持“僵屍”。 這是我的問題,我不知道該怎么做......
我相信我必須處理SIGCHLD並在那里做一些事情,但是我還沒有完全理解何時發送SIGCHLD信號,因為我還試圖將wait(NULL)添加到childSignalHandler()但是它不起作用因為我一旦在后台執行了一個進程,調用了childSignalHandler()函數,因此等待(NULL),這意味着在“后台”進程完成之前我無法對我的shell做任何事情。 由於信號處理程序中的等待,它不再在后台運行。
我在這一切中缺少什么?
最后一件事,這個練習的一部分我還需要打印進程狀態的變化,比如進程終止。 因此,對此的任何見解也非常感激。
這是我目前的完整代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
#include <signal.h>
#include <sys/types.h>
#include "data.h" // Boolean typedef and true/false macros
void childSignalHandler(int signum) {
//
}
int main(int argc, char **argv) {
char bBuffer[BUFSIZ], *pArgs[10], *aPtr = NULL, *sPtr;
bool background;
ssize_t rBytes;
int aCount;
pid_t pid;
//signal(SIGINT, SIG_IGN);
signal(SIGCHLD, childSignalHandler);
while(1) {
write(1, "\e[1;31mmyBash \e[1;32m# \e[0m", 27);
rBytes = read(0, bBuffer, BUFSIZ-1);
if(rBytes == -1) {
perror("read");
exit(1);
}
bBuffer[rBytes-1] = '\0';
if(!strcasecmp(bBuffer, "exit")) {
exit(0);
}
sPtr = bBuffer;
aCount = 0;
do {
aPtr = strsep(&sPtr, " ");
pArgs[aCount++] = aPtr;
} while(aPtr);
background = FALSE;
if(!strcmp(pArgs[aCount-2], "&")) {
pArgs[aCount-2] = NULL;
background = TRUE;
}
if(strlen(pArgs[0]) > 1) {
pid = fork();
if(pid == -1) {
perror("fork");
exit(1);
}
if(pid == 0) {
execvp(pArgs[0], pArgs);
exit(0);
}
if(!background) {
wait(NULL);
}
}
}
return 0;
}
waitpid()
有多種選項可以幫助您(POSIX標准引用):
WCONTINUED
waitpid()函數應報告由pid指定的任何繼續子進程的狀態,該進程的狀態自作業控制停止后繼續報告。
WNOHANG
如果狀態不能立即用於由pid指定的其中一個子進程,則waitpid()函數不應掛起調用線程的執行。
特別是,WNOHANG將允許您查看是否有任何屍體可以收集而不會導致您的進程阻止等待屍體。
如果調用進程設置了SA_NOCLDWAIT或SIGCHLD設置為SIG_IGN,並且進程沒有被轉換為僵屍進程的未被等待的子進程,則調用線程將阻塞,直到包含調用線程的進程的所有子進程終止,並且wait()和waitpid()將失敗並將errno設置為[ECHILD]。
你可能不想忽略SIGCHLD等,你的信號處理程序應該設置一個標志來告訴你的主循環“糟糕;有死孩子 - 去收集那個屍體!”。
SIGCONT和SIGSTOP信號也將與您相關 - 它們分別用於重新啟動和停止子進程(在此上下文中,無論如何)。
我建議看看Rochkind的書或史蒂文斯的書 - 他們詳細介紹了這些問題。
你可以使用:
if(!background)
pause();
這樣,進程就會阻塞,直到它收到SIGCHLD
信號,並且信號處理程序將執行等待。
這應該讓你開始。 主要區別在於我擺脫了子處理程序並在主循環中添加了waitpid
並提供了一些反饋。 測試和工作,但顯然需要更多的TLC。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <wait.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc, char **argv) {
char bBuffer[BUFSIZ], *pArgs[10], *aPtr = NULL, *sPtr;
int background;
ssize_t rBytes;
int aCount;
pid_t pid;
int status;
while(1) {
pid = waitpid(-1, &status, WNOHANG);
if (pid > 0)
printf("waitpid reaped child pid %d\n", pid);
write(1, "\e[1;31mmyBash \e[1;32m# \e[0m", 27);
rBytes = read(0, bBuffer, BUFSIZ-1);
if(rBytes == -1) {
perror("read");
exit(1);
}
bBuffer[rBytes-1] = '\0';
if(!strcasecmp(bBuffer, "exit"))
exit(0);
sPtr = bBuffer;
aCount = 0;
do {
aPtr = strsep(&sPtr, " ");
pArgs[aCount++] = aPtr;
} while(aPtr);
background = (strcmp(pArgs[aCount-2], "&") == 0);
if (background)
pArgs[aCount-2] = NULL;
if (strlen(pArgs[0]) > 1) {
pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
} else if (pid == 0) {
execvp(pArgs[0], pArgs);
exit(1);
} else if (!background) {
pid = waitpid(pid, &status, 0);
if (pid > 0)
printf("waitpid reaped child pid %d\n", pid);
}
}
}
return 0;
}
編輯:使用WNOHANG對waitpid()
添加信號處理並不困難。 就像將waitpid()
東西從循環頂部移動到信號處理程序一樣簡單。 但是你應該知道兩件事:
首先,即使是“前台”流程也會發送SIGCHLD。 由於只能有一個前台進程,因此如果要對前景與背景進行特殊處理,則可以將前景pid(父對象的fork()
返回值)存儲在信號處理程序可見的變量中。
其次,您當前正在標准輸入上阻塞I / O read()
主循環頂部的read()
)。 當SIGCHLD發生時,極有可能在read()
上被阻塞,導致系統調用中斷。 根據操作系統,它可能會自動重新啟動系統調用,或者它可能會發送您必須處理的信號。
我沒有使用全局變量,而是考慮了一個不同的解決方案:
if(!background) {
signal(SIGCHLD, NULL);
waitpid(pid, NULL, 0);
signal(SIGCHLD, childSignalHandler);
}
如果我正在運行前台進程“刪除”SIGCHLD的處理程序,那么它就不會被調用。 然后,在waitpid()之后,再次設置處理程序。 這樣,只處理后台進程。
你覺得這個解決方案有什么問題嗎?
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.