Processo de Boot - Tabelas de Descritores e Interrupções
Autor: Hermano | Publicado: 2013-11-21T04:24:00-08:00


Após a entrega do controle do computador ao núcleo do sistema
operacional, uma série de rotinas é executada. Cada um dos subsistemas do
núcleo é iniciado. As funções para inicialização do sistema de gerência de
memória, gerência de processos, troca de mensagens e sistema de arquivos
virtual são devidamente executadas para realizar as configurações iniciais.



Entretanto, após a inicialização do sistema de gerência de
memória, algumas estruturas responsáveis pelo controle da CPU devem ser
corretamente criadas na memória, e suas posições iniciais devem ser informadas
ao processador. Essas estruturas são conhecidas como tabelas de descritores. As
duas principais tabelas de descritores utilizadas em processadores da família
Intel são a GDT e IDT.



A GDT (Global Descriptors
Table) é uma tabela que contém entradas chamadas de descritores de
segmento. Os descritores de segmento fornecem o endereço base do segmento, os
direitos de acesso, tipo e outras informações [1]. A GDT permite que
a memória seja separada em segmentos, porém o sistema FESO configura esta
tabela de forma a possibilitar a existência de apenas um grande segmento que
ocupe todo o espaço de endereçamento. 



A GDT possui diversos tipos de descritores, de maneira que, para
cada nível de privilégio usado pelo SO existam 2 deles, um para código e um
outro para dados. O controle do nível de privilégio se dá através de alguns
bits nos chamados registradores de segmento da CPU, que apontam para uma
determinada entrada na GDT. A CPU sabe o nível de privilegio do código, graças
aos dados contidos na GDT. 



Cada uma das entradas da GDT possui 64 bits e são dispostas na memória de acordo com as especificações da
mostradas na figura abaixo [3]. É possível notar a existência de dois campos principais:
o base e o limit. O campo base
indica qual é o primeiro endereço do segmento, e o limit indica a sua posição final. Além disso, o campo Access Byte indica se o descritor é de
um segmento de código, ou se é um segmento de dados, e também, qual é o nível
de acesso do segmento. &nbsp;









O sistema FESO cria na memória uma GDT com 6 descritores de
segmento. Como são utilizados 3 níveis de privilégio (níveis 0, 1 e 3), são
criados dois descritores para cada um: o primeiro é o descritor de segmento de código,
e o outro, de dados. Uma vez que a segmentação não é utilizada, o campo base fica em 0 e campo limit é colocado em 4 GB para todos os
descritores, fazendo com que o processador veja a memória como um único
segmento.&nbsp; &nbsp;



Cada descritor de 64 bits
fica disposto em sequência na memória. Além dos descritores, outra estrutura é
criada, e nela é colocado um ponteiro para o começo do primeiro descritor, e a
quantidade de bytes ocupados pela
GDT. &nbsp;Após isso, uma instrução especial
da CPU, a LGDT, é chamada, tendo como operando o endereço dessa estrutura. Essa
instrução carrega um registrador especial da CPU, responsável por armazenar a
posição da GDT atualmente em uso.



Estando a GDT devidamente instalada, é possível utilizar os
registradores de segmento para acessar blocos de memória descritos por ela.
Alguns bits dos registradores de
segmento apontam para uma entrada da GDT. Dessa maneira, é possível especificar
diversos segmentos na GDT, e utilizá-los através da configuração correta dos
registradores de segmento. O sistema FESO não faz uso da segmentação. Desta
forma, as entradas na GDT são utilizadas apenas para o controle dos níveis de
privilégio. Os registradores de segmento são configurados de acordo com o nível
de privilégio do código em execução. Quando as rotinas do núcleo estão em
execução, os registradores de segmentos apontam para as entradas da GDT
correspondentes ao nível de privilégio máximo (nível 0), e o mesmo ocorre para drivers de dispositivos e aplicativos de
usuário com seus respectivos níveis de acesso.



A CPU verifica se a próxima instrução a ser executada está
acordo com o nível de privilégio descrito pela entrada na GDT especificada nos
registradores de segmento. Caso a instrução exija um nível de acesso maior do
que o nível corrente, uma exceção é gerada pela CPU, e o sistema operacional
tem condições de interromper o programa que está tentado realizar uma tarefa
para a qual ele não possui permissão. 



Obviamente, as instruções para alterar a GDT, e para alterar o
valor do nível de privilégio do código atual são instruções privilegiadas que
só podem ser executadas pelo sistema operacional. Isto impede que um programa
altere seu nível de privilégio e passe a controlar o processador. Além disso,
para permitir que o núcleo do sistema execute com o nível de privilégio máximo,
sempre que uma interrupção ocorre, uma rotina do sistema operacional é chamada
automaticamente pelo processador. Essa rotina é executada com privilégio
máximo, pois o próprio processador se encarrega de mudar o nível de privilégio
antes da chamada de uma das rotinas do núcleo. Cabe ao sistema operacional
alterar o nível de privilégio do código novamente, antes de retornar o controle
ao programa previamente interrompido.



