A Aldeia Numaboa ancestral ainda está disponível para visitação. É a versão mais antiga da Aldeia que eu não quis simplesmente descartar depois de mais de 10 milhões de pageviews. Como diz a Sirley, nossa cozinheira e filósofa de plantão: "Misericórdia, ai que dó!"

Se você tiver curiosidade, o endereço é numaboa.net.br.

Leia mais...

Oficina

11. Interrupções, Exceções e IDTs

Dom

27

Mai

2007


08:55

(16 votos, média 4.13 de 5) 


Maluco beleza MÓDULO 11 do SO Numaboa

O módulo 10. Definição de Sistema Operacional ressaltou as principais funções de um sistema operacional: atender chamadas de interrupção e alocar recursos. Antes de tentar melhorar o Sistema Operacional NumaBoa, o melhor que temos a fazer é buscar mais informações sobre interrupções e seus mecanismos de controle.

O que é uma interrupção

Apesar de já termos citado e explicado parcialmente o que são interrupções, vamos dar uma revisada no assunto. A Intel define uma interrupção como "elas [as interrupções] alteram o fluxo normal do programa para manipular eventos externos ou para reportar erros ou condições de exceção".

Esta definição é um tanto simplista e deixa de fora alguns aspectos relevantes. O primeiro deles é como ocorre uma interrupção. Já sabemos que uma interrupção pode ser gerada por software ou por hardware. Tomemos como exemplo uma interrupção de hardware quando uma tecla é digitada. Se tudo estiver configurado corretamente, quando uma tecla é digitada, o teclado envia um pedido de interrupção para a CPU. Neste caso, a CPU pára o código em execução e chama uma função que fará a leitura da porta 0x60 (a porta de saída do teclado) para determinar o que o teclado está enviando. Esta função, então, deverá devolver o controle para o que estava sendo executado antes do teclado ter enviado o pedido de interrupção. O código original, com frequência, nem fica sabendo que ocorreu uma interrupção. Uma interrupção também pode ser gerada via software através da instrução Assembly int .

Neste ponto é interessante saber que o sistema de interrupções pode ser ativado ou desativado através dos comandos de Assembly cli (desativar) e sti (ativar).

Como as interrupções podem ser usadas

Não é só o teclado que pode disparar uma interrupção. Qualquer outro dispositivo do computador, como HDs, drives de disquete, placas de som, drives de CD-ROM, placas de rede, etc, podem (e precisam) fazer o mesmo. Todos utilizam interrupções por dois motivos: para avisar o sistema operacional que completaram alguma tarefa ou que possuem dados para para o sistema operacional. O PIT (Programable Interrupt Timer - Temporizador de Interrupções Programável) também dispara interrupções em intervalos de tempo pré-determinados, o que é muito útil na multitarefa preemptiva (que não será tema deste módulo).

Programas de usuários também podem usar interrupções. O MS-DOS e a BIOS fornecem várias interrupções para este fim. As interrupções da BIOS só funcionam em modo real.

O que é uma ISR

Uma ISR (Interrupt Service Routine - Rotina do Serviço de Interrupções) é o código executado quando ocorre uma interrupção, portanto existe uma ISR para cada interrupção. A forma como a CPU fica sabendo qual ISR executar será analisada mais adiante.

Como escrever uma ISR

Nada impede que se codifique ISRs próprias, ainda mais quando se pretende escrever um novo sistema operacional. Pode parecer difícil escrever um código que não conflite com o processo em execução, mas a coisa é menos complicada do que se pensa. É que a CPU se encarrega automaticamente da parte mais complicada, salvando os registradores SS, EIP, ESP e CS na pilha. Se o ponteiro da pilha estiver apontando para o mesmo endereço no início da ISR e quando a instrução iret for chamada, então esta instrução restaura os registradores automaticamente. Portanto, é só controlar a pilha: caso um ou mais dos registradores EAX, EBX, ECX, EDX, EBP, ESI, EDI, ES, DS, FS ou GS forem utilizados na função (o que é mais do que provável), primeiro é preciso salvá-los na pilha e não esquecer de restaurá-los após o uso. Vamos a um exemplo para as máquinas mais modernas (que possuem registradores para o terceiro e quarto segmento de dados):

