Kernel e Internals

Chamadas de sistema, interrupções e drivers de dispositivo no kernel space

• 8 min de leitura

Chamadas de sistema, interrupções e drivers de dispositivo no kernel space
O kernel space é a área de memória onde o núcleo do sistema operacional executa, com acesso irrestrito a todo o hardware e memória do sistema. O user space, por sua vez, é o ambiente isolado onde processos de usuário rodam, sem acesso direto a recursos críticos. Essa separação é fundamental para a estabilidade e segurança do sistema: um erro em user space afeta apenas o processo atual, enquanto um erro em kernel space pode derrubar todo o sistema.

Chamadas de sistema, interrupções e drivers de dispositivo no kernel space

1. Fundamentos do Kernel Space e Modos de Operação

1.1. Diferenças entre kernel space e user space

O kernel space é a área de memória onde o núcleo do sistema operacional executa, com acesso irrestrito a todo o hardware e memória do sistema. O user space, por sua vez, é o ambiente isolado onde processos de usuário rodam, sem acesso direto a recursos críticos. Essa separação é fundamental para a estabilidade e segurança do sistema: um erro em user space afeta apenas o processo atual, enquanto um erro em kernel space pode derrubar todo o sistema.

1.2. Modos de execução: ring 0 vs. ring 3

Na arquitetura x86, existem quatro níveis de privilégio (rings 0 a 3). O kernel opera no ring 0 (mais privilegiado), com acesso total a instruções privilegiadas (como cli, sti, lgdt) e a todo o espaço de endereçamento. Processos de usuário executam no ring 3, com restrições severas: não podem executar instruções privilegiadas nem acessar memória do kernel diretamente. Qualquer tentativa de violação gera uma exceção (page fault ou general protection fault).

1.3. Transição de contexto

A transição entre user space e kernel space é cara em termos de desempenho, pois envolve:
- Salvar registradores do modo usuário
- Trocar a pilha (para a pilha do kernel)
- Alterar o nível de privilégio
- Verificar permissões e validar parâmetros

Essa troca ocorre principalmente via chamadas de sistema, interrupções ou exceções.

2. Chamadas de Sistema: Interface entre Usuário e Kernel

2.1. Mecanismo de chamada

Chamadas de sistema (syscalls) são a porta de entrada para serviços do kernel. Em x86-64, a instrução syscall é usada para transferir controle ao kernel. O kernel mantém uma tabela de syscalls (sys_call_table) indexada por número, onde cada entrada aponta para um handler específico.

2.2. Fluxo de execução

Quando um programa chama open():
1. A biblioteca glibc empacota os argumentos e executa syscall com o número da syscall em rax
2. O CPU muda para ring 0 e pula para o entry point definido no Model-Specific Register (MSR) LSTAR
3. O kernel salva o contexto, consulta sys_call_table[rax] e executa o handler correspondente
4. Após a execução, o kernel restaura o contexto e retorna ao user space via sysret

2.3. Exemplo prático: rastreando uma syscall

Para rastrear chamadas de sistema, podemos usar strace:

$ strace -e openat cat /etc/passwd 2>&1 | head -5
openat(AT_FDCWD, "/etc/passwd", O_RDONLY) = 3
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3

Um exemplo simplificado de como uma syscall poderia ser invocada em assembly:

; Exemplo conceitual - chamada syscall write (número 1)
mov rax, 1          ; número da syscall (write)
mov rdi, 1          ; fd = stdout
lea rsi, [msg]      ; buffer
mov rdx, 13         ; tamanho
syscall             ; invoca o kernel

3. Tratamento de Interrupções e Exceções

3.1. Interrupções de hardware vs. software

Interrupções de hardware são geradas por dispositivos (ex: teclado pressionado, pacote de rede recebido). Exceções são geradas pelo CPU em situações excepcionais (divisão por zero, page fault). Traps são interrupções intencionais geradas por software (ex: int 0x80 em sistemas legados).

3.2. Descritores de interrupção (IDT)

A Interrupt Descriptor Table (IDT) mapeia cada vetor de interrupção (0-255) para um handler. Quando uma interrupção ocorre, o CPU consulta a IDT, verifica o nível de privilégio e executa o handler no ring apropriado.

3.3. Contextos de interrupção

O kernel divide o tratamento em duas partes:
- Top half: executa imediatamente, com interrupções desabilitadas. Deve ser rápido (ex: apenas salvar dados do dispositivo).
- Bottom half: executa posteriormente, com interrupções habilitadas. Implementado via tasklets, workqueues ou softirqs.

// Exemplo de registro de interrupção (conceitual)
request_irq(irq_number, my_interrupt_handler, IRQF_SHARED, "my_device", dev_id);

4. Drivers de Dispositivo: Estrutura e Registro no Kernel

4.1. Tipos de drivers

  • Drivers de caractere: dispositivos que transmitem dados byte a byte (ex: teclado, mouse, portas seriais)
  • Drivers de bloco: dispositivos que trabalham com blocos de dados (ex: discos rígidos, SSDs)
  • Drivers de rede: gerenciam interfaces de rede (ex: Ethernet, Wi-Fi)

4.2. Operações básicas

Todo driver de caractere implementa a estrutura file_operations, que define callbacks para operações de arquivo:

