Enquanto um processo permanecer no estado "terminado", seu PCB não será apagado pelo kernel.
[#include <unistd.h>
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> int main () { int retval, x ; x = 0 ; retval = fork () ; printf ("No processo %5d x vale %d\n", getpid(), x) ; if ( retval < 0 ){ perror ("Erro") ; exit (1) ; } else if ( retval > 0 ){ x = 0 ; wait (0) ; } else{ x++ ; sleep (5) ; } printf ("No processo %5d x vale %d\n", getpid(), x) ; exit (0) ;
}]
Para exibir o identificador do processo (pid), pode-se utilizar a chamada getpid().
Ao executar o programa fork-print.c abaixo, o valor da variável x no processo filho é alterado pelo comando x++;
[#include <unistd.h>
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> int main () { int retval, x ; x = 0 ; retval = fork () ; printf ("No processo %5d x vale %d\n", getpid(), x) ; if ( retval < 0 ){ perror ("Erro") ; exit (1) ; } else if ( retval > 0 ){ x = 0 ; wait (0) ; } else{ x++ ; sleep (5) ; } printf ("No processo %5d x vale %d\n", getpid(), x) ; exit (0) ;
}]
Quando um processo está no estado “terminado”, as variáveis do processo são liberadas, apenas o PCB é mantido até que o processo pai pegue o retorno do filho por meio da função wait.
Considerando o programa fork2.c abaixo:
[#include <unistd.h>
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> int main () { int retval=0 ; printf ("Ola, sou o processo %5d\n", getpid()) ; retval = fork () ; //Cria novo processo printf ("[retval: %5d] sou %5d, filho de %5d\n", retval, getpid(), getppid()) ; if ( retval < 0 ) { perror ("Erro") ; exit (1) ; } else if ( retval > 0 ){ int status; int id = wait (&status) ; printf("%d terminou com status %d\n",id,WEXITSTATUS(status)); } else sleep (5) ; printf ("Tchau de %5d!\n", getpid()) ; exit (0) ;
}]
A variável x é única em cada processo e possui endereços físicos distintos na memória RAM.
Considere os programas simple.c e fork-execve1.c respectivamente abaixo:
[#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/resource.h>
int main (int argc, char *argv[], char *envp[]){
printf ("Simple.c - sou %5d, filho de %5d\n", getpid(), getppid()) ;
//o "for" abaixo serve apenas para produzir um tempo de processamento.
for(int i=0;i<1999999999;i++);
struct rusage ru;
//Leitura das informacoes do processo.
getrusage(RUSAGE_SELF, &ru);
printf("\nTempo (modo usuário) %.5f (secs)\n",ru.ru_utime.tv_sec+ ru.ru_utime.tv_usec/1000000.0);
printf("\nTrocas involuntarias: %5ld \nTrocas voluntarias: %5ld\n",
ru.ru_nivcsw,ru.ru_nvcsw);
return 0;
}
]
[#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
/*O terceiro argumento "envp" serve para acessar as variáveis de ambiente do sistema.*/
int main (int argc, char *argv[], char *envp[])
{
int retval ;
//printf("\n%s\n",envp[0]); //Acessar variaveis de ambiente
printf ("Ola, sou o processo %5d\n", getpid()) ;
retval = fork () ;
printf ("[retval: %5d] sou %5d, filho de %5d\n",
retval, getpid(), getppid()) ;
if ( retval < 0 )
{
perror ("Erro: ") ;
exit (1) ;
}
else
if ( retval > 0 ){ //Se pai
int status;
int id = wait (&status) ;
printf("%d terminou com status %d\n",id,status);
}
else{
/*Observe que o processo "simple" possui o mesmo id do processo
filho que acabou de ser criado.*/
execve ("./simple", argv, envp) ;
perror ("Erro");/*Essa mensagem ocorrera' apenas quando houver erro
ao fazer o 'load' do "./simple" */
}
printf ("Tchau de %5d!\n",getpid()) ;
exit (0) ;
}
]
A informação sobre a quantidade de trocas de contexto do processo, exibida pelo simple.c, é armazenada no PCB do próprio processo. A execução do fork-execve1.c criará 1 processo. Quando o comando fork for executado, um 2o. processo será criado. Sendo assim, a execução do fork-execve-1.c criará 2 processos.
Considere o programa fork-execve2.c abaixo:
[#include <unistd.h>
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #define NUM_PROCESSOS 2 int main (int argc, char *argv[], char *envp[]) { int retval,i; printf ("Ola, sou o processo %5d\n", getpid()) ; //Cria NUM_PROCESSOS for (i=0;i<NUM_PROCESSOS;i++){ retval = fork () ; if (retval == 0){ //se processo filho printf ("[retval: %5d] sou %5d, filho de %5d\n", retval, getpid(), getppid()) ; break; } } if ( retval == 0 ) execve ("./simple", argv, envp) ; else for (i=0;i<NUM_PROCESSOS;i++) printf("%d terminou\n",wait(0)); printf ("Tchau de %5d!\n",getpid()) ; exit (0) ;
}]
Ao executar o programa, 3 processos serão criados. O primeiro é o main e depois mais 2 processos (conforme o #define NUM_PROCESSOS 2).
Considere que um programa foi executado e que o pid do processo pai é 1000 e do processo filho (criado por meio do fork) é 1001. Pode-se afirmar que o "pid" do processo que contém o código do simple.c também será 1001. Execute o programa fork-execve1.c demonstrado anteriormente. Note que, para executá-lo será necessário compilar o simple.c. Você pode concluir que como a execução do execve pelo processo filho (linha 30 do programa fork-execve1.c) não gera um novo PCB, então a impressão do pid pelo simple.c (linha 9 do programa simple.c) exibe o mesmo pid.
Enquanto um processo permanecer no estado "terminado", seu PCB não será apagado pelo kernel. O PCB existirá mesmo se o processo estiver no estado "terminado". Inclusive, o SO sabe que um processo está no estado "terminado" pois essa informação está armazenada no PCB. O PCB somente será apagado após o SO liberar toda a memória alocada pelo processo e, principalmente, "entregar" o retorno do processo (return 0) ao processo pai. Enquanto o processo pai não pegar o retorno do filho, o PCB não será liberado. Se o processo pai existir e não pegar o retorno do filho, o filho irá se tornar um processo denominado zombie! Você saberia como criar e visualizar um processo zombie? Para visualizar você pode utilizar o comando ps -aux
O programa simple.c visto anteriormente possui um comando "for" na linha 11. Ao aumentar a quantidade de vezes que o "for" executa, a quantidade de trocas de contexto involuntárias poderá aumentar. Trocas de contexto involuntárias ocorrem quando o processo "gostaria" de permanecer no processador, mas seu tempo terminou. Trocas de contexto voluntárias ocorrem quando o próprio processo libera o processador antes de terminar o quantum de tempo dele. Essa liberação pode ocorrer, por exemplo, para executar uma entrada/saída ou para aguardar o término de um processo filho (como ocorre quando a função wait() é chamada) ou até mesmo quando o processo termina (com o comando return da função main).
A chamada execve não duplica o processo pai. Muito pelo contrário, ele sobrescreve as áreas de código, dados, heap e pilha do processo pai.
Ao compilar e executar o programa fork2.c, 2 processos serão criados. O primeiro processo será gerado ao executar o programa pelo terminal. O segundo processo será gerado pelo comando fork().
No programa fork2.c, a chamada do comando fork() criará um novo PCB (ou TCB) no kernel. Para armazenar as informações sobre o novo processo criado pelo comando fork(), o kernel criará um novo PCB (ou TCB).
Ao executar o programa fork-print.c, o valor da variável x no processo pai será 0. Pois a variável x no processo pai não é alterada.
A chamada execve() não cria um novo PCB que conterá as informações do novo processo. A chamada execve() utiliza o mesmo PCB do processo que a chamou.
No programa fork2.c, o processo que executar a função wait(&status) será colocado no estado de Suspenso e somente sairá deste estado quando um processo filho terminar. O processo que chama a função wait() se coloca em espera. Note que esse é um tipo de "trap", ou seja, o próprio processo interrompe sua execução. Quando um processo filho termina (pode ser qualquer filho), o processo pai receberá o retorno do filho. Lembra que em C escrevemos "int main(){.... return 0;}"? O retorno da função main será enviado ao processo pai.
Quando um processo chama da função sleep(), uma troca de contexto voluntária acontecerá. Ao executar a função sleep, o processo passa do estado de executando para suspenso. Essa mudança de estado ocorre a pedido do próprio processo. Por este motivo, ela é chamada de voluntária.
No programa fork2.c, a função wait (&status) será executada apenas pelo processo filho. A função wait (&status) será chamada apenas pelo pai pois está dentro do "if (retval > 0)". Somente o processo pai terá o valor de retval maior do que 0.
COMENTÁRIOS