Escolar Documentos
Profissional Documentos
Cultura Documentos
IDENTIFICAÇÃO
DISCIPLINA: SISTEMAS OPERACIONAIS CÓDIGO:
PROFESSOR: RENÊ GUSMÃO PERÍODO:
Para criar um novo processo a partir do processo em execução (de maneira semelhante ao início do
shell script a partir da execução do shell script), o programador UNIX pode usar a função fork() da
linguagem C disponível na API UNIX/Linux definida pelo padrão POSIX. A implementação típica
da criação de um novo processo com essa função é assim:
int main()
{
pid_t justPid, myPid, myParentPid; /* pid_t is defined in types.h */
if ( justPid == 0 )
{ /* Child process */
myPid = getpid(); /* To get PID of this process */
myParentPid = getppid(); /* To get the PID of parent process – PPID */ /* Rest of the code which runs as child
process. */
...
return( 0 );
}
else
{ /* Parent process - the value returned by fork() is child's PID */
myPid = getpid();
myParentPid = getppid();
/* Rest of the code which runs as parent process. */
...
return( 0 );
}
}
O processo filho recém-criado como uma imagem binária na memória, ou seja, é criado como um
clone de seu pai. Isso significa que ele herda as cópias de todas as variáveis criadas no pai (justPid,
myPid, myParentPid, no exemplo dado acima) com seus valores no momento da invocação da
função fork (). O mesmo é verdade no que diz respeito às funções implementadas no programa pai
(função main () neste caso).
Para trocar o código binário do processo filho por algo diferente do código do programa
principal (pai), o programador pode usar a função da família exec (), que carrega o arquivo binário
dado como a imagem do filho:
justPid = fork();
...
if ( justPid == 0 )
{ /* Child process */
/* Loading new code (program) for child process and starting it */
execlp( "./ex_2-2b", "ex_2-2b", NULL );
}
else
{ /* Parent process */
...
Diferentes estratégias podem ser aplicadas quando vários processos relacionados estão
trabalhando juntos. Por exemplo: eles podem ser executados de forma independente e cada um
deles faz seu próprio trabalho, ou o pai pode esperar que seus processos filhos não façam nada e, em
seguida, fazer uso dos resultados fornecidos pelos processos filhos. Essa segunda estratégia é
implementada com a função wait () usada no processo pai. O código apropriado na variante mais
simples pode ter a seguinte aparência:
justPid = fork();
...
if ( justPid == 0 )
{ /* Child process */
printf( "[CHILD]: Child %d started.\n",
getpid() );
...
return( 0 );
}
else
{ /* Parent process waits for the end of child process: */
wait ( NULL );
printf( "[PARENT]: Child %d finished.\n", justPid );
}
...
Algumas observações interessantes podem ser realizadas com o seguinte exemplo de
programa:
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t justPid, myPid, myParentPid;
int exitStatus;
if ( justPid < 0 )
{ /* Error occurred - no child process. */
fprintf( stderr, "Fork failed." );
return 1;
}
if ( justPid == 0 )
{ /* Child process. */ myPid = getpid();
myParentPid = getppid();
printf( "[CHILD]: PID = %d PPID = %d\n", myPid, myParentPid );
/* Blind loop - signal like SIGINT is needed to exit from process. */
while ( 1 )
{
/* Empty loop - child process "works" */ ;
}
}
else
{ /* Parent process - the value returned by fork() is child's PID. */
myPid = getpid();
myParentPid = getppid();
printf( "[PARENT]: PID = %d PPID = %d Child's PID = %d\n", \ myPid, myParentPid,
justPid );
/* Wait for particular child process end. */
waitpid( justPid, &exitStatus, 0 );
if( WIFEXITED(exitStatus) )
printf("[PARENT]:Child process PID = %d exited with status %d\n", justPid, WEXITSTATUS(exitStatus));
if( WIFSIGNALED(exitStatus) )
printf("[PARENT]:Child process PID=%d terminated by signal %d\n",justPid,
WTERMSIG(exitStatus));
/* Then we can do something more (reminder section). */ while ( 1 )
{
/* Empty loop - parent process "works" */
;
}
return( 0 );
}
}
2. Criando um processo leve (thread) com as funções da API do UNIX.
Para criar um encadeamento de acordo com o programador padrão POSIX, é possível usar a
função da linguagem C pthread_create () disponível na biblioteca pthread da Interface do
Programador de Aplicativos UNIX. Esta preparação de tempo e inicialização apropriada do
ambiente necessário para manter o segmento ativo sob seu controle de processo pai é necessário.
Portanto, as funções pthread_attr_init () e pthread_attr_setscope () devem ser chamadas antes. A
implementação típica da criação de dois novos encadeamentos é assim:
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
void *threadCode1()
{ /* The code which should be run as thread #1 */
...
}
void *threadCode2()
{ /* The code which should be run as thread #2 */
...
}
int main()
{
pthread_t threadId1, threadId2;
pthread_attr_t threadAttr1, threadAttr2;
int main(){
signal( SIGINT, myInt ); /* Setting a new handler for SIGINT */
Tente executar o programa como descrito acima (modelo de um processo orientado por CPU)
em primeiro plano e mate-o usando Ctrl + C para enviar o sinal SIGINT, execute-o em segundo
plano e use o comando kill SIGINT <PID> depois de verificar o valor PID .
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
saved_errno = errno;
while ( waitpid( (pid_t)-1, 0, WNOHANG ) > 0 ) {
}
errno = saved_errno;
printf( "Process %d received SIGCHLD.\n", getpid() );
}
}
int main()
{
pid_t justPid, myPid, myParentPid;
/* Setting custom handler for SIGTERM, SIGINT and SIGCHLD */ signal( SIGTERM,
mySignal );
signal( SIGINT, mySignal );
signal( SIGCHLD, mySignal );
Leia o manual (man) sobre as funções de API UNIX/Linux fork(), getpid(), getppid(). Em seguida,
escreva um programa que crie um processo filho e, em seguida, pai e filho estejam executando em
loops infinitos exibindo seus PIDs e PPIDs. Dessa forma, você terá os modelos de processos típicos
orientados a E/S. Realize algumas observações, como o tempo de CPU é compartilhado entre esses
dois processos e como eles estão reagindo para Ctrl + C (SIGINT) ou sinal SIGTERM padrão
(enviado da linha de comando pelo comando kill shell quando executado em segundo plano). A tela
de saída pode ficar assim:
Exercício 2.
Leia o manual sobre as funções wait() e exec(). Então escreva um programa que crie um
processo filho, carregue um novo código binário (compilado de outra fonte) no filho, e então o pai
esperará (sem CPU ou E/S) até que o filho termine sua tarefa (contando de 1 a 100 e exibindo o
status do contador, por exemplo). A tela de saída pode ficar assim:
[PARENT]: PID 2421, waits for child with PID 2422
[CHILD]: PID 2421, starts counting:
[CHILD]: i = 1
[CHILD]: i = 2
...
[CHILD]: i = 100
[PARENT]: Child with PID 2422 finished and unloaded.
Exercício 3.
Leia o manual sobre as funções kill() e signal(). Então escreva um programa que cria um
processo filho, então pai e filho estão executando em loops incrementando suas próprias cópias do
contador declarado como variável pública (global) sem exibi-lo (modelos de um processo orientado
pela CPU) até o sinal SIGINT (Ctrl + C) mata os dois. O manipulador personalizado para o sinal
SIGINT deve exibir o valor do contador e sair do processo. A tela de saída pode ficar assim:
Realize vários experimentos com diferentes períodos de tempo de execução e tente tirar
algumas conclusões sobre a qualidade da multitarefa de compartilhamento de tempo em seu sistema
operacional: os processos são tratados igualmente pelo escalonador de processos?
Exercício 4.
Leia o manual sobre as funções pthread_attr_init(), pthread_attr_setscope() e
pthread_create(). Em seguida, escreva o programa que cria dois threads da seguinte forma:
primeiro deles incrementa a variável comum no loop infinito (variáveis globais desta vez não
são clonadas, mas acessíveis em uma instância para o processo principal e threads), o segundo
thread decrementa a mesmo variável. Ambos devem também contar o número de suas
iterações, o mesmo deve fazer o processo principal (três contadores diferentes). Todos eles
devem ser orientados a CPU (isso significa trabalhar sem qualquer saída para o terminal). O
final do processamento será causado pelo sinal SIGINT. O manipulador personalizado de
SIGINT deve exibir o valor final da variável comum (qual segmento é o vencedor?) E
números de iterações para as threads e processo principal. A tela de saída pode ficar assim:
[MAIN]: PID = 8902, i = 396891444
[MAIN]: main segment iterations: 506370425
[MAIN]: incrThread iterations: 732214172
[MAIN]: decrThread iterations: 409308229
Realize vários experimentos com diferentes períodos de tempo de execução e tente fazer
algumas conclusões se os threads e o processo principal são tratados de forma mais ou menos
igual.
Tente usar as configurações PTHREAD_SCOPE_PROCESS e
PTHREAD_SCOPE_SYSTEM.
Etapa 2: relatório
Escreva um relatório descrevendo, detalhadamente, o que foi feito nas duas etapas anteriores.
O intuito aqui é demonstrar, textualmente, a compreensão dos conceitos e capacidade de
aplicá-los.