Como esperar corretamente para processos de primeiro plano / fundo na minha própria concha em C?
-
23-08-2019 - |
Pergunta
Na esta pergunta anterior eu postei mais do meu próprio código shell. Meu próximo passo é implementar a execução do primeiro plano e processo de fundo e devidamente esperar por eles para terminar de modo que não fique como "zumbis".
Antes de adicionar a possibilidade de executá-los no fundo, todos os processos foram executado em primeiro plano. E, para isso, eu simplesmente chamada wait (NULL) depois de executar qualquer processo com execvp (). Agora, eu verificar o caractere '&' como o último argumento e se ele está lá, executar o processo em segundo plano não chamando wait (NULL) e o processo pode ser executado feliz no fundo irá Eu estou voltou a minha concha.
Isto é tudo a funcionar correctamente (eu acho), o problema agora é que eu também preciso de espera de chamada () (ou waitpid ()?) De alguma forma para que o processo de fundo não permanece "zombie". Esse é o meu problema, eu não tenho certeza de como fazer isso ...
Eu acredito que eu tenho que lidar com SIGCHLD e fazer algo lá, mas eu ainda tenho que entender completamente quando o sinal SIGCHLD é enviado porque eu tentei adicionar também wait (NULL) para childSignalHandler (), mas não funcionou porque, como assim que eu executado um processo em segundo plano, a função childSignalHandler () foi chamado e, consequentemente, o tempo de espera (NULL), ou seja, eu não podia fazer nada com minha concha até que o processo "background" terminou. Que não estava funcionando no fundo mais por causa da espera no manipulador de sinal.
O que estou ausente em tudo isso?
Uma última coisa, parte deste exercício Eu também preciso imprimir as alterações do estatuto processos, como o encerramento do processo. Então, qualquer visão sobre o que é também muito apreciado.
Este é o meu código completo no momento:
#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;
}
Solução
Existem várias opções para waitpid()
para ajudá-lo (citações do padrão POSIX):
WCONTINUED
A função waitpid () deve comunicar o status de qualquer processo filho contínuo especificado por pid cujo status não foi relatado, uma vez que continuou de uma paragem de controle de trabalho.
WNOHANG
A função waitpid () não deve suspender a execução do segmento de chamada se o estado não está imediatamente disponível para um dos processos filho especificados por pid.
Em particular, WNOHANG irá permitir que você veja se existem quaisquer cadáveres para recolher sem causar o seu processo para bloquear à espera de um cadáver.
Se o processo de chamada tem um conjunto SA_NOCLDWAIT ou tem um conjunto SIGCHLD para SIG_IGN, eo processo não tem unwaited-para as crianças que foram transformadas em processos zombie, o segmento de chamada deve bloquear até que todos os filhos do processo que contém o segmento de chamada terminar, e wait () e waitpid () deve falhar e setar errno para [ECHILD].
Você provavelmente não quer estar ignorando SIGCHLD, etc, e seu manipulador de sinal provavelmente deve ser a criação de uma bandeira de dizer ao seu loop principal. "Oops!; Há criança morta - vão cobrar que o cadáver"
Os sinais SIGCONT e SIGSTOP também será de importância para você -. Eles são usados ??para reiniciar e parar um processo filho, respectivamente (neste contexto, de qualquer modo)
Eu recomendo a olhar para o livro de Rochkind ou livro Stevens' - eles cobrem estas questões em detalhe
.Outras dicas
Este deve começar. A principal diferença é que eu me livrei do manipulador criança e acrescentou waitpid
no circuito principal com algum feedback. Testado e funcionando, mas, obviamente, precisa de mais 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;
}
EDIT: Adicionando volta na manipulação de sinal não é difícil com waitpid()
usando WNOHANG. É tão simples como mover o material waitpid()
a partir do topo do loop para o manipulador de sinal. Você deve estar ciente de duas coisas, no entanto:
Em primeiro lugar, até mesmo processos de "primeiro plano" enviará SIGCHLD. Uma vez que não pode ser apenas processo de um plano você pode simplesmente armazenar o pid primeiro plano (valor de retorno do pai de fork()
) de modo visível variável para o manipulador de sinal, se você quer fazer um tratamento especial do fundo plano vs..
Em segundo lugar, você está fazendo atualmente o bloqueio I / O na entrada padrão (o read()
no topo loop principal). Você é extremamente provável a ser bloqueado em read()
quando SIGCHLD ocorre, resultando em uma chamada de sistema interrompida. Dependendo OS pode reiniciar a chamada de sistema automaticamente, ou pode enviar um sinal de que você deve lidar.
Você pode usar:
if(!background)
pause();
Desta forma, os blocos de processo até que ele recebe o sinal SIGCHLD
, eo manipulador de sinal vai fazer as coisas espera.
Em vez de usar uma variável global, pensei em uma solução diferente:
if(!background) {
signal(SIGCHLD, NULL);
waitpid(pid, NULL, 0);
signal(SIGCHLD, childSignalHandler);
}
Se eu estou correndo um processo em primeiro plano "apagar" o manipulador para SIGCHLD para que ele não obter chamado. Então, depois de waitpid (), definir o manipulador de novo. Desta forma, apenas os processos em segundo plano serão tratadas.
Você acha que há algo de errado com essa solução?