Processos
- Consiste num método de
descrição das actividades de um sistema operativo;
- Todo o software incluído
no sistema operativo é organizado num grupo de programas executáveis. Cada um
destes programas quando activo forma um processo juntamente com o
ambiente de execução associado;
- Cada processo pode
"correr" num processador diferente, mas na prática é utilizada a
multi-programação (cada processador "corre" vários processos), logo o sistema
operativo deve fazer o escalonamento de processos de forma a ser efectuada a
comutação de processos;
- Os sistemas operativos
baseados neste modelo, fornecem primitivas para a criação e destruição de
processos;
- Cada processo pode criar
por sua vez processos filho independentes, podendo este continuar a sua
execução concorrentemente com eles, ou então bloquear até à sua
conclusão.
Estados principais
de um Processo:
- (1) Activo;
- (2) Bloqueado;
- (3) Pronto a executar;
São possíveis quatro transições:
- (1)
®
(2) : o processo não pode continuar porque está à espera de I/O (ex: teclado,
ler/escrever num ficheiro, etc.);
- (1)
®
(3) : o processo ultrapassou o tempo de CPU ( o tempo que o processo tem para
estar em execução);
- (3)
®
(1) : todos os processos já executaram e está na altura do primeiro
processo voltar a executar;
- (2)
®
(3) : o evento pelo qual o processo foi bloqueado já "chegou" e é necessário
desbloquear o processo de forma a estar pronto para ser executado. Se nenhum
processo está activo, então este processo é executado.
Contexto de um
Processo
É toda a informação
necessária à descrição completa do estado actual de um processo:
- PCB (Bloco de Controlo
do Processo)
- Identificação do
processo, grupo, etc;
- Informação sobre o
escalonamento: estado, prioridade, etc;
- Localização e tamanho
dos contextos de memória, de dados, de stack, ficheiros e código;
- Registos de controlo;
- Código;
- Dados do programa;
- Stack;
- Descritores de
ficheiros.
Nível de um Processo
Resulta do facto de um
processo depender de outro, ser colocado a um nível inferior:
- Nível 1 : processos do
utilizador - programas do utilizador que usam serviços do supervisor;
- Nível 2 : processos do
supervisor - executam funções como gestão de memória, ficheiros,
escalonamento, etc;
- Nível 3 : I/O -
respondem tipicamente a interrupts;
- Nível 4 : excepções
: executam acções de manutenção do sistema (os erros e excepções
são tratados aqui).
Gestão de Processos
Processos que executam
funções como :
- criação e eliminação
de processos;
- protecção de
processos;
- sincronização de
processos;
- comutação e
escalonamento de processos.
Protecção de
Processos
De modo a evitar que um
processo do utilizador tenha acesso a um processo do supervisor,
ou mesmo de outros utilizadores, é necessário garantir a integridade do sistema.
Esta integridade deve basear-se em duas partes:
- Segurança
- prevenção dos craches;
- Privacidade
- acesso controlado.
Sincronização de
Processos
A sincronização de
processos é necessária para controlar a ordem em que cada processa opera
sobre os dados e recursos partilhados.
Comutação de
Processos
Os processos do
utilizador cooperam com processos de mais alto nível de prioridade, actuando
concorrentemente sobre o mesmo CPU. É necessário escolher o melhor
algoritmo de escalonamento para a atribuição de tempo de CPU a cada processo.
A comutação deve ter em
conta vários aspectos:
- Justiça : cada
processo deve ter a sua "fatia" de tempo de CPU;
- Eficiência : 100% de
utilização do CPU;
- Tempo de Resposta:
minimizar o tempo de resposta de sistemas interactivos;
- Throughput
- maximizar o número de processos por hora.
Uma das técnicas mais usadas é a preempção
( técnica de suspender processos durante um período de tempo).
System Calls para manuseamento de Processos
fork()
- única maneira de criar um processo em Unix.
- Cria uma cópia exacta do
processo original (código, dados, stack, descritor de ficheiros, etc), criando
uma relação pai-filho , mas seguindo ambos "caminhos"
diferentes;
- Todas as variáveis têm
valores idênticos até à execução do fork, mas qualquer alteração feita
em um, não se reflecte no outro, a partir dessa altura;
- O uid (User
Identifier) do pai é copiado para o filho;
- Ao filho é
atribuído um novo pid (Process Identifier);
- Geralmete o pai e
o filho precisam de executar código diferente, logo o fork
retorna 0 para o filho e retorna o pid do filho
para o pai, sendo assim possível saber-se em qualquer altura qual dos
dois processos está a ser executado.
n = wait (estado)
- obriga o pai a esperar pela terminação de qualquer processo. A n
é associado o pid do primeiro processo filho que terminar e o
respectivo valor de retorno é associado a estado.
exit(estado)
- termina um processo filho, retornando estado como valor de
saída. O estado é guardado na tabela de processos, na qual o respectivo
processo à assinalado com Zombie, até o pai o ir buscar através da
system call wait (ou quando terminar a sua execução) ,
originando assim uma eliminação definitiva da tabela de processos.
n=getpid()
- associa a n o valor do pid do processo corrente.
n= getppid()
- associa a n o valor do pid do pai do processo corrente.
sleep(n)
- adormece o processo corrente durante n segundos.
a = WEXITSTATUS(n)
- a variável a fica com o valor dos 8 bits menos significativos de n.
Funcionamento de Algumas System Calls
fork
( int pid = fork() )
- Verifica se há lugar na
Tabela de Processos;
- Tenta alocar memória
para o filho;
- Altera mapa de memória e
copia para a tabela;
- Copia imagem do pai
para o filho;
- Copia para a tabela de
processos a informação do processo pai (pid, prioridades, estado, etc);
- Atribui um pid ao
filho
- Informa o Kernel
(Núcleo do Sistema Operativo) e o sistema de ficheiros que foi criado um novo
processo.
exit (exit(int estado) )
O funcionamento depende do
pai estar à espera ou não.
- Se o pai está à
espera, então a entrada na tabela é limpa, e o espaço de memória é
desalocado (o processo termina definitivamente);
- Se o pai não está
à espera, o processo fica Zombie, activando um bit na entrada
correspondente da tabela; o scheduler (o responsável pelo escalonamento
dos processos) recebe uma mensagem de modo a evitar o processo.
- Se o processo pai
morre antes do filho fazer exit, e de modo a evitar que o
processo fique Zombie eternamente, este é adoptado pelo processo tty
(processo que "lê" o login) correspondente, já que este está sempre a
fazer wait.
wait
(int pid=wait(int *estado) )
Obriga o processo a esperar
pelo fim de um filho de modo a libertar o espaço correspondente na
tabela e originar uma eliminação definitiva.
- Obriga a uma pesquisa da
tabela de processos, para descobrir se existe algum processo filho;
- Se existir um processo
filho Zombie, ele é limpo da tabela de processos e o seu
pid é retornado para o wait, assim como o argumento do exit
do processo Zombie;
- Se não houver nenhum
filho Zombie, e existir algum filho no scheduler,
então o processo é bloqueado até o filho executar o exit;
- Se não existir nenhum
filho na tabela de processos, o wait retorna um valor de erro.
Nota: Se um processo
estiver em wait e receber um sinal, este desbloqueia de imediato.
Exemplo
#include
<unistd.h>
#include
<sys/wait.h>
#include <sys/types.h>
main()
{
pid_t pid,
pid2;
int
estado;
pid = fork(); /* Cria um PROCESSO */
if (pid < 0)
{
printf("Erro ao criar
o processo\n");
exit(-1);
}
else
if (pid >0) /* Código
que só vai ser executado pelo Processo PAI */
{
printf("Eu sou o
PAI\n");
pid2 = wait(&estado);
printf("O filho com o
pid %d terminou\n",pid2);
}
else /* Código que só
vai ser executado pelo Processo FILHO */
printf("Processo
Filho\n");
printf("REPETIDO\n");
}
Exercícios:
1- Considere a seguinte parte de um programa:
...
p =
fork();
a =
fork();
printf("Sistemas Operativos II\n");
...
a)
Quantas vezes é apresentada "Sistemas Operativos II" ?
Justifique.
b)
Quantos processos são criados pelo programa?
2- Faça um programa
que crie 2 processos e:
- Escreve "Eu sou o pai" no processo pai.
- Escreve "Eu sou o 1º filho" no primeiro
filho.
- Escreve "Eu sou o 2º filho" no segundo filho.
3- Considere o seguinte pedaço de código em C:
…
for (i=0; i<4; i++ )
pid = fork();
printf(“SOP2\n”);
…
a) Quantos processos são criados por este programa? Justifique.
b) Quantas vezes é apresentado “SOP2”? Justifique.
4- Considere a seguinte parte de um programa:
main()
{
...
pid2=0;
for(i=0;i<2;i++)
{
pid=fork();
if (pid==0)
pid2=fork();
if (pid2==0)
printf(“Exame de SO2\n”);
}
...
}
Quantas vezes é apresentada a frase “Exame de SO2” ?
Justifique.
5– Considere a seguinte parte de um programa:
main ()
{
...
for(i=0; i<2: i++)
{
pid1=fork();
pid2=fork();
if (pid1 > 0)
printf("Sistemas\n");
if (pid2 == 0)
printf("Operativos\n");
}
...
}
Quantas vezes são apresentadas as palavras "Sistemas" e
"Operativos"? Justifique.
Nota: Considere que a função fork() nunca devolve valores
menores que zero.
6- Tendo um array de inteiros com mil posições, crie
um processo e faça o seguinte:
- Os dois processos (pai e filho) fazem o
seguinte cálculo: resultado[i] = dados[i]*4 + 20.
- Cada processo é responsável pelo cálculo de 500
posições do array dados, colocando o resultado no array resultado.
- Os resultados devem ser apresentados segundo a ordem
do array (de 0 a 499 e depois de 500 a 999).
7- Implemente um programa que obtenha a “funcionalidade” apresentada no
seguinte diagrama:

