# Interrupções

IRQs, subscrição, rotinas de atendimento e máscaras no Minix.

Página: https://resumos.rgo.pt/cadeiras/lc/interrupcoes/

O polling prende o processador a perguntar. As **interrupções** invertem o sentido da conversa: o periférico avisa quando tem algo a dizer, e o processador atende. O preço é um mecanismo com mais peças, subscrição de linhas de interrupção, rotinas de atendimento e máscaras, que esta página monta peça a peça.

## Quem avisa quem

Cada periférico capaz de interromper está ligado a uma linha **IRQ** (_interrupt request_). O pedido não chega direto ao processador: passa pelo **controlador de interrupções** (PIC), que assinala o pedido e só o entrega se essa linha estiver desmascarada. O processador suspende o programa, corre a **rotina de atendimento** (_handler_) e depois retoma onde parou. A rotina deve ser curta: lê o estado, recolhe os dados, assinala que há trabalho novo e sai. O processamento pesado fica para o programa principal, que consome o que a rotina recolheu.

![Fluxo de uma interrupção: o periférico gera o evento, o PIC assinala o IRQ, o CPU suspende o programa, o handler recolhe os dados e o programa principal consome-os.](https://resumos.rgo.pt/cadeiras/lc/interrupcoes/figura-1.svg)

O resultado disto é que entre eventos o processador é livre. Um teclado que gera meia dúzia de interrupções por segundo custa meia dúzia de atendimentos; em polling, o mesmo teclado custaria milhões de perguntas por segundo.

## As linhas de cada periférico

O PC clássico tem 16 linhas, da IRQ0 à IRQ15, e cada periférico da cadeira tem a sua fixa. Decora esta tabela, porque a máscara de subscrição e o diagnóstico de “não chega nada” passam sempre por ela:

| Periférico | Linha |
| --- | --- |
| Temporizador | IRQ0 |
| Teclado | IRQ1 |
| Porta série COM2 | IRQ3 |
| Porta série COM1 | IRQ4 |
| RTC | IRQ8 |
| Rato | IRQ12 |

Em DOT, a topologia é sempre a mesma: periféricos pendurados no controlador, controlador a falar com o CPU, CPU a despachar para o handler certo.

![Diagrama do caminho de uma interrupção: teclado e temporizador ligam ao PIC, o PIC ao CPU e o CPU ao handler.](https://resumos.rgo.pt/cadeiras/lc/interrupcoes/figura-2.svg)

## Subscrever uma interrupção

No Minix, usar uma interrupção segue sempre a mesma sequência: subscrever a linha IRQ para receberes notificações, ativar a linha, entrar no ciclo de espera por mensagens e, no fim, desativar e remover a subscrição. Em pseudocódigo com a API do Minix:

```
sys_irqsetpolicy(TIMER_IRQ, IRQ_REENABLE, &bit_mask);
sys_irqenable(&bit_mask);

int ipc_status;
message msg;
while (continuar) {
  driver_receive(ANY, &msg, &ipc_status);
  if (is_ipc_notify(ipc_status) && (msg.m_source == HARDWARE)) {
    timer_handler(); /* curta: conta e sai */
  }
}

sys_irqdisable(&bit_mask);
sys_irqrmpolicy(&bit_mask);
```

Cada chamada tem um papel: `setpolicy` regista o interesse e diz se a linha se rearma sozinha, `enable` abre a porta, `driver_receive` bloqueia o processo até chegar uma mensagem (de hardware ou de outro processo), e no fim `disable` e `rmpolicy` devolvem tudo ao estado inicial. Esquecer a limpeza final deixa a linha armada para o próximo programa, que recebe interrupções que não pediu.

## Máscaras e linhas partilhadas

A **máscara** (`bit_mask`) identifica a tua linha no meio de todas: cada IRQ corresponde a um bit, e o bit a 1 significa “esta é a minha”. Quando o `driver_receive` acorda, confirma sempre que a notificação veio mesmo da tua linha antes de chamares o handler, porque o processo pode receber mensagens de outras fontes no mesmo ciclo.

Duas regras de higiene que evitam os bugs mais penosos: a rotina nunca espera por nada (não há polling dentro de um handler, e muito menos chamadas bloqueantes), e os dados partilhados entre a rotina e o programa principal são o mínimo possível, atualizados de forma que uma leitura a meio nunca veja um estado inconsistente.

## Exemplo: medir um segundo com o temporizador

O temporizador interrompe 60 vezes por segundo. Para medir um segundo, o handler incrementa um contador e o programa principal espera que ele chegue a 60:

```
static int ticks = 0;

void timer_handler(void) {
  ticks++;
}
```

Com `ticks` a subir de 60 em 60 por segundo, esperar `ticks >= 60` é esperar um segundo, e esperar `ticks >= 300` são cinco segundos. A conta é direta porque a frequência é conhecida: número de interrupções a dividir pela frequência dá o tempo decorrido. Confirma a aritmética correndo a simulação: 60 tiques dão 1 segundo, 300 dão 5 e 3600 dão 60.

```c
#include <stdio.h>

int main(void) {
    int periodos[] = {60, 300, 3600};
    for (int i = 0; i < 3; i++) {
        int ticks = 0;
        for (int t = 0; t < periodos[i]; t++) ticks++;
        printf("%d tiques = %d s\n", periodos[i], ticks / 60);
    }
    return 0;
}
```

Este mesmo contador serve de relógio para tudo o resto, do ritmo de um jogo ao timeout de uma operação.

## Ver também

Este vídeo explica as diferenças entre polling, interrupções e DMA com exemplos visuais. Vê-o depois de perceberes a subscrição acima: cada mecanismo do vídeo mapeia para uma sequência desta página.

[Vídeo: Diferenças entre polling, interrupções e DMA explicadas](https://www.youtube.com/watch?v=LNPBr3WvuNg)

A miniatura vem do YouTube. O vídeo só carrega quando clicas. [Abrir no YouTube](https://www.youtube.com/watch?v=LNPBr3WvuNg)

Como depurar interrupções que não chegam

Se o handler nunca corre, verifica por esta ordem: a linha foi subscrita e ativada, a máscara corresponde ao IRQ certo, o ciclo chama `driver_receive` de verdade em vez de fazer polling por cima, e a subscrição foi feita antes de ativares. Em nove de dez casos, o problema está nesta lista, não no hardware.