struct file_operations my_fops = {
    .owner   = THIS_MODULE,
    .open    = my_open,
    .read    = my_read,
    .write   = my_write,
    .release = my_release,
    .unlocked_ioctl = my_ioctl,
};

4.3. Exemplo de código: esqueleto de um driver de caractere mínimo

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "mydevice"
#define CLASS_NAME  "myclass"

static int major;
static struct class *my_class;

static int my_open(struct inode *inodep, struct file *filep) {
    printk(KERN_INFO "mydevice: device opened\n");
    return 0;
}

static ssize_t my_read(struct file *filep, char __user *buffer,
                       size_t len, loff_t *offset) {
    char msg[] = "Hello from kernel!\n";
    size_t msg_len = strlen(msg);

    if (*offset >= msg_len) return 0;
    if (len > msg_len - *offset) len = msg_len - *offset;

    if (copy_to_user(buffer, msg + *offset, len)) return -EFAULT;

    *offset += len;
    return len;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open  = my_open,
    .read  = my_read,
};

static int __init my_init(void) {
    major = register_chrdev(0, DEVICE_NAME, &fops);
    if (major < 0) return major;

    my_class = class_create(THIS_MODULE, CLASS_NAME);
    device_create(my_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);

    printk(KERN_INFO "mydevice: registered with major %d\n", major);
    return 0;
}

static void __exit my_exit(void) {
    device_destroy(my_class, MKDEV(major, 0));
    class_destroy(my_class);
    unregister_chrdev(major, DEVICE_NAME);
    printk(KERN_INFO "mydevice: unregistered\n");
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");

5. Comunicação entre Interrupções e Drivers

5.1. Mapeamento IRQ para driver

O driver solicita uma IRQ específica usando request_irq():

static irqreturn_t my_irq_handler(int irq, void *dev_id) {
    // Top half: salva dados rapidamente
    // Agenda bottom half para processamento posterior
    return IRQ_HANDLED;
}

// No init do driver:
if (request_irq(irq_number, my_irq_handler, IRQF_SHARED, "my_device", dev)) {
    printk(KERN_ERR "Failed to register IRQ\n");
    return -EBUSY;
}

5.2. Buffering e sincronização

O kernel oferece mecanismos de sincronização para evitar condições de corrida:
- Spinlocks: para contextos onde não se pode dormir (interrupções)
- Semáforos/mutexes: para contextos que podem dormir
- Filas de espera (wait queues): para bloquear processos até que um evento ocorra

5.3. Estudo de caso: driver de teclado simplificado

Um driver de teclado processa interrupções geradas pelo controlador PS/2 ou USB HID. O handler top half lê o scancode do registro do hardware e acorda um processo em user space que lê os dados via read().

6. Depuração e Ferramentas no Kernel Space

6.1. Logs do kernel com printk

printk() é a função principal para logging no kernel. Os níveis de mensagem são:

printk(KERN_EMERG   "Sistema em pânico!\n");   // Nível 0
printk(KERN_ALERT   "Ação imediata necessária\n"); // Nível 1
printk(KERN_CRIT    "Condição crítica\n");     // Nível 2
printk(KERN_ERR     "Erro\n");                  // Nível 3
printk(KERN_WARNING "Aviso\n");                 // Nível 4
printk(KERN_NOTICE  "Nota\n");                  // Nível 5
printk(KERN_INFO    "Informação\n");            // Nível 6
printk(KERN_DEBUG   "Depuração\n");             // Nível 7

6.2. Inspeção via /proc, sysfs e debugfs

  • /proc/devices — lista drivers de caractere e bloco registrados
  • /proc/interrupts — mostra estatísticas de interrupções por CPU
  • /sys/class/ — expõe dispositivos do kernel organizados por classe
  • /sys/kernel/debug/ — interface de depuração (requer montagem do debugfs)

6.3. Ferramentas avançadas

  • ftrace: rastreia chamadas de função no kernel, incluindo syscalls e handlers de interrupção
  • perf: amostragem de eventos de hardware e software, incluindo interrupções
  • kgdb: depurador remoto para kernel Linux via serial ou Ethernet

7. Considerações de Desempenho e Segurança

7.1. Latência de interrupções

Interrupções frequentes podem degradar o desempenho. Técnicas de redução incluem:
- Interrupt coalescing: agrupar múltiplas interrupções em uma
- NAPI (New API): para drivers de rede, alterna entre polling e interrupções
- Threaded IRQs: tratar interrupções como threads do kernel, permitindo preempção

7.2. Segurança em drivers

Drivers são um ponto crítico de segurança. Práticas essenciais:
- Validar toda entrada vinda de user space com copy_from_user()/copy_to_user()
- Verificar limites de buffers para evitar buffer overflows
- Usar capable() para verificar privilégios do processo chamador
- Evitar uso de memcpy() sem verificação de tamanho

7.3. Boas práticas

  • devres: gerenciamento automático de recursos (managed device resources)
  • DMA API: para acesso direto à memória por dispositivos
  • Modularização: dividir drivers em módulos carregáveis para facilitar manutenção
  • Uso de APIs modernas: preferir devm_* functions que liberam recursos automaticamente

Referências

💬 Comentários
Mais em Kernel e Internals