Gerência de Processos
Autor: Hermano | Publicado: 2013-11-21T07:14:00-08:00


O sistema de gerência de processos é o componente do SO responsável pela criação, eliminação e escalonamento dos processos e threads. Nesta seção são tratados os detalhes do funcionamento da gerência de processos do sistema FESO.


&nbsp;Processos e Threads






Processos são abstrações utilizadas para o controle da execução
dos programas pelo SO. Para cada processo presente no sistema FESO, diversos
dados precisam ser armazenados. Entre eles, o estado dos registradores da CPU,
os blocos de memória física alocados e outras informações utilizadas no
controle do processo.





O sistema FESO possui um suporte rudimentar a threads. Os threads são unidades básicas de alocação da CPU [1]. Os processos possuem uma lista
de threads associados, cada thread possui sua própria estrutura com o
conjunto de estados dos registradores. Os threads
em um processo podem estar executando trechos de código diferentes entre si,
porém todos compartilham o mesmo espaço de endereçamento e mesmas variáveis
globais. Entretanto, existe uma pilha associada a cada thread, na qual são armazenadas as variáveis locais e as estruturas
de controle a chamada de sub-rotinas.





A figura abaixo &nbsp;mostra como está dividido o espaço de endereçamento virtual
de um processo. Os programas desenvolvidos para funcionar sobre o FESO precisam
possuir seus endereços resolvidos a partir da posição 0 da memória. No começo
do espaço de endereçamento, ficam o código e os dados do programa.







Imediatamente após os dados, começa o heap, posição onde ficam mapeados os frames de memória alocados dinamicamente pelo programa. A região de
memória entre o heap e as pilhas é
reservada para o crescimento da quantidade de pilhas, que é feita em posições
de memória menores, e para a expansão do heap
que é feita para posições de memória maiores. 





No final do espaço de endereçamento de um processo, ficam as
pilhas dos threads. Ao contrário do heap que é único para todos os threads de um mesmo processo, as pilhas
são individuais. O tamanho da pilha é fixo, e não é alterado durante a execução
de um programa. Quando um thread é
finalizado, o espaço de sua pilha não e desalocado, pois ele poderá ser
utilizado como pilha de um novo thread
a ser criado no futuro.





O núcleo do sistema armazena os processos e threads em árvores, ordenados pelos identificadores. Cada processo
possui um identificador, e cada thread
também. Assim que são criados, os processos possuem um thread principal, que possui o mesmo identificador do processo ao
qual está atrelado. O sistema FESO armazena em duas estruturas básicas, uma
para processos e outra threads,
algumas informações utilizadas no controle do uso da CPU e dos recursos
computacionais.


Para os processos, o SO precisa armazenar uma lista com todos os
frames de memória ocupados. Isto é
necessário para que eles possam ser liberados quando o processo terminar. O
sistema operacional também armazena o endereço da Page Directory, que é fundamental na troca de contexto. Além disso,
se faz necessário o armazenamento de algumas variáveis para o controle do heap, um flag para indicar o nível de privilégio do processo, uma lista com
pilhas alocadas pelos threads, um
ponteiro para a próxima posição livre na área reservada a pilhas, entre outras
informações.









As estruturas que armazenam threads
são um pouco diferentes. Elas possuem campos para armazenar o estado dos
registradores da CPU, o estado de execução em que o thread se encontra (pronto para a execução, em espera, ou em
execução) e seu nível de privilégio. Os threads
possuem também uma lista de mensagens, que é utilizada pelo sistema de
comunicação entre processos do FESO. &nbsp;&nbsp;





Diversas operações são realizadas sobre os processos e threads. Processos e threads podem ser criados ou destruídos
pelo sistema a qualquer momento. O sistema operacional fornece um conjunto
mínimo de chamadas ao sistema que permitem a realização dessas operações. A
criação de processos é feita através do carregamento de arquivos executáveis no
formato ELF (Executable and Linkable
Format). Porém, o sistema FESO ainda não suporta o carregamento de
bibliotecas compartilhadas, então é imperativo que o arquivo executável esteja
com todas as suas bibliotecas estaticamente linkadas
em um único arquivo binário.



Algoritmo de Escalonamento




O número total de threads
no sistema, na maioria dos casos, é maior do que a quantidade de CPUs
disponíveis. Por este motivo, o sistema operacional deve permitir que cada thread tenha uma fatia de tempo para ser
executado pela CPU. O sistema FESO se utiliza de um algoritmo de escalonamento
por chaveamento circular, conhecido como round-robin
para escalonar as CPUs para os diversos threads
presentes no sistema. A figura abaixo mostra um exemplo de execução o algoritmo
de escalonamento, bem como um diagrama das possíveis trocas de estado entre threads. 







Existem basicamente 2 tipos de threads presentes no sistema
FESO, os threads de aplicativos de
usuário (com prioridade 1) e os threads dos processos servidores (prioridade
0). O algoritmo de escalonamento do FESO utiliza 2 filas de threads com prioridades diferentes, e o
algoritmo round-robin é executado
nessas duas filas. O algoritmo de escalonamento verifica, inicialmente, na
primeira fila, de prioridade 0, se existe algum thread pronto para a execução. E caso exista ele é escalonado. Caso
não exista, a mesma verificação é executada na segunda fila.





O algoritmo de
escalonamento é executado constantemente, seja por causa de uma interrupção
causada pelos temporizadores do sistema, ou porque algum thread executou uma chamada bloqueante. Em ambos os casos, o
processo tem sua execução interrompida, e o estado de seus registradores salvo.
A diferença é que threads cuja fatia
de tempo se encerrou ainda estão prontos para execução, e só não estão alocados
pois o sistema decidiu permitir que outro thread
executasse. O caso do thread que
realizou uma chamada bloqueante é diferente, pois a continuação de sua execução
só será possível, depois da ocorrência do evento pelo qual ele está esperando.
O algoritmo de escalonamento sempre armazena o estado de todos os registradores
da CPU no momento em que um thread é
interrompido, e quando um thread é
posto de volta em execução, o estado de seus registradores é restaurado. Este
processo é chamado de troca de contexto, e permite que a execução de um thread seja retomada exatamente de onde
parou.&nbsp; 



O algoritmo de
escalonamento coloca o thread em
execução no final de sua fila, sempre que ele sofre preempção, ou seja, tem sua
execução interrompida, ou executa uma chamada bloqueante. Desta maneira, todos
os threads tem a chance de obter uma
fatia de tempo para execução na CPU. Entretanto, como os threads de processo servidores têm uma prioridade maior que threads de processos de usuário, caso um
thread servidor nunca entre em estado
de espera, qualquer thread de usuário
fica indefinidamente impedido de executar. Para evitar que isto ocorra, os
processos servidores, que só podem ser criados durante a inicialização do
sistema, devem ser desenvolvidos de forma a não ocuparem a CPU indefinidamente.





Há ainda, um thread
especial, criado pelo SO durante o processo de inicialização. É o thread IDLE, ou ocioso. Este thread é escalonado sempre que não
existe nenhum outro thread pronto
para a execução no sistema. Este thread
simplesmente coloca a CPU em um estado espera, até que uma interrupção ocorra e
o algoritmo de escalonamento seja novamente executado. &nbsp;

[1]&nbsp;SILBERSCHATZ, Abraham; GALVIN, Peter; GAGNE, Greg. Sistemas Operacionais: Conceitos e Aplicações. 1. ed. Rio de Janeiro: Campus, 2000.