Interrupções

PIC, vetores, hooks e notificações de hardware no ciclo de eventos Minix.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

Uma interrupção permite ao dispositivo assinalar um evento sem o programa estar sempre a consultar o estado. No PC clássico, o pedido passa pelo controlador de interrupções antes de o processador o atender. No Minix, há ainda outra etapa: o kernel notifica o processo que subscreveu esse pedido.

IRQ, vetor e rotina

Uma IRQ é uma linha de pedido de interrupção. O PIC i8259 regista pedidos, aplica máscaras e escolhe qual apresentar ao processador. No PC clássico há dois PICs em cascata. A IRQ2 do primeiro liga ao segundo, pelo que não há 16 entradas independentes para dispositivos.

DispositivoIRQ global
Timer 00
Teclado1
COM23
COM14
RTC8
Rato PS/212

Na máscara do PIC, um bit a 1 desativa a linha correspondente. É uma máscara de bloqueio, ou seja, não a confundas com a máscara de eventos recebidos no Minix, onde um bit a 1 assinala uma notificação.

O PIC entrega um vetor, um número que o processador usa para escolher a entrada na tabela de descritores de interrupção, a IDT em IA-32. O vetor não é o número da IRQ, por isso não derives um vetor do número da IRQ no código da aplicação.

Em IA-32, o atendimento guarda o contexto exigido, executa a rotina e restaura o contexto para continuar o programa interrompido. A rotina precisa de reconhecer o pedido no controlador, com EOI, e terminar pelo mecanismo de retorno de interrupção apropriado. No modelo clássico dos slides, isso aparece como IRETD. A arquitetura ARM tem exceções e controladores próprios; não usa a IDT nem as instruções IN, OUT ou IRETD do x86.

Dois atendimentos diferentes

A rotina genérica do Minix, no kernel, recebe a interrupção real, mascara a linha, notifica os interessados, aplica a política de reativação, envia EOI e regressa. A função timer_ih ou kbc_ih que escreves no laboratório corre no teu processo, quando o teu ciclo lê a notificação e a chama.

Isto muda o raciocínio sobre concorrência, porque as tuas funções C não são chamadas arbitrariamente pela CPU enquanto outra instrução C tua está a correr. No modelo de um processo e um ciclo de receção, chamas cada handler de forma sequencial. Não acrescentes volatile como se isso resolvesse sincronização, nem envies EOI ao PIC por iniciativa própria: o kernel trata da interrupção física.

O handler do dispositivo recolhe estado e dados. A lógica da aplicação interpreta-os depois. Para teclado e rato, uma notificação permite recolher o byte disponível, por isso não esperes ali pelos restantes bytes de um scancode ou pacote. Podem chegar em notificações futuras.

Hook e máscara de notificação

sys_irqsetpolicy(irq, policy, &hook) usa hook em dois sentidos:

  1. À entrada, o valor escolhe o bit da notificação que o processo quer usar.
  2. À saída, a variável contém o identificador que deves passar a outras kernel calls sobre essa subscrição.

Guarda o número do bit pedido antes da chamada. A máscara a testar é 1u << bit_number; com IRQ 12 e bit pedido 2, a máscara de notificação é 0x04, não 1 << 12. Um enunciado do teste de abril de 2025 pedia o caso do bit 7: hook entra com o valor 7 e a máscara a testar é BIT(7), ou 0x80. Passar BIT(7), que vale 128, como valor de hook pediria um bit que não existe no campo de notificações.

Repara que a variável hook muda de sentido: entra com 2 e sai com o identificador do kernel. A máscara continua a usar o 2 inicial; o valor devolvido serve para as kernel calls seguintes. Os bits das várias subscrições do processo devem ser distintos e caber no campo de notificações.

IRQ_REENABLE autoriza a rotina genérica a reativar a linha. Sem essa política, o processo tem de chamar sys_irqenable quando estiver pronto. IRQ_EXCLUSIVE impede a partilha da subscrição com outro driver. É relevante para periféricos como o rato, onde não queres que o driver normal do Minix consuma os mesmos dados. No timer, a subscrição pode coexistir com a do sistema.

Ciclo completo com timer

Esta função completa destina-se a um módulo do ambiente Minix/LCF, com permissão para subscrever IRQ0. Chama wait_timer_ticks(60) depois de inicializares o programa de apoio. Se o timer estiver a 60 Hz, o efeito esperado é regressar após cerca de um segundo. Não configura a frequência e não é um programa de C portátil.

#include <lcom/lcf.h>
#include <stdint.h>

int wait_timer_ticks(uint32_t limit) {
  if (limit == 0) return OK;
  const int notification_bit = 0;
  int hook = notification_bit;
  const uint32_t mask = 1u << notification_bit;
  int result = sys_irqsetpolicy(0, IRQ_REENABLE, &hook);
  if (result != OK) return result;

  result = sys_irqenable(&hook);
  if (result != OK) {
    sys_irqrmpolicy(&hook);
    return result;
  }

  uint32_t ticks = 0;
  while (ticks < limit) {
    message msg;
    int ipc_status;
    result = driver_receive(ANY, &msg, &ipc_status);
    if (result != OK) break;
    if (!is_ipc_notify(ipc_status)) continue;
    if (_ENDPOINT_P(msg.m_source) != HARDWARE) continue;
    if (msg.m_notify.interrupts & mask) ++ticks;
  }

  int disable_result = sys_irqdisable(&hook);
  int remove_result = sys_irqrmpolicy(&hook);
  if (result != OK) return result;
  if (disable_result != OK) return disable_result;
  return remove_result;
}