&nbsp;A segunda tabela de
descritores está intimamente ligada ao processo de tratamento de interrupções.
A IDT (Interrupt Descriptors Table)
armazena os descritores de porta (gate
descriptors) que fornecem o acesso aos manipuladores de exceções e
interrupções (INTEL, 2011). Esses manipuladores nada mais são do que rotinas
chamadas para o tratamento de eventos. Durante o processo de inicialização, o
sistema FESO cria uma IDT cujos descritores apontam para cada uma de suas
funções de tratamento. Desta forma, sempre que um evento ocorre, é uma rotina
do sistema FESO que é posta em execução para tratá-la.



A figura mostra como é a estrutura de um descritor da GDT.
Cada descritor também possui 64 bits, o
campo offset in segment possui 32 bits ao todo, nele fica armazenado o
endereço da rotina de tratamento da interrupção. O campo segment selector aponta para um índice da GDT responsável por
especificar em qual segmento o código de tratamento da interrupção está. Todas
as interrupções tem seu segmento configurado para a entrada da GDT que
especifica o segmento de código de nível de privilégio máximo. Deste modo,
sempre que uma interrupção for chamada, a CPU automaticamente altera o
registrador de segmento fazendo com que a rotina de tratamento tenha privilégio
máximo. 








De forma muito similar a GDT, os descritores da IDT são
armazenados na memória em sequência. Uma estrutura com um ponteiro para o
primeiro descritor e com tamanho total da IDT é criada, e uma instrução, a
LIDT, é chamada para configurar o registrador responsável por armazenar a
posição da IDT. Esta também é uma instrução privilegiada, e apenas o SO tem
condições de instalar suas próprias rotinas de tratamento de interrupções. 



Cada interrupção possui um número, que é utilizado como índice
dentro da IDT para a definição, por parte da CPU, de qual rotina deve ser
chamada para o que ela seja tratada. A IDT possui 256 entradas, tanto para
interrupções de hardware, software ou
exceções. As faixas de valores para cada tipo serão tratadas em uma seção a
parte.


&nbsp;<!--[endif]-->Instalação das Interrupções


O sistema operacional FESO instala as rotinas de tratamento de
interrupções durante o processo de inicialização. O sistema lida com 3 tipos
distintos de interrupções: as interrupções de hardware geradas pelos dispositivos de E/S. As interrupções de software geradas pelos programas quando
estes precisam de algum serviço do SO, e finalmente, as exceções que ocorrem
quando algum programa tem algum comportamento indevido, tal como executar uma
instrução para a qual ele não tem permissão.



As primeiras 32 entradas da IDT, ou seja, as interrupções cujos
índices vão de 0 até 31, são exceções. As exceções são geradas pela própria
CPU, e cabe ao sistema operacional tomar a decisão de qual ação deverá ser
tomada para tratá-las. Por padrão, o sistema FESO finaliza
o processo em execução sempre que uma exceção é gerada. Após isso o algoritmo
de escalonamento é executado, e um novo processo é escalonado. 



As interrupções de hardware
são tratadas primeiramente por um chip
presente nos computadores, o PIC (Programmable
Interrupt Controller). O PIC gerencia diversos pedidos de interrupção e os
repassa ao processador dependendo de sua prioridade [2]. O PIC
possui apenas 8 entradas, porém, como podem existir mais fontes de interrupção,
outro PIC é colocado em cascata na entrada 2 do primeiro. O primeiro PIC é
conhecido como master, e o segundo,
ligado a entrada dois, é o slave. 



O PIC possui internamente uma tabela na qual é possível
configurar qual índice de vetor será enviado ao processador na ocorrência de
uma interrupção. É possível configurar essa tabela através da escrita e leitura
de portas de E/S especificas para cada um dos PICs. O sistema FESO configura os
vetores das interrupções de hardware
a partir do número 32. Isto porque os 32 primeiros vetores estão reservados
para as exceções. Após a chamada de uma função para o tratamento de uma
interrupção de hardware, o PIC deve
ser sinalizado. O PIC só enviará o próximo sinal de interrupção, quando o
último sinal enviado tiver sido tratado pela CPU. A sinalização também é feita
através do uso de portas de E/S e instruções especiais da CPU.



Finalmente, a entrada 80 da IDT é configurada com a função de
tratamento de interrupções de software
ou system calls (chamada ao sistema).
A rotina de inicialização do FESO instala uma função de tratamento padrão na
entrada correspondente da IDT. Os programas de usuário e drivers de dispositivo podem gerar uma interrupção chamando uma
instrução em assembly, a INT. O
operando da instrução é o número 80, o que faz o processador buscar a entrada
correspondente na IDT e coloca-la em execução. Essa função verifica o estado
dos registradores, que foram previamente configurados pelo programa que gerou a
interrupção, e desta maneira o sistema operacional sabe como proceder para
atender a chamada.



Todas as interrupções devem estar devidamente configuradas antes
que o sistema operacional carregue na memória e entregue o controle da CPU a
algum programa. Uma vez que todas as interrupções estão devidamente
configuradas, existe a garantia de que o sistema operacional irá entrar em
execução sempre que um evento ocorrer, podendo assim, manter seu controle sobre
o sistema computacional.&nbsp;

[1]&nbsp;INTEL. Intel 64 and IA-32 Architectures Software Developer’s Manual.2011. 4161 p.
[2]&nbsp;MESSMER, Hans-Peter. The Indispensable PC Hardware Book. 3. ed. Addison-Wesley Professional, 1999.
[3]&nbsp;OSDEV WIKI. Global Descriptor Table. 2012.