Teu progresso
0 / 83 módulos0%
Estágio 01 · 01-02
BloqueadoO sistema operacional (SO) é o software que multiplexa a CPU, a memória, o disco e a rede entre múltiplos programas que pensam que cada um tem a máquina inteira. Sem entender o SO, conceitos como event loop, worker threads, epoll, process.fork, permission denied, EAGAIN, SIGTERM, pipe, mount parecem mágica.
Exemplos onde desconhecimento custa caro:
ulimit -n. Você não sabia que sockets, files, pipes, todos são FDs e há limite por processo.flock no mesmo arquivo. Você não conhecia file locks.fs.readFile e o Node mata o processo com OOM. Você não sabia distinguir leitura síncrona/buffered vs streaming.Este módulo te dá o vocabulário e os mecanismos do SO que sustentam toda a stack de runtime (Node, Postgres, Redis, Docker, Kubernetes).
O kernel é um programa especial que roda em modo privilegiado da CPU. Ele tem acesso direto a hardware (CPU, RAM, disco, rede). Aplicações rodam em user space, em modo não-privilegiado, e não podem tocar hardware diretamente.
┌────────────────────────────────────────┐
│ User space (apps, libs, runtime) │
│ Node, postgres, redis, etc │
└────────────────────┬───────────────────┘
│ system calls
▼
┌────────────────────────────────────────┐
│ Kernel (Linux, BSD, etc) │
│ Scheduler, VM, FS, Network, Drivers │
└────────────────────┬───────────────────┘
│
▼
Hardware
Quando uma app precisa fazer algo privilegiado (ler arquivo, abrir socket), ela faz uma system call (syscall), uma chamada que passa controle pro kernel via interrupção de software. Após o kernel executar, retorna ao user space.
Custo de syscall: ~100-1000 ns dependendo da operação. Não é grátis. Por isso runtimes como Node fazem batching (uma syscall writev em vez de várias write).
Um processo é uma instância em execução de um programa. Cada processo tem:
wait), stoppedCriação de processo (Linux): fork() cria uma cópia exata do processo atual. O filho recebe um PID novo, herda FDs do pai, e continua a execução do mesmo ponto do código. Geralmente o filho então faz exec() pra trocar o programa em execução por outro (assim que bash roda comandos).
Process tree: Linux tem init (PID 1) como ancestral de todos. pstree mostra a árvore.
Zombie process: quando processo termina, seu exit status fica esperando o pai chamar wait(). Se o pai nunca chama, o filho fica como zombie (consome só uma entrada na process table). Se o pai morre antes do filho, o filho é "adotado" por PID 1.
Uma thread é uma sequência de execução dentro de um processo. Threads do mesmo processo:
Vantagem: comunicação rápida via memória compartilhada. Custo: sincronização (mutex, semáforos, atomics) é difícil. Race conditions e deadlocks são fáceis de introduzir.
Em Node: o seu código JS roda em uma única thread (a main thread). Mas o Node usa thread pool internamente (libuv) pra I/O bloqueante (filesystem, DNS, crypto). Worker Threads (módulo node:worker_threads) permitem JS paralelo.
O kernel escalona threads/processos sobre os cores físicos (CPUs). Componentes:
Linux scheduler atual: CFS (Completely Fair Scheduler). Mantém uma red-black tree de threads runnable, ordenada por vruntime (tempo virtual de CPU acumulado). Sempre escolhe a thread com menor vruntime, daí "fair".
Estados de thread:
Quando uma thread faz syscall bloqueante (read num socket sem dado), o kernel a coloca em Sleeping. Quando o evento ocorre (dado chega), thread vai pra Runnable.
Implicações práticas:
taskset, sched_setaffinity) trava thread em cores específicos, útil pra cache locality em workloads críticas.CFS reinou de 2007 até 2024. A partir do Linux 6.6 (out/2023), o kernel mainline adotou EEVDF (Earliest Eligible Virtual Deadline First) substituindo CFS pra workloads não-realtime. Mudança discreta pra usuário comum, relevante pra quem ajusta latência fina.
EEVDF em uma frase: cada thread recebe um deadline virtual; scheduler sempre roda quem está "elegível" (acumulou direito) com menor deadline. CFS minimizava unfairness entre quem rodou; EEVDF agrega slice/lag explícitos, mais fácil raciocinar sobre latência tail.
Por que mudou:
sched_min_granularity_ns, etc.) pra balancear interatividade vs throughput. EEVDF expressa o trade-off via slice por entidade.Outras classes de scheduler em Linux (não substituídas por EEVDF):
SCHED_FIFO/SCHED_RR (real-time, prioridade fixa). Usada em audio, controle industrial. Sem timesharing, pode ser starver.SCHED_DEADLINE (EDF, Earliest Deadline First). Real-time hard. Você declara (runtime, deadline, period) e kernel admite só se cabe.SCHED_IDLE (background, prioridade mais baixa que normal).chrt muda a classe de um processo.Windows scheduler: multilevel feedback queue com 32 prioridades. Foreground apps recebem boost (UI responsivo), I/O-bound idem. Não é "fair" no sentido CFS, é "responsivo". A partir do Windows 11, há Thread Director que coopera com Intel hybrid CPUs (P-cores + E-cores) pra colocar work certo no core certo.
macOS/BSD scheduler: Mach + BSD scheduler layer. Threads têm quality of service class (QOS_CLASS_USER_INTERACTIVE, ..._USER_INITIATED, ..._UTILITY, ..._BACKGROUND). Apple Silicon tem heterogeneous cores (P/E), scheduler decide energy/perf.
Implicações práticas pra Senior:
SCHED_FIFO ou SCHED_DEADLINE em vez de só nice. Cuidado com starvation.cpu.weight é o que você ajusta em K8s resources.requests.cpu.taskset em P-core/E-core importa. Background scrapers em E-core, hot path em P-core.perf sched, bpftrace (eBPF), ou schedviz pra ver decisões reais do scheduler.Aplicação não chama syscalls diretamente, chama wrappers da libc (em C, libc é a implementação que faz a syscall). Em outras linguagens, há equivalente (Node usa libuv que chama syscalls).
Syscalls clássicas que você precisa saber existir:
| Categoria | Syscalls | O que faz |
|---|---|---|
| Process | fork, execve, wait, exit, getpid | Criar/finalizar processos |
| Memory | mmap, munmap, brk, mprotect | Alocar memória virtual |
| File I/O | open, read, write, close, lseek, stat | Ler/escrever arquivos |
| Filesystem | mkdir, unlink, rename, chmod, chown | Manipular FS |
| Network | socket, bind, listen, accept, connect, send, recv | TCP/UDP |
| I/O multiplex | select, poll, epoll_*, kqueue (BSD), IOCP (Windows) | Gerenciar muitos FDs |
| Signals | kill, signal, sigaction | Inter-process signaling |
| Time | clock_gettime, nanosleep | Relógio, sleep |
| IPC | pipe, socketpair, shmget, mq_open | Inter-process communication |
Use strace -f em qualquer processo Linux pra ver as syscalls que ele faz. Faça isso uma vez com node script.js: você vai entender o que o runtime está realmente fazendo.
Tudo no Linux é arquivo: ou pelo menos é exposto via API de arquivo. Sockets, pipes, terminais, arquivos regulares, dispositivos, tudo é representado por um file descriptor (FD): um inteiro pequeno que indexa uma tabela por processo.
FDs especiais:
Quando você abre arquivo (open), o kernel retorna o FD numericamente menor disponível. Quando faz socket, idem. Quando faz pipe, retorna dois FDs (read end + write end).
Limite de FDs por processo: ulimit -n (default 1024 ou 65536, varia). Servidores de alta concorrência aumentam pra milhões. Cada socket aberto consome 1 FD.
Closing FDs é responsabilidade do processo. Não fechar = leak. Use try/finally (em qualquer linguagem) ou RAII (C++/Rust).
Tabela de FDs após fork: o filho herda cópia da tabela. Os mesmos FDs apontam pras mesmas entries no kernel, então pai e filho compartilham posição em arquivos abertos!
Imagine read(fd, buf, 1024):
Sleep até dado chegar. Simples, mas escala mal, uma thread por conexão pra um servidor web é caro.O_NONBLOCK): se não há dado, retorna imediatamente com EAGAIN/EWOULDBLOCK. Aplicação tem que pollar/voltar depois. Permite uma thread gerenciar muitos FDs.select/poll/epoll): thread bloqueia em muitos FDs ao mesmo tempo, acorda quando qualquer um tem dado pronto. epoll (Linux) é eficiente até 100k+ conexões, usado pelo libuv (Node), nginx, redis.io_uring): kernel faz a operação em background, acorda app quando termina. Mais eficiente mas mais complexo.Por que isso importa pra Node:
epoll/kqueue/IOCP (via libuv) pra esperar muitos FDs.Signal é uma notificação assíncrona enviada pelo kernel a um processo. Lista clássica:
SIGINT (Ctrl+C), interrupçãoSIGTERM, pedido educado pra terminar (default kill <pid>)SIGKILL (9), terminação forçada, não captávelSIGSEGV, segmentation fault (acesso a memória inválida)SIGCHLD, filho terminouSIGPIPE, escreveu em pipe sem leitorSIGUSR1, SIGUSR2, definidos pelo usuárioAplicações podem capturar signals (exceto SIGKILL e SIGSTOP) com signal() ou sigaction(). Em Node: process.on('SIGTERM', handler).
Padrão importante: graceful shutdown. Captura SIGTERM, fecha conexões abertas, espera in-flight requests terminarem, depois encerra. Kubernetes envia SIGTERM, espera terminationGracePeriodSeconds, depois SIGKILL.
Mecanismos pra processos se comunicarem:
| no shell): stream unidirecional, criada com pipe() ou ao spawnar com popen. Usado em Node via child_process./var/run/docker.sock), Postgres (default usa Unix socket pra conexões locais).shmget, mmap com MAP_SHARED): regiões de memória mapeadas em múltiplos processos. Mais rápido, mas exige sincronização manual.Cada arquivo tem owner (UID), group (GID) e bits de permissão:
chmod 755 file = rwxr-xr-x.Bits especiais:
sudo funciona internamente./tmp), só owner pode deletar arquivos.Princípio de menor privilégio: rode aplicações com usuário não-root (Docker USER appuser). Capabilities Linux (CAP_NET_BIND_SERVICE, etc.) permitem dar permissões granulares sem dar root inteiro.
Linux 2026 não é o Linux 2015. Três tecnologias mudaram o jogo: io_uring (async I/O sem syscalls em hot path), cgroups v2 + PSI (pressure-aware resource control), eBPF (kernel programável em userspace, sem rebuild). Quem ainda raciocina em epoll + thread-pool + cgroups v1 + iptables opera com vocabulário deprecated.
1. io_uring — async I/O moderno (Linux 5.1+, mainstream desde 5.10 LTS, hardened em 6.1+ LTS)
Submission Queue (SQ) + Completion Queue (CQ) shared entre kernel e userspace via mmap. Userspace escreve SQE (Submission Queue Entry), kernel processa, escreve CQE. Com IORING_SETUP_SQPOLL, kernel thread polla SQ — zero syscalls em hot path. Ganho real: PostgreSQL 17 (Set 2024) introduziu io_uring backend reportando 30-50% throughput improvement em random I/O (PostgreSQL 17 release notes). MySQL InnoDB, ScyllaDB, libuv (Node.js 20+ opcional) também suportam.
// io_uring read+write batched (liburing wrapper)
struct io_uring ring;
io_uring_queue_init(32, &ring, 0);
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd_in, buf, BUF_SZ, 0);
sqe->flags |= IOSQE_IO_LINK; // chain próximo
sqe = io_uring_get_sqe(&ring);
io_uring_prep_write(sqe, fd_out, buf, BUF_SZ, 0);
io_uring_submit(&ring); // 1 syscall (zero com SQPOLL)
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
io_uring_cqe_seen(&ring, cqe);
Restrição crítica: io_uring teve 4+ CVEs sandbox-escape em 2022-2023. Google Security blog (Jul 2023) anunciou que Chrome OS, Docker default seccomp, e Android 15 desabilitaram io_uring no syscall filter por padrão. Em produção: avalie threat model — perf gain vs attack surface. Em container multi-tenant não-confiável, deixe desabilitado. Em backend trusted (database, file server dedicado), habilite.
2. epoll vs io_uring — decision matrix 2026
| Critério | epoll vence | io_uring vence |
|---|---|---|
| Simplicidade | sim (libev/libuv maduras) | curva ainda íngreme |
| Threat model restrito | sim (battle-tested) | seccomp issues |
| I/O massivo paralelo | não (1 syscall por op) | sim (batch + zero-copy) |
| Registered buffers / fixed FDs | n/a | sim (3-5x speedup) |
| Sandboxed/multi-tenant | sim | bloqueado por seccomp |
Default conservador: epoll. Default agressivo perf: io_uring com SQPOLL pinned em CPU dedicada.
3. cgroups v2 (default em Ubuntu 22.04+, Debian 12+, RHEL 9+, Fedora 31+)
V1 tinha múltiplas hierarchies (cpu, memory, blkio cada uma sua árvore) — bug-prone. V2 é single unified hierarchy, controllers principais: cpu, memory, io, pids, cpuset. PSI (Pressure Stall Information) Linux 4.20+ mede quanto tempo processos esperam por CPU/memory/io — métrica direta de saturação, exposta em /proc/pressure/{cpu,memory,io} e per-cgroup.
oomd (Facebook) e systemd-oomd (default Ubuntu 22.04+) usam thresholds PSI pra OOM-kill antes do kernel OOM (que é catastrófico, freezeia o sistema enquanto decide).
# /etc/systemd/system/dispatch.service
[Service]
ExecStart=/usr/bin/dispatch-worker
MemoryHigh=512M # throttle ANTES de hard limit
MemoryMax=768M # hard limit (OOM-kill)
IOWeight=200 # 1-10000, default 100
CPUWeight=150 # CPU share relativo
TasksMax=512 # pids.max
4. eBPF observability stack 2026
Kernel programável: você carrega bytecode verificado em runtime, attach a tracepoints/kprobes/uprobes/XDP/TC. Sem rebuild, sem reboot, sem kernel module.
bpftrace (one-liners, like awk pra kernel), Tracee (Aqua Security, runtime security), Pixie (NewRelic, no-instrumentation k8s).opensnoop, execsnoop, tcpconnect), Parca (continuous profiling), Pyroscope (Grafana).Adoção real: Cilium em Google GKE Dataplane V2, AWS EKS auto-attach, Microsoft AKS "Azure CNI Powered by Cilium" GA Q4 2024 (CNCF Cilium release notes). Cilium em GKE substitui kube-proxy → latência p99 service-to-service cai 30-40%.
5. eBPF programs — exemplos 2026
# Trace todo execve syscall (ver o que está sendo executado)
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve {
printf("%s -> %s\n", comm, str(args->filename));
}'
# Latência de open() por processo
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { @start[tid] = nsecs; }
tracepoint:syscalls:sys_exit_openat /@start[tid]/ {
@us[comm] = hist((nsecs - @start[tid]) / 1000); delete(@start[tid]); }'
Stack moderna: CO-RE (Compile Once, Run Everywhere) + libbpf. Substitui BCC (older, precisava kernel headers + clang em runtime — bloated, lento). Requer kernel 5.4+ pra CO-RE básico, 5.8+ pra full features (LSM hooks, ring buffer, trampolines).
6. PSI-driven autoscaling em Kubernetes 2026
PSI exposto em pods via cAdvisor (container_pressure_memory_full_seconds_total). KEDA (CNCF graduated) + Prometheus adapter escala baseado em pressure metrics — direto, não em CPU% (proxy ruim).
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata: { name: dispatch-worker }
spec:
scaleTargetRef: { name: dispatch-worker }
minReplicaCount: 2
maxReplicaCount: 50
cooldownPeriod: 180 # evita flapping
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus:9090
threshold: '0.30' # 30% memory pressure full
query: |
avg(rate(container_pressure_memory_full_seconds_total{pod=~"dispatch-.*"}[2m]))
Adoção real: Spotify reportou 25% cost reduction em batch workers migrando de CPU-based HPA pra PSI-aware HPA (Spotify engineering blog 2024).
7. journald — observability nativa Linux 2026
systemd-journald é o default em Ubuntu 22.04+, RHEL 8+, Debian 12+. Logs binários estruturados (campos indexados: _PID, _UID, _TRANSPORT=audit, _SYSTEMD_UNIT), persistência opt-in via Storage=persistent em /etc/systemd/journald.conf (cria /var/log/journal/). Forward pra Grafana Loki / Datadog via systemd-journal-upload ou Vector. journalctl -u dispatch.service -f --since "10 min ago" substitui tail -F /var/log/syslog | grep.
Anti-patterns 2026
systemd.unified_cgroup_hierarchy=1).bpftrace ou libbpf-based tools.@map = lhist() com sampling, não printf em todo evento.Storage=persistent — logs perdidos a cada reboot, debugging post-mortem impossível.IORING_SETUP_SQ_AFF + sq_thread_cpu.Logística applied. O courier-tracking ingestor usa io_uring registered buffers pra ler GPS UDP packets em batch (zero-copy, fixed buffers pré-alocados). O dispatch service roda em systemd unit com MemoryHigh=512M IOWeight=200 CPUWeight=200 — isolado de batch jobs noturnos (MemoryHigh=2G IOWeight=50). bpftrace em produção monitora execve syscalls do dispatch process — qualquer spawn inesperado dispara alert (Falco rule). Cilium CNI substituiu kube-proxy no cluster: latência p99 dispatch-to-postgres caiu de 8ms pra 4.5ms. Hubble visualiza fluxo dispatch → backend em tempo real.
Cruza com. 01-02 §2.10 (capabilities + seccomp são complementos a cgroups — defense in depth), 01-03 (Cilium opera em camada XDP/TC do kernel, abaixo do socket), 03-02 (Docker runtime respeita cgroups; entender v2 é entender resource limits de containers), 03-03 (cgroups v2 + PSI dirigem HPA/VPA modernos), 03-07 (eBPF + journald + RUM são as três camadas de observability 2026).
Fontes inline. PostgreSQL 17 release notes (Set 2024); Google Security blog "io_uring CVEs and our response" (Jul 2023); Spotify engineering blog "PSI-aware HPA" (2024); CNCF Cilium release notes "Azure CNI Powered by Cilium GA" (Q4 2024); Linux kernel docs Documentation/admin-guide/cgroup-v2.rst e Documentation/accounting/psi.rst.
Pra passar o Portão Conceitual, sem consultar:
SCHED_OTHER / SCHED_FIFO / SCHED_RR / SCHED_DEADLINE em Linux. Quando usar cada um.fork()./usr/bin/passwd).Implementar um mini-shell Unix em TypeScript.
Construa um REPL que aceite comandos e execute como um shell (bash-like). Suporte:
ls, cat foo.txt, node script.js, etc. Use child_process.spawn ou equivalente.cat foo.txt | grep bar | wc -l, encadeamento de processos.ls > out.txt, cat < in.txt, command 2> err.log.sleep 10 &, não bloqueia o prompt.cd <path>, exit, pwd, export VAR=value.SIGINT ao processo em foreground sem matar o shell.wait em filhos terminados.shelljs, execa com complex modes). Apenas node:child_process, node:readline, e Node API base.yargs-parser), mas o controle de processos tem que ser seu.strace no shell e analise).jobs, fg, bg, kill %1.~/.myshell_history).epoll esperando em N sockets é o fundamento do servidor Node.*.lock files) pra serializar escritas no .git/. Falhas em fork+exec são origem de "git stuck on lock" issues.epoll (Linux), kqueue (BSD/macOS), IOCP (Windows). Worker threads são threads kernel.strace: trace de syscalls de um processo.ltrace: trace de chamadas a libraries (libc).lsof -p <pid>: lista todos FDs abertos por um processo.htop, top: estado de processos, threads, scheduling.ps -ef, ps auxf: snapshot de processos.pidstat, vmstat, iostat: estatísticas finas./proc/<pid>/: filesystem virtual com info de cada processo (status, fd, maps, etc).bpftrace, eBPF: tracing avançado low-overhead.kernel/sched/ pra scheduler, fs/ pra filesystems.man 2 <syscall> (seção 2 = syscalls). Use sempre.Encerramento: após 01-02 você consegue raciocinar sobre runtime: por que o Node escala bem em I/O e mal em CPU-bound, por que Postgres usa multi-process em vez de multi-thread (até versão recente), por que Docker é "leve" comparado a VMs. Esse modelo mental é a base de toda discussão de operação em escala.
Threshold de Maestria
Acerte todas as 5 pra marcar o módulo como concluído. Sem pressa, sem timer. Tudo fica salvo no teu navegador.
Q1Por que uma syscall não é grátis (custa ~100-1000 ns) e o que runtimes como Node fazem pra mitigar?
Q2Qual destas NÃO é uma diferença real entre processo e thread no Linux?
Q3O Linux 6.6 substituiu CFS por EEVDF como scheduler default não-realtime. Qual é a vantagem prática principal?
Q4Após fork() em Linux, o que acontece com os file descriptors do processo pai?
Q5Você tem um servidor de banco trusted que faz I/O massivo e batched. epoll vs io_uring — qual a decisão certa em 2026 e por quê?
Destrava
01-02 é prereq dos seguintes módulos: