System calls
Toda vez que seu programa acessa um arquivo, abre um socket ou cria um processo, ele usa uma system call — uma porta controlada do espaço de usuário para o kernel. Veja o que acontece em cada camada.
A barreira entre usuário e kernel
Seu programa roda no modo usuário (Ring 3 em x86) — sem acesso direto
ao hardware. Quando precisa de algo do SO (abrir arquivo, ler socket, alocar memória),
usa uma system call: executa a instrução syscall, que faz
a CPU salvar o contexto do usuário e pular para o handler no kernel (Ring 0).
O kernel executa a operação solicitada com seus privilégios e retorna o resultado.
A CPU restaura o contexto do usuário e o programa continua. Você raramente chama
syscalls diretamente — a glibc (ou libc em geral) provê wrappers em C
que definem os argumentos nos registradores corretos e executam syscall.
syscall em x86-64 leva
~100ns — comparado a ~1ns de uma chamada de função normal. A razão: salvar registradores,
mudar o modo da CPU, invalidar partes do TLB, e (pós-Spectre) barreiras de especulação.
Por isso interfaces de alto desempenho como io_uring (Linux 5.1) agrupam múltiplas
operações em uma única syscall — reduzindo o overhead de transição.
Rastreador de system calls
Selecione uma syscall e avance pelas camadas de execução — do código de usuário, pela instrução syscall, pelo kernel, até o retorno. Veja os erros comuns de cada chamada.
open("arquivo.txt", O_RDONLY)
→ 3Abre um arquivo e retorna um file descriptor (inteiro). O kernel busca o inode no sistema de arquivos, verifica permissões e cria uma entrada na tabela de arquivos abertos do processo.
Modos de proteção (rings)
// Modos de proteção da CPU (x86) // Ring 3 (User mode) — código de usuário // - Sem acesso direto a hardware (disco, rede, I/O de portas) // - Sem escrita em registradores de controle (CR0, CR3) // - Sem instruções privilegiadas (HLT, IN/OUT, CLI/STI) // - Espaço de endereçamento virtual limitado ao processo // Ring 0 (Kernel mode) — código do kernel // - Acesso total ao hardware // - Controle do MMU (page tables, TLB) // - Gerencia interrupções // - Enxerga toda a memória física // A transição Ring 3 → Ring 0 acontece via: // syscall (x86-64 Linux — rápido, ~100ns) // int 0x80 (x86-32 Linux legado — mais lento, ~300ns) // sysenter (Windows 32-bit — variante rápida do int 0x80) // VDSO: funções como gettimeofday() mapeadas no espaço do usuário // evitam a transição para o kernel → ~5ns!
ABI de syscalls em x86-64 Linux
// ABI de system calls x86-64 Linux // Antes de executar a instrução "syscall": // RAX = número do syscall // RDI = 1º argumento // RSI = 2º argumento // RDX = 3º argumento // R10 = 4º argumento (não RCX! — syscall usa RCX internamente) // R8 = 5º argumento // R9 = 6º argumento // Exemplo: write(1, "hello ", 6) sem glibc section .text global _start _start: mov rax, 1 ; SYS_write = 1 mov rdi, 1 ; fd = 1 (stdout) mov rsi, msg ; buffer mov rdx, 6 ; count syscall ; executa syscall — RAX recebe retorno (6) mov rax, 60 ; SYS_exit = 60 xor rdi, rdi ; exit code = 0 syscall section .data msg: db "hello "
read,
write, send) para o submission queue sem syscall — o kernel
as consome em background. Conclusões chegam via completion queue, sem syscall para buscar.
Isso reduz o número de syscalls de N (uma por operação) para 1–2 (submeter lote +
aguardar). Nginx e liburing já usam io_uring para throughput próximo a zero-copy.
Rastreando system calls
Mini projeto: use strace ./seu_programa em qualquer executável Linux. Observe as syscalls de inicialização (execve, mmap, brk) antes do seu main() executar. Use strace -c para ver o sumário de quantas vezes cada syscall foi chamada e o tempo total.
Projeto principal: escreva um programa em assembly x86-64 (ou C com syscall()) que abre um arquivo, lê seu conteúdo e escreve em stdout — sem usar nenhuma função da libc (sem printf, fopen, etc). Apenas syscalls diretas: open (nr=2), read (nr=0), write (nr=1), close (nr=3), exit_group (nr=231).
Desafio extra: implemente um mini strace em C usando ptrace(PTRACE_SYSCALL, pid, ...): faça fork de um processo filho, anexe com ptrace, e intercepte cada syscall — imprimindo o número, os 6 argumentos e o valor de retorno. Compare o overhead de ptrace (strace real pode ser 10–100× mais lento que execução normal).
Teste sua intuição
syscall funciona e o que muda na CPU?
Onde você encontra isso
Seccomp — filtragem de syscalls
Seccomp (Secure Computing Mode) permite que um processo defina quais syscalls ele aceita fazer — todas as outras resultam em SIGKILL. Chrome usa seccomp para sandboxar processos de renderer: eles podem fazer read/write, mas não open ou exec. Docker usa seccomp profiles para limitar o que containers podem fazer.
vDSO — syscalls sem syscall
O vDSO (Virtual Dynamic Shared Object) é uma biblioteca mapeada pelo kernel no espaço de usuário. gettimeofday() e clock_gettime() são implementadas no vDSO — lêem um valor de memória compartilhada atualizado pelo kernel via HPET/TSC, sem precisar da instrução syscall. Resultado: de ~100ns para ~5ns por chamada.
Python e o GIL
O Python GIL (Global Interpreter Lock) é um mutex que garante que apenas uma thread Python execute bytecode por vez. Mas durante syscalls de I/O bloqueante, o GIL é liberado — outras threads Python podem executar enquanto o read() aguarda dados. Por isso código Python I/O-bound escala com threads; CPU-bound não.