- Inicialização das Interrupções
Este documento explora a implementação de interrupções no kernel Linux 2.6.18, enfatizando a interação entre hardware e software para compreensão do código-fonte.
1.1 Portas no Intel versus Linux
Arquiteturas Intel definem quatro tipos de portas: porta do sistema, porta de interrupção, porta de armadilha e porta de chamada. A porta de chamada facilita a transferência controlada de programas entre níveis de privilégio, mas o Linux opta por utilizar portas de armadilha para chamadas de sistema, oferecendo maior flexibilidade e possibilidades de otimização. No Linux, as portas são categorizadas conforme o acesso:
- Porta de interrupção: Restrita ao modo kernel, usada para tratamento de interrrupções externas.
- Porta de interrupção do sistema: Acessível ao modo usuário, empregada para exceções como breakpoints (vetor 3).
- Porta de armadilha: Exclusiva do modo kernel, aplicável à maioria das exceções.
- Porta do sistema: Permitida ao modo usuário, utilizada para instruções como
int 0x80. - Porta de tarefa: Utilizada para exceções específicas, como double fault, com base em TSS.
1.2 Configuração Inicial das Exceções
A rotina de inicailização de exceções configura as portas na IDT (Interrupt Descriptor Table). O código abaixo, adaptado para clareza, ilustra a configuração dos vetores de exceção:
void __init setup_exception_vectors(void)
{
#ifdef CONFIG_EISA
void __iomem *ptr = ioremap(0x0FFFD9, 4);
if (readl(ptr) == 'E' + ('I' << 8) + ('S' << 16) + ('A' << 24)) {
eisa_bus_detected = 1;
}
iounmap(ptr);
#endif
set_trap_gate(0, ÷_fault_handler);
set_intr_gate(1, &debug_interrupt);
set_intr_gate(2, &nmi_handler);
set_system_intr_gate(3, &breakpoint_handler);
set_system_gate(4, &overflow_handler);
set_trap_gate(5, &bounds_check_fault);
set_trap_gate(6, &invalid_opcode_fault);
set_trap_gate(7, &device_unavailable_fault);
set_task_gate(8, GDT_ENTRY_DOUBLEFAULT_TSS);
set_trap_gate(9, &coprocessor_overrun_fault);
set_trap_gate(10, &invalid_tss_fault);
set_trap_gate(11, &segment_not_present_fault);
set_trap_gate(12, &stack_segment_fault);
set_trap_gate(13, &general_protection_fault);
set_intr_gate(14, &page_fault_handler);
set_trap_gate(15, &spurious_interrupt_fault);
set_trap_gate(16, &coprocessor_error_fault);
set_trap_gate(17, &alignment_check_fault);
#ifdef CONFIG_X86_MCE
set_trap_gate(18, &machine_check_handler);
#endif
set_trap_gate(19, &simd_coprocessor_fault);
if (cpu_supports_fxsr) {
set_in_cr4(X86_CR4_OSFXSR);
printk(KERN_INFO "Suporte a FXSAVE/FXRSTOR habilitado.\n");
}
if (cpu_supports_xmm) {
set_in_cr4(X86_CR4_OSXMMEXCPT);
printk(KERN_INFO "Exceções SIMD FPU habilitadas.\n");
}
set_system_gate(SYSCALL_VECTOR, &system_call_handler);
cpu_init();
exception_init_hook();
}
As funções set_trap_gate, set_intr_gate, etc., inserem portas específicas na IDT. O vetor de chamada de sistema é definido como 0x80 no cabeçalho irq_vector.h.
1.3 Processamento de Hardware para Interrupções e Exceções
Quando ocorre uma interrupção ou exceção, o hardware executa as seguintes etapas assumindo uma porta de interrupção ou armadilha com mudança de privilégio:
- Localiza a TSS (Task State Segment) via registrador
TRe obtém o ponteiro de pilha kernel do campoESP0. - Salva na pilha kernel os registradores:
SS,ESP,EFLAGS,CS,EIP. - Se houver um código de erro de hardware, ele é empilhado.
- Desvia para o handler correspondente na IDT baseado no vetor.
1.4 Fluxo Genérico de Handlers de Exceção
No arquivo entry.S, handlers de exceção seguem um padrão. Macros como RING0_INT_FRAME e RING0_EC_FRAME definem informações de depuração para unwind, mas não geram código executável direto. O fluxo típico é:
ENTRY(example_exception_handler)
pushl $0 // Empilha código de erro zero se não fornecido pelo hardware
pushl $do_example_exception
jmp exception_common_entry
exception_common_entry:
pushl %ds
pushl %eax
xorl %eax, %eax
pushl %ebp
pushl %edi
pushl %esi
pushl %edx
decl %eax // eax = -1
pushl %ecx
pushl %ebx
cld
pushl %es
popl %ecx // Salva ES em ECX
movl ES(%esp), %edi // Endereço do handler C
movl ORIG_EAX(%esp), %edx // Código de erro
movl %eax, ORIG_EAX(%esp) // Marca como exceção com -1
movl %ecx, ES(%esp) // Restaura ES na pilha
movl $(__USER_DS), %ecx
movl %ecx, %ds
movl %ecx, %es
movl %esp, %eax // Ponteiro para pt_regs
call *%edi // Chama handler C
jmp ret_from_exception
O handler C recebe argumentos via registradores eax (ponteiro de regs) e edx (código de erro).
- Tratamento de Interrupções
A estrutura irq_desc em include/linux/irq.h descreve cada linha de IRQ, incluindo seu handler, chip de hardware e ações compartilhadas.
struct irq_descriptor {
void fastcall (*handle_irq)(unsigned int irq, struct irq_descriptor *desc, struct pt_regs *regs);
struct irq_chip *chip;
void *handler_data;
void *chip_data;
struct irqaction *action_chain;
unsigned int status_flags;
unsigned int disable_depth;
unsigned int wake_enable_depth;
unsigned int interrupt_count;
unsigned int unhandled_count;
spinlock_t lock;
#ifdef CONFIG_SMP
cpumask_t cpu_affinity;
unsigned int assigned_cpu;
#endif
// Outros campos para balanceamento e procfs
} ____cacheline_aligned;
extern struct irq_descriptor irq_desc_table[NR_IRQS];
NR_IRQS é definido como 224, representando o número de vetores de interrupção externos.
2.1 Inicialização dos Vetores de Interrupção
A função init_IRQ em arch/i386/kernel/i8259.c configura portas de interrupção para dispositivos externos:
void __init init_IRQ(void)
{
int vector_index;
pre_intr_init_hook();
for (vector_index = 0; vector_index < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); vector_index++) {
int irq_vector = FIRST_EXTERNAL_VECTOR + vector_index;
if (vector_index >= NR_IRQS)
break;
if (irq_vector != SYSCALL_VECTOR)
set_intr_gate(irq_vector, interrupt_handlers[vector_index]);
}
intr_init_hook();
setup_pit_timer();
if (boot_cpu_data.hard_math && !cpu_has_fpu)
setup_irq(FPU_IRQ, &fpu_irq_action);
irq_context_init(smp_processor_id());
}
A tabela interrupt_handlers é gerada em assembly em entry.S. Vetores de 0 a 31 são reservados para exceções e uso da Intel; interrupções externas começam no vetor 32.
2.2 Compartilhamento e Alocação Dinâmica de IRQ
Linhas de IRQ podem ser compartilhadas por múltiplos dispositivos. Cada IRQ descrita em irq_desc aponta para uma cadeia de estruturas irqaction:
struct irq_action {
irqreturn_t (*handler)(int irq, void *dev_instance, struct pt_regs *regs);
unsigned long flags;
cpumask_t affinity_mask;
const char *device_name;
void *device_id;
struct irq_action *next;
int irq_number;
struct proc_dir_entry *proc_entry;
};
Ao ocorrer uma interrupção compartilhada, todos os handlers na cadeia são executados. A alocação dinâmica associa linhas de IRQ a drivers apenas quando necessário, otimizando recursos.