Há três filtros: a mensagem é uma notificação, vem do hardware e contém o bit do timer. Uma mensagem normal não contém necessariamente o significado que esperas em m_notify.

driver_receive bloqueia enquanto não há mensagem. Depois da receção, o teu código chama o handler. Um ciclo a consultar uma variável à espera de que o handler corra sozinho impediria chegar à receção e ao despacho que o chamam.

Notificações podem agrupar pedidos. O bit significa que há um pedido assinalado; não contém um contador de quantas interrupções ocorreram enquanto o processo estava ocupado. Contar um tick por notificação é o modelo usado nos pequenos ciclos do laboratório, mas não garante um relógio sem perdas se bloqueares longamente o atendimento.

Vários dispositivos na mesma notificação

Suponhamos que pediste o bit 0 para o timer e o bit 3 para o teclado. Uma notificação com interrupts = 0x09 contém ambos. Os testes devem ser independentes:

if (pending & timer_mask) timer_ih();
if (pending & keyboard_mask) keyboard_ih();

Um else if descartaria um evento quando os dois bits estivessem ativos. Define também a ordem da lógica da aplicação. Se um timeout e uma tecla chegarem juntos, a política de aceitação da tecla no limite do tempo deve ser explícita.

Latência e limpeza

A interrupção não tem latência zero. O evento espera enquanto as interrupções estão bloqueadas, enquanto pedidos prioritários são atendidos e enquanto o processo ainda não recebeu a notificação. Um handler curto reduz o tempo ocupado, mas não elimina esses custos.

Ao terminar, repõe configurações alteradas e remove cada subscrição obtida. Trata também os caminhos de falha parcial. A ausência de dados pode resultar de uma linha não ativada, máscara errada, dispositivo que não gera o evento, leitura que não consome o buffer ou outro driver que o consome primeiro. Verifica estas hipóteses por observações, sem trocar várias configurações ao mesmo tempo.

Exercícios

Separar IRQ e bit de notificação

Subscreves o rato na IRQ12 e pedes inicialmente hook = 3. Que máscara de notificação deves conservar para testar o evento, em decimal?

Pista
A notificação usa o bit pedido à entrada.
Solução

A máscara é 1 << 3 = 8, ou 0x08. IRQ12 escolhe a linha física. O valor devolvido em hook serve nas kernel calls seguintes e não substitui a máscara guardada. A mesma distinção vale para o bit 7 do teste de abril de 2025: passa-se o número 7 em hook e testa-se com BIT(7), ou seja 0x80; passar BIT(7) como número pediria um bit inexistente.

Erros frequentes

Usar 1 << 12; usar diretamente o número 3 como máscara; ou testar com 7 em vez de BIT(7), o que verifica os bits 0 a 2 em conjunto em vez do bit 7.

Voltar à explicação
Atender dois bits pendentes

Timer usa máscara 0x02 e teclado usa 0x10. A notificação contém 0x12. Um programa usa if para timer e else if para teclado. Que evento perde e como corriges o despacho?

Pista
0x12 é o OR das duas máscaras.
Solução

O programa chama o timer e deixa de chamar o teclado. Deve usar dois if independentes, com pending & mask em cada um. Depois aplica a política definida para a ordem de eventos, sobretudo num limite de timeout.

Confere a tua resposta:

  • Reconheci que 0x12 contém 0x02 e 0x10.
  • Usei testes independentes.
  • Defini a ordem da aplicação.

Erros frequentes

Supor que só pode existir uma IRQ em cada mensagem ou comparar pending == mask, o que falharia quando há vários bits.

Voltar à explicação
Localizar a rotina que escreves

No modelo Minix do laboratório, a CPU chama diretamente timer_ih do teu ficheiro C a qualquer instante? Explica o caminho entre IRQ0 e essa função e quem envia EOI.

Pista
O driver é um processo de utilizador com privilégios.
Solução

O kernel atende a interrupção física pela rotina genérica, notifica o processo e trata EOI e retorno de interrupção. O processo recebe a notificação com driver_receive, confere a máscara e chama a sua timer_ih de forma sequencial no ciclo. Não é essa função que recebe diretamente a entrada da IDT.

Confere a tua resposta:

  • Distingui a rotina genérica do kernel.
  • Expliquei quando corre timer_ih no processo.
  • Atribuí EOI e retorno físico ao kernel.

Erros frequentes

Aplicar automaticamente o modelo de uma ISR de kernel ao handler C chamado pelo ciclo de mensagens.

Voltar à explicação
Contar sem supor um por um

O timer gerou duas interrupções enquanto o processo atendia o teclado. O kernel entrega uma notificação com o bit do timer ativo uma vez. Quantos ticks podes garantir a partir desse bit?

Pista
O bit assinala pelo menos um pedido.
Solução

Garantes pelo menos um, não exatamente dois. O bit significa que há um pedido assinalado. Nos pequenos ciclos do laboratório conta-se um tick por notificação, mas esse modelo pode perder ticks se bloqueares o atendimento.

Confere a tua resposta:

  • Tratei o bit como pedido assinalado.
  • Não converti um bit em vários ticks.
  • Assinalei a possível perda sob carga.

Erros frequentes

Somar sempre um tick por interrupção física ou ler a notificação como contador.

Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.