Correções técnicas e explorações sobre conceitos fundamentais: diferença entre mmap() e malloc(), como worker threads funcionam no kernel, MAP_SHARED vs MAP_PRIVATE, e a relação entre ferramentas estáticas (nm, objdump) e dinâmicas (mmap) no ciclo de vida de programas.
Errata sobre considerações feitas no post anterior
1. system:
Tempo gasto dentro do kernel, executando chamadas de sistema (syscalls), I/O, alocação de memória, etc.
Correção:
O kernel não realiza alocação de memória no sentido tradicional (como o malloc do espaço de usuário).
O que ele faz é mapear regiões de memória por meio de chamadas como mmap(), que estabelecem a correspondência entre endereços virtuais e páginas físicas ou arquivos.
Esses mapeamentos podem ser:
- Anônimos: usados para alocação dinâmica (sem associação a arquivo), base do heap de processos. É como pegar uma folha em branco pra anotar algo temporário. Não é salva em lugar nenhum, quando joga fora, o conteúdo se perde.
- Baseados em arquivo: usados para mapear arquivos inteiros na memória (como executáveis, bibliotecas dinâmicas ou memória compartilhada via
MAP_SHARED). Reserva espaço na memória que espelha um arquivo existente no disco. É como abrir um livro físico sobre a mesa, você lê e escreve direto nas páginas. O kernel cuida pra trazer as páginas do livro pro seu alcance quando você precisa delas.
Após o mmap(), a gerência efetiva dessa memória (fragmentação, liberação, realocação etc.) é feita no espaço de usuário, por bibliotecas como libc, jemalloc ou tcmalloc.
O kernel apenas garante o isolamento, proteção e paginação do espaço virtual, sem participar do gerenciamento interno dessa área.
Se entendi corretamente: o mmap() cria o mapeamento virtual (a reserva de endereços), o user space escreve e lê dentro desse espaço, mas quem traz as páginas reais, seja uma página em branco (no caso anônimo) ou o conteúdo do arquivo (no caso baseado em arquivo) é o kernel, quando o processo acessa pela primeira vez.
Sobre MAP_SHARED (no mmap()):
Quando você usamos mmap() para mapear um arquivo na memória, você diz como quer que esse mapeamento se comporte. Um dos principais flags é o MAP_SHARED.
Exemplo: Vamos imaginar que dois processos querem “abrir o mesmo livro” (um arquivo) e trabalhar nele:
Se usa MAP_SHARED,
- Os dois estão literalmente vendo e escrevendo nas mesmas páginas do livro. Ou seja, qualquer modificação feita na memória é refletida no arquivo e visível para outros processos que também o mapearam com MAP_SHARED.
Se usa MAP_PRIVATE,
- Cada processo ganha uma cópia particular das páginas. Quando ele altera algo, é como se escrevesse em uma fotocópia do livro, a alteração não vai para o arquivo original, nem aparece para outros processos.
Uma analogia em JavaScript sobre MAP_PRIVATE e MAP_SHARED que faz sentido na minha cabeça:
// MAP_SHARED
let array1 = [1, 2, 3];
let array2 = array1; // outra variável aponta pro mesmo array
array1.reverse(); // inverte a ordem
console.log(array2); // [3,2,1] → mudança visível
// MAP_PRIVATE
let array1 = [1, 2, 3];
let array2 = [...array1]; // copia do array
array1.reverse();
console.log(array2); // [1,2,3] → mudança não afeta a cópia
Sobre Espaço de usuário (user space)
Imaginando que o sistema operacional é um prédio com dois andares:
-
Andar de baixo (kernel): Com acesso total ao hardware (memória física, disco, CPU, etc). Só o kernel pode, por exemplo, falar diretamente com a RAM, as placas de rede, o disco…
-
Andar de cima (user space): Onde rodam os programas comuns: navegador, editor de texto, etc… Esses programas não podem acessar diretamente o hardware, só pedem “permissão” ao kernel através de syscalls (como read(), write(), mmap(), open(), etc).
O espaço de usuário é a região de memória onde o programa roda isolado do kernel.
2. softirq:
Mostra custo de tratamento de rede e tarefas assíncronas.
Correção: O campo softirq é específico para o processamento de rede e algumas rotinas críticas de baixo nível. O termo “tarefas assíncronas” é impreciso, pois o kernel inteiro é estruturado de forma assíncrona, com múltiplas worker threads internas executando operações em paralelo.
Essas worker threads são entidades do kernel que processam filas de tarefas pendentes. Por exemplo: limpeza de caches, escrita assíncrona em disco, ou manipulação de pacotes de rede, sem bloquear o contexto de interrupção que as gerou. Assim, elas permitem que o kernel delegue trabalho pesado ou demorado para execução posterior, fora do caminho crítico da interrupção, mantendo o sistema responsivo e eficiente.
Exemplo de fluxo: [Hardware dispara evento] → [Contexto de interrupção no kernel: captura e registra rapidamente] → [Delegação para worker thread] → [Worker thread processa tarefa pesada: limpeza de cache / escrita assíncrona / manipulação de pacotes] → [Tarefa concluída, kernel continua responsivo]
Devaneios sobre nm, objdump e mmap
nmeobjdump
Eles mostram como o binário está estruturado, ou seja, como o programa foi “montado” antes de ser carregado pelo kernel.
nm: lista símbolos (funções, variáveis globais, seções).
objdump -h mostra as seções do ELF: .text, .data, .bss, .rodata, etc.
mmapemalloc
Esses entram quando o binário já está em execução, o kernel e a libc “montam o prédio” e passam a alocar/apontar regiões de memória reais:
O loader do kernel usa mmap() para carregar:
- .text (seção do código em binário),
- .data, .bss,
- bibliotecas dinâmicas,
- stack, heap etc.
Depois disso, o programa pode chamar malloc(), que internamente:
- Usa
brk()(para crescer o heap) ou mmap()(para alocar grandes blocos)
Analogia:
nm, objdump: Planta do prédio no papel (binário no disco)
mmap(): Kernel construindo os cômodos (endereços de memória)
malloc(): Programa colocando móveis/dados (alocação dinâmica)