isr: pusha ; Salva os registradores de uso geral push gs ; Salva o ponteiro do quarto segmento de dados push fs ; Salva o ponteiro do terceiro segmento de dados push es ; Salva o ponteiro do segmento extra de dados ; (segundo segmento de dados) push ds ; Salva o ponteiro do segmento de dados ; seu código vai aqui :) pop ds ; Restaura o ponteiro do segmento de dados pop es ; Restaura o ponteiro do segmento extra de dados pop fs ; Restaura o ponteiro do terceiro segmento de dados pop gs ; Restaura o ponteiro do quarto segmento de dados popa ; Restaura os registradores de uso geral iret ;

IRQ

As IRQs (Interrupt Request - Requisição de Interrupção) são interrupções disparadas pelo hardware. Existem 16 no total e são numeradas de 0 a 15. O PIC (Programable Interrupt Controller) mapeia estas IRQs em dois blocos com 8 IRQs cada. É padrão que as 8 primeiras IRQs sejam mapeadas em interrupções de 8 a 15 e que as 8 últimas seja mapeadas em interrupções de 112 a 119. Isto acaba interferindo com as exceções (o que será visto adiante), de modo que se torna necessário remapear as IRQs para um bloco diferente de números de interrupção.

O PIC, a ponte entre as IRQs e a CPU

Quando ocorre uma interrupção de hardware, uma série de eventos é desencadeada. Para simplificar o processo e aumentar a compatibilidade dos dispositivos nas diferentes plataformas, quando um dispositivo dispara uma IRQ, ele a dirige para o PIC enviando toda a informação necessária. O PIC identifica o número da IRQ e depois avisa a CPU. A CPU, assim que terminar a instrução corrente, executa o número da interrupção recebida.


O que é uma exceção e como é disparada

Uma exceção é uma interrupção que ocorre quando alguma coisa dá errado com o código que está sendo executado. Pode ser uma divisão por zero, uma tentativa de acessar um segmento inexistente ou coisa parecida.

Existem 15 tipos de exceções na CPU x86, classificadas como interrupções de 0 a 16, o que significa que existem algumas "falhas" na sequência. Estas falhas correspondem a interrupções reservadas pela Intel, talvez para uso futuro. As interrupções das exceções são as seguintes (mantenho a nomenclatura da Intel entre parênteses):

   0  Erro de Divisão (Divide Error)
   1  Exceções de Debug (Debug Exceptions)
   2
   3  Ponto de parada de execução (Breakpoint)
   4  Overflow
   5  Checagem de Limites (Bounds Check)
   6  Código Operacional Inválido (Invalid Opcode)
   7  Coprocessador não disponível (Coprocessor Not Available)
   8  Falha Dupla (Double Fault)
   9  Segmento de Coprocessador Ultrapassado (Coprocessor Segment Overrun)
  10  TSS Inválida (Invalid TSS)
  11  Segmento Não Presente (Segment Not Present)
  12  Exceção da Pilha (Stack Exception)
  13  Exceção de Proteção Geral - Falha Tripla
      (General Protection Exception - Triple Fault) 
  14  Falha de Página (Page Fault)
  15
  16  Erro do Coprocessador (Coprocessor Error)

Uma ISR para uma Exceção Simples

O modelo de ISR também serve para tratar exceções, mas falta o código que trata a interrupção. Como exemplo, veja uma ISR completa para a exceção 0. Esta ISR mistura código C e Assembly:

isr0: pusha push gs push fs push ds push es extern _interrupt_0 call _interrupt_0 pop es pop ds pop fs pop gs popa iret

A função _interrupt_0, escrita em C, leva em consideração que temos apenas tarefas do ring 0 (ou seja, que estamos trabalhando em modo real). Se tivéssemos tarefas do ring 3 e uma destas tarefas tivesse causado a exceção, então deveríamos deletar a tarefa do ring 3 e continuar. Se você não tiver idéia do que sejam os rings, dê uma lida em O Sistema Windows de 32 bits que está no Oráculo de Referências.

void interrupt_0() { printf("Exceção: Erro de Divisão"); asm("cli"); asm("hlt"); };

Existem duas opções: criar uma função para cada tipo de exceção ou criar apenas uma função C, que recebe como parâmetro a exceção ocorrida e com rotinas que tratam de todas as exceções possíveis.

O que é uma IDT

Uma IDT (Interrupt Descriptor Table - Tabela de Descritores de Interrupção) é um array de descritores usado para associar interrupções e exceções às respectivas ISRs. É o mapa da mina para a CPU saber qual rotina precisa ser executada quando receber uma chamada de interrupção. Cada descritor é composto por 8 bytes e a IDT pode conter no máximo 256 descritores (o total de interrupções no PC também é 256). Quando se cria uma IDT, não é necessário que a tabela contenha 256 descritores, basta usar um para cada interrupção que será tratada no sistema operacional. Para informar a localização da IDT para a CPU, usamos a instrução Assembly LIDT.

O formato do descritor IDT

Como já foi mencionado, cada descritor possui 8 bytes, ou seja, 64 bits. O formato dos descritores contidos numa IDT é o seguinte:

31-16 1514-1312-87-54-0
Deslocamento (word) Presente (1 bit)DPL (2 bits) 01110 (5 bits)000 (3 bits)não usado
63-48 47-32
Seletor (word) Deslocamento (word)
  • O bit 15, Presente: 0 = ausente, 1 = presente. Se Presente não for 1, ocorrerá uma exceção.
  • DPL (Descriptor Privilege Level - Nível de Privilégio do Descritor): 00 = ring0, 01 = ring1, 10 = ring2, 11 = ring3
  • Seletor: seletor do código que a ISR usará
  • Deslocamento: o endereço da ISR. É dividido em dois campos diferentes para manter o processador 386 (e melhores) compatível com o 286.

O melhor é criar um seletor para entender como funciona. No descritor do exemplo o DPL está ajustado para o ring0, é um descritor presente, o seletor é 0x10 e o deslocamento (o endereço da ISR) é 0x200000. Veja abaixo:

0x20 100 0111000000000
0x10 0x0000

Conhecendo a estrutura dos descritores de interrupção, fica fácil entender a estrutura de uma IDT. No momento, é o que basta. Acontece que isto não é tudo. Se não soubermos informar onde a CPU pode encontrar a IDT... de nada vale compor a tabela.

Carregando a IDT via LIDT

Informamos a localização da IDT à CPU através da instrução Assembly LIDT. Esta instrução precisa de um parâmetro do tipo ponteiro. Este aponta para uma pequena estrutura que descreve a IDT para que a CPU saiba onde ela começa e quantos descritores contém. Esta estrutura, que chamaremos de ponteiro da IDT, possui os seguintes campos:

31-16 15-0
Limite (word)
63-32
Base (duplo word)
  • Base: endereço do começo da IDT
  • Limite: comprimento da IDT em bytes

Com estas informações é possível criar uma pequena IDT com apenas três entradas usando o NASM:

inicio_da_idt: ; primeira entrada dw 0x0000 dw 0x10 dw 0x8E00 dw 0x20 ; segunda entrada dw 0x0000 dw 0x10 dw 0x8E00 dw 0x30 ; terceira entrada dw 0x0000 dw 0x10 dw 0x8E00 dw 0x10 fim_da_idt:

Agora os marcadores inicio_da_idt e fim_da_idt nos fornecem os endereços que devem compor o ponteiro da IDT:

fim_da_idt - inicio_da_idt - 1
Base (duplo word)

Traduzido para o NASM, o ponteiro da IDT é o seguinte:

ponteiro_idt: dw fim_da_idt - inicio_da_idt - 1 dd inicio_da_idt

Tendo o ponteiro da IDT, agora basta usar a instrução LIDT:

lidt [ponteiro_idt]

Considerações finais

Estamos chegando cada vez mais perto do nosso objetivo que, para ser sincera, não é construir um sistema operacional que faça concorrência aos existentes. O importante nesta série de tutoriais é adquirir conhecimento suficiente para que possamos entender os sistemas operacionais e, se estivermos planejando um grande projeto, sabermos por onde começar. É por isso que textos da qualidade do Interrupts, Exceptions, and IDTs, escrito por K.J. em três partes, são tão valiosos. Como o texto serviu de base para este módulo, agradeço o autor por ter disponibilizado este material na Internet e o coloquei na seção de downloads da Aldeia em Tutoriais / Sistemas Operacionais.

Вадим Логофет отецручки для сковородок купитьсупермаркет клас харьковcall centerпрофнастил цена новгородобзор ноутбукникас сайт

Informações adicionais