Trilha 08 · Sistema operacional

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.

① Intuição

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.

Custo de uma syscall: a instrução 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.
② Visualização interativa

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.

SYSCALL #2VFS (Virtual File System)
open("arquivo.txt", O_RDONLY)
3

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

ERROS COMUNS
ENOENT (arquivo não existe)
EACCES (sem permissão de leitura)
EMFILE (too many open files)
CAMADAS DE EXECUÇÃO
👤
Código do usuário
fd = open("arquivo.txt", O_RDONLY);
📦
glibc wrapper
syscall(SYS_open, "arquivo.txt", O_RDONLY); // carrega nr=2 em RAX
CPU: instrução syscall
CPU muda para Ring 0 (kernel mode). Salva contexto de usuário.
🐧
Kernel: sys_open()
Valida caminho, procura inode no VFS, verifica permissões (uid/gid).
💾
Driver de FS (ext4)
Localiza blocos do arquivo no disco. Preenche struct file.
↩️
Retorno ao usuário
fd = 3 retornado em RAX. CPU volta para Ring 3 (user mode).
Clique para ver camada por camada
③ Explicação técnica

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
"
io_uring (Linux 5.1+): interface de I/O assíncrona que usa dois ring buffers compartilhados entre kernel e userspace. O programa envia operações (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.
④ Projeto para programar

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

⑤ Exercícios rápidos

Teste sua intuição

O que é uma system call e por que ela existe?
Como a instrução syscall funciona e o que muda na CPU?
Qual é o papel da glibc nas system calls?
⑥ Aplicações no mundo real

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.

← Anterior: Deadlock ✓ Concluir trilha: Sistema operacional