Nota: A função void M(char *) apresenta no monitor
uma string;
8- Faça um programa que crie 6 processos e:
- Cada processo escreve 200 números:
1º processo: 1 .. 200
2º processo: 201 .. 400
3º processo: 401 .. 600
4º processo: 601 .. 800
5º processo: 801 .. 1000
6º processo: 1001 .. 1200
- O processo pai tem de esperar que todos os
processos filho terminem.
9- Tendo um array de 1000 posições, faça um programa que
crie 5 processos e:
- Dado um número, procurar esse número no array.
- Cada processo filho, procura 200 posições.
- O processo que encontrar o número, deve imprimir a
posição do array onde se encontra. Também deve "retornar" como valor de
saída o número do processo (1, 2, 3, 4, 5).
- Os processos que não encontrarem o número devem
"retornar" como valor de saída o valor 0.
- O processo pai tem de esperar que todos os
filhos terminem e imprimir o número do processo onde esse número foi
encontrado (1, 2, 3, 4, 5).
Nota: O array não tem números
repetidos.
10- Crie a seguinte função:
a)
char cria_gémeos(pid_t lista[2]) – esta função deve criar
dois processos e devolver para o primeiro processo criado o caracter ‘a’ e para
o segundo processo o caracter ‘b’. Para o pai, a função devolve o caracter ‘p’
assim como os identificadores dos processos criados.
b)
Utilize a função da alínea anterior em substituição da
função fork para resolver o exercício 8.