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;
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() )
1. Verifica se há lugar na Tabela de Processos; 2. Tenta alocar memória para o filho;
3. Altera mapa de memória e copia para a tabela; 4. Copia imagem do pai para o filho;
5. Copia para a tabela de processos a informação do processo pai (pid, prioridades, estado, etc); 6. Atribui um pid ao filho
7. 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> 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); }
printf("REPETIDO\n"); }