Análise do Mecanismo de Interrupções no Linux 2.6.18

  1. 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, &divide_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:

  1. Localiza a TSS (Task State Segment) via registrador TR e obtém o ponteiro de pilha kernel do campo ESP0.
  2. Salva na pilha kernel os registradores: SS, ESP, EFLAGS, CS, EIP.
  3. Se houver um código de erro de hardware, ele é empilhado.
  4. 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).

  1. 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.

Tags: linux-2.6.18 interrupcoes x86 IDT TSS

Publicado em 6-22 22:22