<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex Volnei Galante</title>
    <description>The latest articles on DEV Community by Alex Volnei Galante (@lexgalante).</description>
    <link>https://dev.to/lexgalante</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1256214%2F28ed753f-f53c-42e5-850e-341bb02914f1.jpg</url>
      <title>DEV Community: Alex Volnei Galante</title>
      <link>https://dev.to/lexgalante</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lexgalante"/>
    <language>en</language>
    <item>
      <title>Kernel Linux para Desenvolvedores Backend - Gerenciamento de Memória Parte VIII</title>
      <dc:creator>Alex Volnei Galante</dc:creator>
      <pubDate>Fri, 21 Aug 2026 01:03:13 +0000</pubDate>
      <link>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-viii-2e35</link>
      <guid>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-viii-2e35</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
Última encerramento da "saga" de gerenciamento de memória. Na Parte VII vimos reclaim, PSI e o OOM Killer. Agora fechamos com o mecanismo que amarra tudo em produção — os &lt;strong&gt;cgroups v2&lt;/strong&gt; — e a conexão profunda com &lt;strong&gt;Python, .NET e Go&lt;/strong&gt;, diagnóstico e estudos de caso.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Sumário
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Memory Control Groups (cgroups v2)

&lt;ul&gt;
&lt;li&gt;memory.max, memory.high e memory.low&lt;/li&gt;
&lt;li&gt;Memory accounting&lt;/li&gt;
&lt;li&gt;Hierarquia e eventos&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Conexão com Backend: Python&lt;/li&gt;
&lt;li&gt;Conexão com Backend: .NET&lt;/li&gt;
&lt;li&gt;Conexão com Backend: Go&lt;/li&gt;
&lt;li&gt;Técnicas de Diagnóstico e Tuning&lt;/li&gt;
&lt;li&gt;Boas Práticas&lt;/li&gt;
&lt;li&gt;Estudos de Caso&lt;/li&gt;
&lt;li&gt;Encerramento da Série de Memória&lt;/li&gt;
&lt;li&gt;Referências Bibliográficas&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Memory Control Groups (cgroups v2)
&lt;/h2&gt;

&lt;p&gt;Na Parte V falamos de alocação &lt;strong&gt;global vs local&lt;/strong&gt; de frames. Os &lt;strong&gt;cgroups&lt;/strong&gt; (control groups) são como o Linux moderno reintroduz fronteiras &lt;strong&gt;locais&lt;/strong&gt; sobre um sistema global — e são a fundação de &lt;strong&gt;todo&lt;/strong&gt; container (Docker, Podman, Kubernetes). Todo pod que você sobe é, no fundo, um conjunto de processos dentro de um cgroup com limites de memória.&lt;/p&gt;

&lt;p&gt;Vamos focar em &lt;strong&gt;cgroups v2&lt;/strong&gt; (a versão unificada, padrão nas distros modernas). O controlador de memória expõe arquivos num diretório do sistema de arquivos &lt;code&gt;cgroup&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Um container/serviço vive em algo como:&lt;/span&gt;
/sys/fs/cgroup/system.slice/docker-&amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;.scope/
&lt;span class="c"&gt;# ou, no Kubernetes:&lt;/span&gt;
/sys/fs/cgroup/kubepods.slice/.../&amp;lt;pod&amp;gt;/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  memory.max, memory.high e memory.low
&lt;/h3&gt;

&lt;p&gt;Os três controles essenciais, e a diferença entre eles é &lt;strong&gt;crucial&lt;/strong&gt; e frequentemente ignorada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     0 ─────────── memory.low ─────── memory.high ─────── memory.max
     │                  │                   │                  │
  protegido do    abaixo daqui,        throttle:          limite HARD:
  reclaim         reclaim "poupa"      reclaim agressivo   estourou → OOM
                  este cgroup          + throttle da app   do cgroup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;memory.max&lt;/code&gt;&lt;/strong&gt; — o &lt;strong&gt;limite absoluto (hard limit)&lt;/strong&gt;. Se o cgroup tenta passar disso e o reclaim não consegue segurar, o &lt;strong&gt;OOM Killer do cgroup&lt;/strong&gt; dispara (o &lt;code&gt;OOMKilled&lt;/code&gt; do Kubernetes). É o teto intransponível.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;memory.high&lt;/code&gt;&lt;/strong&gt; — o &lt;strong&gt;limite suave (throttle)&lt;/strong&gt;. Ao ultrapassá-lo, o cgroup &lt;strong&gt;não&lt;/strong&gt; é morto; em vez disso, o kernel aplica reclaim agressivo e &lt;strong&gt;desacelera (throttle)&lt;/strong&gt; as alocações do grupo, criando "contrapressão". É um freio, não uma guilhotina — dá à aplicação a chance de reagir (rodar GC, liberar cache) antes de bater no &lt;code&gt;max&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;memory.low&lt;/code&gt;&lt;/strong&gt; — &lt;strong&gt;proteção&lt;/strong&gt;. Memória abaixo desse valor é &lt;strong&gt;protegida&lt;/strong&gt; do reclaim enquanto houver memória recuperável em outros cgroups. Garante um mínimo para serviços críticos.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ler os limites de um cgroup&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/&amp;lt;meu-servico&amp;gt;/memory.max     &lt;span class="c"&gt;# ex.: 2147483648 (2 GB) ou "max"&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/&amp;lt;meu-servico&amp;gt;/memory.high    &lt;span class="c"&gt;# ex.: 1932735283 (~1.8 GB)&lt;/span&gt;

&lt;span class="c"&gt;# Definir (normalmente feito pelo Docker/K8s, mas você pode ajustar)&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;2G &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /sys/fs/cgroup/&amp;lt;meu-servico&amp;gt;/memory.max
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]&lt;br&gt;
A estratégia de ouro em produção: configure &lt;code&gt;memory.high&lt;/code&gt; &lt;strong&gt;abaixo&lt;/strong&gt; de &lt;code&gt;memory.max&lt;/code&gt;. O &lt;code&gt;high&lt;/code&gt; dá à sua aplicação uma zona de "amortecimento" onde ela é desacelerada e pressionada a liberar memória (o runtime faz GC), &lt;strong&gt;antes&lt;/strong&gt; de o &lt;code&gt;max&lt;/code&gt; disparar um OOMKill abrupto. Muitos setups só definem &lt;code&gt;max&lt;/code&gt; (via &lt;code&gt;limits&lt;/code&gt; do K8s) e perdem essa rede de segurança.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Memory accounting
&lt;/h3&gt;

&lt;p&gt;O cgroup contabiliza &lt;strong&gt;tudo&lt;/strong&gt;: não só a memória anônima (heap) do processo, mas também o &lt;strong&gt;page cache&lt;/strong&gt; e o &lt;strong&gt;slab&lt;/strong&gt; atribuíveis a ele. Isso é uma surpresa comum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/&amp;lt;meu-servico&amp;gt;/memory.current   &lt;span class="c"&gt;# uso total atual&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/&amp;lt;meu-servico&amp;gt;/memory.stat
&lt;span class="c"&gt;# anon           524288000    ← heap/stack (páginas anônimas)&lt;/span&gt;
&lt;span class="c"&gt;# file           838860800    ← PAGE CACHE contabilizado no cgroup!&lt;/span&gt;
&lt;span class="c"&gt;# kernel_stack     2097152&lt;/span&gt;
&lt;span class="c"&gt;# slab            10485760    ← estruturas do kernel deste cgroup&lt;/span&gt;
&lt;span class="c"&gt;# sock             1048576&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pegadinha clássica&lt;/strong&gt;: seu container "usa" memória perto do limite, mas boa parte é &lt;strong&gt;page cache&lt;/strong&gt; (arquivos que ele leu). Isso normalmente é reclaimável e não causa OOM sozinho — mas conta no &lt;code&gt;memory.current&lt;/code&gt;. Entender &lt;code&gt;memory.stat&lt;/code&gt; (separando &lt;code&gt;anon&lt;/code&gt; de &lt;code&gt;file&lt;/code&gt;) é o que distingue um falso alarme de um vazamento real.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hierarquia e eventos
&lt;/h3&gt;

&lt;p&gt;Cgroups são &lt;strong&gt;hierárquicos&lt;/strong&gt;: limites do pai restringem os filhos, e o accounting sobe na árvore. E, como vimos na Parte VII, o &lt;code&gt;memory.events&lt;/code&gt; e o &lt;code&gt;memory.pressure&lt;/code&gt; (PSI por cgroup) dão observabilidade fina:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/&amp;lt;meu-servico&amp;gt;/memory.events
&lt;span class="c"&gt;# high 152   ← bateu no throttle 152 vezes (sinal de subdimensionamento!)&lt;/span&gt;
&lt;span class="c"&gt;# max  8&lt;/span&gt;
&lt;span class="c"&gt;# oom  2&lt;/span&gt;
&lt;span class="c"&gt;# oom_kill 2&lt;/span&gt;

&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/&amp;lt;meu-servico&amp;gt;/memory.pressure   &lt;span class="c"&gt;# PSI local (Parte VII)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um &lt;code&gt;high&lt;/code&gt; alto e crescente é o alarme &lt;strong&gt;precoce&lt;/strong&gt; de que o container está apertado — muito antes do &lt;code&gt;oom_kill&lt;/code&gt;. É a métrica que você quer no dashboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conexão com Backend: Python
&lt;/h2&gt;

&lt;p&gt;Python data-intensive (Pandas, NumPy, PyArrow) vive e morre pela relação com o page cache (Parte VI) e pelos limites de cgroup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Page cache e mmap em análise de dados
&lt;/h3&gt;

&lt;p&gt;Bibliotecas como Pandas e PyArrow podem ler arquivos via &lt;code&gt;mmap&lt;/code&gt;, deixando o &lt;strong&gt;page cache&lt;/strong&gt; fazer o trabalho pesado: só as páginas realmente tocadas entram na RAM, e são compartilhadas entre processos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pyarrow.parquet&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pq&lt;/span&gt;

&lt;span class="c1"&gt;# memory_map=True → usa mmap; os dados vêm do page cache sob demanda,
# sem carregar o arquivo inteiro na heap do Python (zero-copy quando possível).
&lt;/span&gt;&lt;span class="n"&gt;tabela&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dados_grandes.parquet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory_map&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ETL sem entupir o page cache
&lt;/h3&gt;

&lt;p&gt;Em jobs ETL que varrem arquivos gigantes &lt;strong&gt;uma única vez&lt;/strong&gt;, o page cache vira um problema: ele enche a RAM com dados que não serão relidos, pressionando o working set de outros processos (o cenário de "varredura única" da Parte VII). A solução é a dica &lt;code&gt;fadvise&lt;/code&gt; da Parte VI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;processar_arquivo_grande&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caminho&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caminho&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_RDONLY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posix_fadvise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POSIX_FADV_SEQUENTIAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# read-ahead agressivo
&lt;/span&gt;    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# ... lê e processa em streaming, sem carregar tudo ...
&lt;/span&gt;        &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Descarta do page cache o que já foi processado: não polui a RAM
&lt;/span&gt;        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posix_fadvise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POSIX_FADV_DONTNEED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Evitando OOM em scripts de dados e isolando Celery
&lt;/h3&gt;

&lt;p&gt;Um script Pandas que faz &lt;code&gt;df.copy()&lt;/code&gt; descuidado dobra o uso de memória e toma OOM (Parte VII). Monitore com &lt;code&gt;tracemalloc&lt;/code&gt; (Artigo 2, Parte IV) e processe em &lt;strong&gt;chunks&lt;/strong&gt;. Para workers Celery, isole cada um em seu cgroup com &lt;code&gt;memory.high&lt;/code&gt;/&lt;code&gt;memory.max&lt;/code&gt;, evitando que um worker guloso derrube os vizinhos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# systemd unit de um worker Celery
&lt;/span&gt;&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;MemoryHigh&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1500M      # throttle: pressiona antes do limite&lt;/span&gt;
&lt;span class="py"&gt;MemoryMax&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2G          # hard limit: OOM do cgroup aqui&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conexão com Backend: .NET
&lt;/h2&gt;

&lt;h3&gt;
  
  
  FileStream, page cache e MemoryMappedFile
&lt;/h3&gt;

&lt;p&gt;Por padrão, &lt;code&gt;System.IO.FileStream&lt;/code&gt; usa I/O bufferizado — passa pelo &lt;strong&gt;page cache&lt;/strong&gt; (Parte VI). Para arquivos grandes acessados aleatoriamente, &lt;code&gt;MemoryMappedFile&lt;/code&gt; deixa o kernel gerenciar via page faults:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.IO.MemoryMappedFiles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mmf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MemoryMappedFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dados_grandes.bin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                                &lt;span class="n"&gt;FileMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;accessor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mmf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateViewAccessor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// O acesso dispara page faults que trazem só as páginas necessárias&lt;/span&gt;
&lt;span class="c1"&gt;// do page cache — sem carregar o arquivo inteiro na managed heap.&lt;/span&gt;
&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GC pressure vs kernel memory pressure
&lt;/h3&gt;

&lt;p&gt;Um ponto sutil: o GC do .NET decide coletar com base na sua &lt;strong&gt;própria&lt;/strong&gt; visão de heap, que pode não enxergar a pressão de memória do &lt;strong&gt;cgroup&lt;/strong&gt;. O resultado: o container bate em &lt;code&gt;memory.high&lt;/code&gt;/&lt;code&gt;memory.max&lt;/code&gt; (Parte VII) &lt;strong&gt;antes&lt;/strong&gt; de o GC achar que precisa coletar → throttle ou OOMKill. A ponte é fazer o CLR respeitar o cgroup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// runtimeconfig.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"configProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"System.GC.Server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"System.GC.HeapHardLimitPercent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// GC "vê" o limite do container&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assim o GC coleta de forma mais agressiva conforme se aproxima do teto, transformando um OOMKill em coleta controlada. (Retomamos aqui o tema do Server GC da Parte IV.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagnóstico e page cache
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet-counters monitor &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;PID&amp;gt; &lt;span class="nt"&gt;--counters&lt;/span&gt; System.Runtime
&lt;span class="c"&gt;#   GC Heap Size, Gen 2 GC Count, LOH Size, Working Set...&lt;/span&gt;

&lt;span class="c"&gt;# Correlacionar working set do processo com o page cache do cgroup&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/&amp;lt;container&amp;gt;/memory.stat | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'anon|file'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conexão com Backend: Go
&lt;/h2&gt;

&lt;h3&gt;
  
  
  mmap e I/O com page cache
&lt;/h3&gt;

&lt;p&gt;Go acessa &lt;code&gt;mmap&lt;/code&gt; diretamente via &lt;code&gt;syscall.Mmap&lt;/code&gt;, ideal para processar arquivos grandes (log aggregation, índices) aproveitando o page cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"syscall"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;mapearArquivo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caminho&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;caminho&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c"&gt;// Mapeia o arquivo: o kernel serve as páginas via page cache,&lt;/span&gt;
    &lt;span class="c"&gt;// sob demanda, sem cópia para a heap gerenciada pelo GC.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fd&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROT_READ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAP_SHARED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  bufio e a interação com o page cache
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;bufio.Reader&lt;/code&gt;/&lt;code&gt;bufio.Writer&lt;/code&gt; adicionam um buffer em &lt;strong&gt;user space&lt;/strong&gt; sobre o I/O que já passa pelo page cache em &lt;strong&gt;kernel space&lt;/strong&gt;. Para leituras sequenciais, isso reduz o número de syscalls (cada &lt;code&gt;read&lt;/code&gt; traz um bloco maior), enquanto o read-ahead do kernel (Parte VI) trabalha por baixo. Duas camadas de buffer cooperando.&lt;/p&gt;

&lt;h3&gt;
  
  
  GOMEMLIMIT: o freio contra o OOM do cgroup
&lt;/h3&gt;

&lt;p&gt;O ponto mais importante para Go em containers. Como o GC do Go (por padrão, guiado por &lt;code&gt;GOGC&lt;/code&gt;) não conhecia o limite do cgroup, apps Go tomavam OOMKill sob picos. O &lt;strong&gt;&lt;code&gt;GOMEMLIMIT&lt;/code&gt;&lt;/strong&gt; (Go 1.19+) resolve: define um teto suave de memória que faz o GC ficar mais agressivo ao se aproximar dele.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Alinhe o GOMEMLIMIT ABAIXO do memory.max do cgroup (deixe folga p/ não-heap)&lt;/span&gt;
&lt;span class="nv"&gt;GOMEMLIMIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1800MiB ./minha-app     &lt;span class="c"&gt;# se o container tem memory.max=2Gi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é o equivalente Go do &lt;code&gt;HeapHardLimitPercent&lt;/code&gt; do .NET: fazer o runtime "enxergar" o cgroup (Parte VII) e agir antes do OOM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Direct I/O
&lt;/h3&gt;

&lt;p&gt;Para workloads que gerenciam o próprio cache e não querem duplicação no page cache (Parte VI), Go pode abrir arquivos com &lt;code&gt;O_DIRECT&lt;/code&gt; — casos específicos (bancos, storage engines), com o custo de alinhamento de buffers e perda de read-ahead automático.&lt;/p&gt;




&lt;h2&gt;
  
  
  Técnicas de Diagnóstico e Tuning
&lt;/h2&gt;

&lt;p&gt;O arsenal completo do Artigo 3, conectando todas as partes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Visão geral (lembre: 'available', não 'free' — Parte VI)&lt;/span&gt;
free &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'MemAvailable|Cached|Dirty|Writeback|SReclaimable'&lt;/span&gt; /proc/meminfo

&lt;span class="c"&gt;# 2. Pressão real de memória (PSI — Parte VII). A métrica que importa.&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/pressure/memory
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/&amp;lt;servico&amp;gt;/memory.pressure

&lt;span class="c"&gt;# 3. Slab: memória do kernel (Parte VI)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;slabtop &lt;span class="nt"&gt;-o&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'SReclaimable|SUnreclaim'&lt;/span&gt; /proc/meminfo

&lt;span class="c"&gt;# 4. cgroup: o que realmente está acontecendo no container (Parte VIII)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/&amp;lt;servico&amp;gt;/memory.current
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/&amp;lt;servico&amp;gt;/memory.stat      &lt;span class="c"&gt;# separe anon (heap) de file (cache)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/&amp;lt;servico&amp;gt;/memory.events    &lt;span class="c"&gt;# high/oom_kill&lt;/span&gt;

&lt;span class="c"&gt;# 5. Swap e reclaim (Partes III e VII)&lt;/span&gt;
vmstat 1                                       &lt;span class="c"&gt;# si/so + coluna 'wa'&lt;/span&gt;
sar &lt;span class="nt"&gt;-B&lt;/span&gt; 1                                       &lt;span class="c"&gt;# pgscank/pgscand (kswapd vs direct)&lt;/span&gt;

&lt;span class="c"&gt;# 6. Watermarks e zonas (Parte VII)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/zoneinfo | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'zone|min|low|high|free'&lt;/span&gt;

&lt;span class="c"&gt;# 7. Quem foi morto pelo OOM (Parte VII)&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-k&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'out of memory'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tuning que vale conhecer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vm.swappiness            &lt;span class="c"&gt;# anon-swap vs cache-drop (Parte VII)&lt;/span&gt;
vm.min_free_kbytes       &lt;span class="c"&gt;# folga da watermark min (Parte VII)&lt;/span&gt;
vm.dirty_ratio           &lt;span class="c"&gt;# teto de dirty pages antes de bloquear (Parte VI)&lt;/span&gt;
vm.dirty_background_ratio# quando as flusher threads acordam &lt;span class="o"&gt;(&lt;/span&gt;Parte VI&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Boas Práticas
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure &lt;code&gt;memory.high&lt;/code&gt; abaixo de &lt;code&gt;memory.max&lt;/code&gt;.&lt;/strong&gt; Dê à aplicação uma zona de amortecimento com throttle antes do OOMKill. Só definir &lt;code&gt;limits&lt;/code&gt; no K8s não basta.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Faça o runtime enxergar o cgroup.&lt;/strong&gt; &lt;code&gt;GOMEMLIMIT&lt;/code&gt; (Go), &lt;code&gt;HeapHardLimitPercent&lt;/code&gt; (.NET), &lt;code&gt;max_requests&lt;/code&gt;/monitoramento (Python). O runtime deve fazer GC/reciclagem &lt;strong&gt;antes&lt;/strong&gt; do &lt;code&gt;memory.max&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitore por PSI, não por &lt;code&gt;free&lt;/code&gt;.&lt;/strong&gt; Alerte em &lt;code&gt;memory.pressure&lt;/code&gt; (&lt;code&gt;full&lt;/code&gt; avg60) e em &lt;code&gt;memory.events:high&lt;/code&gt;, que sobem &lt;strong&gt;antes&lt;/strong&gt; do OOM. Considere &lt;code&gt;systemd-oomd&lt;/code&gt; para ação graciosa.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Entenda seu accounting.&lt;/strong&gt; Separe &lt;code&gt;anon&lt;/code&gt; de &lt;code&gt;file&lt;/code&gt; no &lt;code&gt;memory.stat&lt;/code&gt;. Cache alto raramente é problema; &lt;code&gt;anon&lt;/code&gt; crescente sem parar é vazamento.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use dicas de page cache em jobs de varredura única.&lt;/strong&gt; &lt;code&gt;POSIX_FADV_DONTNEED&lt;/code&gt;/&lt;code&gt;SEQUENTIAL&lt;/code&gt; em ETL para não expulsar o working set alheio.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Direct I/O só quando fizer sentido.&lt;/strong&gt; Se a aplicação já tem cache próprio (bancos), &lt;code&gt;O_DIRECT&lt;/code&gt; evita dupla cache; caso contrário, o page cache é seu amigo.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Estudos de Caso
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Python — Pipeline de ETL com controle de page cache
&lt;/h3&gt;

&lt;p&gt;Um job noturno de ETL em Pandas processava dezenas de arquivos Parquet de vários GB. A cada execução, o page cache enchia com dados de uso único, pressionando os serviços co-residentes (que começavam a sofrer direct reclaim — Parte VII). A solução combinou leitura em chunks, &lt;code&gt;memory_map=True&lt;/code&gt; no PyArrow e &lt;code&gt;POSIX_FADV_DONTNEED&lt;/code&gt; após cada arquivo. Resultado: o working set dos serviços vizinhos deixou de ser expulso, e o PSI &lt;code&gt;full&lt;/code&gt; do host caiu drasticamente.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. .NET — Microserviço com memory.high para prevenir OOM
&lt;/h3&gt;

&lt;p&gt;Uma API ASP.NET Core em Kubernetes sofria &lt;code&gt;OOMKilled&lt;/code&gt; intermitente sob picos. Só havia &lt;code&gt;limits.memory&lt;/code&gt; (= &lt;code&gt;memory.max&lt;/code&gt;) definido; sem &lt;code&gt;memory.high&lt;/code&gt;, o container ia de "confortável" a "morto" sem transição. A correção: adicionar &lt;code&gt;memory.high&lt;/code&gt; a ~85% do &lt;code&gt;max&lt;/code&gt;, configurar &lt;code&gt;HeapHardLimitPercent=75&lt;/code&gt; e alertar em &lt;code&gt;memory.events:high&lt;/code&gt;. O Server GC passou a coletar sob pressão, o throttle absorveu os picos, e os OOMKills cessaram.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Go — Stream processor com mmap e GOMEMLIMIT
&lt;/h3&gt;

&lt;p&gt;Um agregador de logs em Go mapeava arquivos grandes via &lt;code&gt;syscall.Mmap&lt;/code&gt; e sofria OOMKill quando o volume subia — o GC, guiado só por &lt;code&gt;GOGC&lt;/code&gt;, não via o limite do container. Definir &lt;code&gt;GOMEMLIMIT&lt;/code&gt; logo abaixo do &lt;code&gt;memory.max&lt;/code&gt; fez o GC intensificar perto do teto; combinado ao &lt;code&gt;mmap&lt;/code&gt; (que mantém os dados no page cache, fora da heap gerenciada), o RSS anônimo estabilizou e o OOM desapareceu.&lt;/p&gt;




&lt;h2&gt;
  
  
  Encerramento da Série de Memória
&lt;/h2&gt;

&lt;p&gt;Percorremos um caminho longo. No &lt;strong&gt;Artigo Sobre Gerenciamento de Memória&lt;/strong&gt;, construímos a fundação: a hierarquia de memória, a abstração do espaço de endereçamento, paginação, tabelas multinível, TLB, page faults, Copy-on-Write, o memory layout de um processo, NUMA — e como CPython, CLR e o runtime Go se apoiam em tudo isso.&lt;/p&gt;

&lt;p&gt;O próximo artigo da série muda de subsistema: &lt;strong&gt;I/O e Armazenamento&lt;/strong&gt; — block layer, I/O schedulers, filesystems e o revolucionário &lt;code&gt;io_uring&lt;/code&gt;. Muitos dos conceitos daqui (page cache, dirty pages, &lt;code&gt;fsync&lt;/code&gt;, Direct I/O) reaparecerão lá, agora do ponto de vista do disco. Até lá! tenham paciencia que esse vai demorar um pouco mais pra sair, mas vai ser tão profundo quanto este.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fevpq2tvd5jjz3uatr9aq.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fevpq2tvd5jjz3uatr9aq.jpeg" alt="Parabens" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conteúdo parcialmente gerado com auxílio de IA generativa (me ajudou a organizar tudo isso kkkk)&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referências Bibliográficas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Desenvolvimento-do-Kernel-Linux/dp/8573933410" rel="noopener noreferrer"&gt;Desenvolvimento Do Kernel Do Linux - David Cram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/admin-guide/cgroup-v2.html" rel="noopener noreferrer"&gt;Control Group v2 - kernel.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.dev/doc/gc-guide" rel="noopener noreferrer"&gt;A Guide to the Go Garbage Collector (GOMEMLIMIT)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/dotnet/core/runtime-config/garbage-collector" rel="noopener noreferrer"&gt;.NET GC e Containers - Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/gorman/html/understand/" rel="noopener noreferrer"&gt;Understanding the Linux Virtual Memory Manager - Mel Gorman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Sistemas-Operacionais-Modernos-Andrew-Tanenbaum/dp/8582606168" rel="noopener noreferrer"&gt;Sistemas Operacionais Modernos - Tanenbaum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>dotnet</category>
      <category>go</category>
      <category>python</category>
    </item>
    <item>
      <title>Kernel Linux para Desenvolvedores Backend - Gerenciamento de Memória Parte VII</title>
      <dc:creator>Alex Volnei Galante</dc:creator>
      <pubDate>Fri, 21 Aug 2026 00:55:52 +0000</pubDate>
      <link>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-vii-253f</link>
      <guid>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-vii-253f</guid>
      <description>&lt;p&gt;Se voce chegou ate aqui meus parabens, gerenciamento de memoria e kernel linux nao sao assuntos triviais. Nesta parte vamos finalmente falar de &lt;strong&gt;reclaim&lt;/strong&gt; e &lt;strong&gt;OOM&lt;/strong&gt; — como o kernel decide quando e como recuperar memoria, e o que acontece quando ele nao consegue.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sumário
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Watermarks: os níveis de alerta&lt;/li&gt;
&lt;li&gt;kswapd: o reclaim em background&lt;/li&gt;
&lt;li&gt;Direct reclaim: quando não dá para esperar&lt;/li&gt;
&lt;li&gt;As LRU lists do Linux&lt;/li&gt;
&lt;li&gt;vm.swappiness&lt;/li&gt;
&lt;li&gt;PSI: Pressure Stall Information&lt;/li&gt;
&lt;li&gt;
O OOM Killer

&lt;ul&gt;
&lt;li&gt;Como a vítima é escolhida&lt;/li&gt;
&lt;li&gt;Ajustando o oom_score_adj&lt;/li&gt;
&lt;li&gt;OOM em cgroups&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Referências Bibliográficas&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Watermarks: os níveis de alerta
&lt;/h2&gt;

&lt;p&gt;Na Parte IV mencionamos que cada zona de memória tem &lt;strong&gt;watermarks&lt;/strong&gt; (marcas d'água). Agora elas entram em ação. O kernel monitora a memória livre de cada zona contra três limiares:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Memória livre na zona
    alta │──────────────────────  WATERMARK_HIGH  ← "tudo ok, pare de recuperar"
         │
         │──────────────────────  WATERMARK_LOW   ← "acorde o kswapd!"
         │
   baixa │──────────────────────  WATERMARK_MIN   ← "emergência: direct reclaim"
         │
       0 └──────────────────────  abaixo do min → risco de OOM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Acima de &lt;strong&gt;high&lt;/strong&gt;: memória confortável, nada a fazer.&lt;/li&gt;
&lt;li&gt;Cruzou &lt;strong&gt;low&lt;/strong&gt; (descendo): o kernel &lt;strong&gt;acorda o kswapd&lt;/strong&gt; para recuperar memória em background.&lt;/li&gt;
&lt;li&gt;Cruzou &lt;strong&gt;min&lt;/strong&gt;: situação crítica — a alocação força &lt;strong&gt;direct reclaim&lt;/strong&gt; (a própria thread que pede memória precisa recuperá-la antes de prosseguir).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ver as watermarks de cada zona (em páginas)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/zoneinfo | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'Node|zone|min|low|high|free'&lt;/span&gt;

&lt;span class="c"&gt;# vm.min_free_kbytes controla o WATERMARK_MIN; aumentá-lo dá mais "folga"&lt;/span&gt;
&lt;span class="c"&gt;# ao kernel (útil em máquinas com rajadas de alocação), ao custo de RAM reservada.&lt;/span&gt;
sysctl vm.min_free_kbytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  kswapd: o reclaim em background
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;kswapd&lt;/strong&gt; é uma thread de kernel (uma por nó NUMA) cuja função é recuperar memória &lt;strong&gt;proativamente&lt;/strong&gt;, antes que falte. Quando a memória livre cai abaixo da watermark &lt;strong&gt;low&lt;/strong&gt;, o kswapd acorda e começa a liberar páginas até voltar acima da watermark &lt;strong&gt;high&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O que ele recupera, em ordem de preferência:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Páginas de page cache limpas&lt;/strong&gt; (file-backed, não modificadas) — as mais baratas: basta descartar (Parte VI).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Páginas de page cache sujas&lt;/strong&gt; — precisam de write-back antes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Páginas anônimas&lt;/strong&gt; — precisam ir para o &lt;strong&gt;swap&lt;/strong&gt; (se houver).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slab reclaimable&lt;/strong&gt; — via shrinkers (Parte VI).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Como o kswapd trabalha em background, idealmente ele mantém a memória saudável &lt;strong&gt;sem&lt;/strong&gt; que as aplicações percebam. Um kswapd trabalhando muito (visível como consumo de CPU dele em &lt;code&gt;top&lt;/code&gt;) é sinal de pressão de memória sustentada.&lt;/p&gt;




&lt;h2&gt;
  
  
  Direct reclaim: quando não dá para esperar
&lt;/h2&gt;

&lt;p&gt;Se a alocação de memória é tão rápida que o kswapd não consegue acompanhar, e a memória livre bate na watermark &lt;strong&gt;min&lt;/strong&gt;, o kernel recorre ao &lt;strong&gt;direct reclaim&lt;/strong&gt;: a &lt;strong&gt;própria thread da aplicação&lt;/strong&gt; que pediu memória é forçada a parar e recuperar páginas ela mesma, ali, sincronamente, antes de a alocação retornar.&lt;/p&gt;

&lt;p&gt;Isso é &lt;strong&gt;péssimo para latência&lt;/strong&gt;. Do ponto de vista da sua aplicação, uma alocação trivial (&lt;code&gt;malloc&lt;/code&gt;, criar um objeto) de repente &lt;strong&gt;trava&lt;/strong&gt; por milissegundos ou mais, enquanto a thread faz o trabalho sujo de liberar memória — possivelmente esperando I/O de swap ou write-back.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Alocação normal:      app pede memória → kernel entrega → segue (µs)
Direct reclaim:       app pede memória → SEM frames livres →
                      app é sequestrada para liberar páginas →
                      espera write-back/swap → só então recebe (ms+)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Picos de latência de cauda (p99) inexplicáveis em produção são, com frequência, direct reclaim acontecendo. A métrica PSI (adiante) foi criada justamente para tornar isso visível.&lt;/p&gt;




&lt;h2&gt;
  
  
  As LRU lists do Linux
&lt;/h2&gt;

&lt;p&gt;Como o kernel decide &lt;strong&gt;quais&lt;/strong&gt; páginas recuperar? Aqui a teoria da Parte V vira prática. O Linux mantém, por nó/zona (e por cgroup), um conjunto de &lt;strong&gt;listas LRU&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 ┌─────────────────┐
   file-backed:  │  active_file    │  páginas de arquivo "quentes"
                 ├─────────────────┤
                 │  inactive_file  │  candidatas a despejo (frias)
                 └─────────────────┘
                 ┌─────────────────┐
   anônimas:     │  active_anon    │  heap/stack "quentes"
                 ├─────────────────┤
                 │  inactive_anon  │  candidatas a swap
                 └─────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Páginas nascem na lista &lt;strong&gt;inactive&lt;/strong&gt;. Se forem referenciadas de novo (o bit Accessed, Parte II), são &lt;strong&gt;promovidas&lt;/strong&gt; para &lt;strong&gt;active&lt;/strong&gt;. Sob pressão, o kernel recupera da ponta da lista &lt;strong&gt;inactive&lt;/strong&gt; — as páginas frias — mantendo as active. É o algoritmo do relógio / LRU aproximado da Parte V, refinado (e agora, com &lt;strong&gt;MGLRU&lt;/strong&gt;, ainda mais preciso — Parte VI).&lt;/p&gt;

&lt;p&gt;A separação &lt;strong&gt;file vs anon&lt;/strong&gt; é deliberada: recuperar uma página de arquivo limpa é barato (só descartar); recuperar uma anônima exige swap. O &lt;code&gt;vm.swappiness&lt;/code&gt; controla o equilíbrio entre as duas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Refault distance e proteção do working set
&lt;/h3&gt;

&lt;p&gt;Um problema clássico do LRU simples: uma varredura única de muitos arquivos (um &lt;code&gt;grep&lt;/code&gt; gigante, um backup) pode &lt;strong&gt;inundar&lt;/strong&gt; o inactive_file e expulsar o working set real do cache. O Linux moderno rastreia a &lt;strong&gt;refault distance&lt;/strong&gt; — se uma página descartada é logo pedida de volta (refault), o kernel aprende que ela era importante e &lt;strong&gt;protege&lt;/strong&gt; o working set de ser expulso por acessos transitórios. É a formalização da Parte V ("proteger o working set") implementada de forma adaptativa.&lt;/p&gt;




&lt;h2&gt;
  
  
  vm.swappiness
&lt;/h2&gt;

&lt;p&gt;O parâmetro mais famoso (e mais mal-entendido) do tuning de memória. O &lt;code&gt;vm.swappiness&lt;/code&gt; (0 a 200; padrão geralmente 60) define o &lt;strong&gt;quanto&lt;/strong&gt; o kernel prefere recuperar memória fazendo &lt;strong&gt;swap de páginas anônimas&lt;/strong&gt; versus &lt;strong&gt;descartar page cache&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;swappiness baixo&lt;/strong&gt; (ex.: 10): "evite swap, prefira descartar cache". Bom quando o page cache é descartável e você quer manter a heap na RAM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;swappiness alto&lt;/strong&gt; (ex.: 100): "não hesite em fazer swap de páginas anônimas frias". Pode liberar RAM para mais cache.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sysctl vm.swappiness
&lt;span class="c"&gt;# Ajuste temporário:&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;sysctl &lt;span class="nt"&gt;-w&lt;/span&gt; vm.swappiness&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
Não existe valor "correto" universal. Em bancos de dados que gerenciam o próprio cache, swappiness baixo costuma ajudar. Em servidores de arquivos, cache é rei. Em containers com &lt;code&gt;memory.max&lt;/code&gt;, o comportamento muda (Parte VIII). E, com SSD/NVMe (Parte III), o swap não é mais o vilão que era nos tempos de HDD — swappiness moderado pode ser saudável.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  PSI: Pressure Stall Information
&lt;/h2&gt;

&lt;p&gt;Antes do PSI, medir pressão de memória era arte adivinhatória: &lt;code&gt;free&lt;/code&gt; engana (Parte VI), e alta atividade de swap pode ser normal ou catastrófica. O &lt;strong&gt;PSI (Pressure Stall Information)&lt;/strong&gt;, introduzido no &lt;strong&gt;Kernel 4.20&lt;/strong&gt;, mudou o jogo.&lt;/p&gt;

&lt;p&gt;O PSI mede &lt;strong&gt;quanto tempo&lt;/strong&gt; as tarefas ficaram &lt;strong&gt;paradas (stalled)&lt;/strong&gt; esperando por um recurso — exatamente o que dói. Para memória:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/pressure/memory
&lt;span class="c"&gt;# some avg10=0.00 avg60=0.15 avg300=0.05 total=12345678&lt;/span&gt;
&lt;span class="c"&gt;# full avg10=0.00 avg60=0.08 avg300=0.02 total=6789012&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;some&lt;/strong&gt;: % do tempo em que &lt;strong&gt;pelo menos uma&lt;/strong&gt; tarefa ficou parada esperando memória (reclaim, swap, refault).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;full&lt;/strong&gt;: % do tempo em que &lt;strong&gt;todas&lt;/strong&gt; as tarefas não-ociosas ficaram paradas simultaneamente — o sistema inteiro travado por memória. &lt;code&gt;full&lt;/code&gt; subindo é o sinal mais claro e precoce de thrashing (Parte V).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Diferente de olhar swap in/out cru, o PSI mede o &lt;strong&gt;impacto real&lt;/strong&gt; na execução. É a métrica moderna para alertas: em vez de "swap &amp;gt; X", monitore "memory pressure &lt;code&gt;full&lt;/code&gt; avg60 &amp;gt; Y%". Ferramentas como o &lt;strong&gt;systemd-oomd&lt;/strong&gt; e o &lt;strong&gt;oomd&lt;/strong&gt; do Facebook usam PSI para agir &lt;strong&gt;antes&lt;/strong&gt; do OOM Killer clássico — matando algo de forma controlada quando a pressão passa de um limiar, evitando o congelamento total.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Pressão de memória por cgroup (essencial em containers — Parte VIII)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/&amp;lt;meu-servico&amp;gt;/memory.pressure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  O famoso OOMKiller
&lt;/h2&gt;

&lt;p&gt;Chegamos ao mais temido. Quando &lt;strong&gt;todo&lt;/strong&gt; o reclaim falha — não há mais page cache para descartar, nem swap para páginas anônimas, e a memória continua insuficiente — o kernel toma a decisão nuclear: invocar o &lt;strong&gt;OOM Killer (Out-Of-Memory Killer)&lt;/strong&gt; para matar um processo e liberar memória, evitando o congelamento total do sistema.&lt;/p&gt;

&lt;p&gt;Lembra do overcommit (Parte III)? O kernel &lt;strong&gt;prometeu mais memória do que tem&lt;/strong&gt;, apostando (via zero-fill on demand e COW) que nem todos escreveriam tudo. O OOM Killer é o que acontece quando essa aposta dá errado e todos cobram ao mesmo tempo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Como a vítima é escolhida
&lt;/h3&gt;

&lt;p&gt;O OOM Killer não escolhe ao acaso — ele tenta matar o processo que &lt;strong&gt;libera mais memória causando o menor dano&lt;/strong&gt;. Cada processo tem um &lt;strong&gt;oom_score&lt;/strong&gt;, derivado principalmente do seu consumo de memória (RSS + swap + page tables), ajustado por &lt;code&gt;oom_score_adj&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Score atual de um processo (0 a 1000; maior = mais provável de ser morto)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&amp;lt;PID&amp;gt;/oom_score

&lt;span class="c"&gt;# O ajuste que você controla (-1000 a +1000)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&amp;lt;PID&amp;gt;/oom_score_adj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grosso modo: &lt;strong&gt;quanto mais memória um processo usa, maior seu score, mais provável ser a vítima&lt;/strong&gt;. Isso costuma acertar (o maior consumidor geralmente é o culpado), mas nem sempre — às vezes mata justamente seu processo principal, o maior da máquina.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ajustando o oom_score_adj
&lt;/h3&gt;

&lt;p&gt;Você pode influenciar a decisão. O &lt;code&gt;oom_score_adj&lt;/code&gt; vai de &lt;strong&gt;-1000&lt;/strong&gt; (nunca mate, imune) a &lt;strong&gt;+1000&lt;/strong&gt; (mate primeiro):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Proteger um processo crítico (ex.: o banco de dados) de ser morto&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-900&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /proc/&amp;lt;PID_do_banco&amp;gt;/oom_score_adj

&lt;span class="c"&gt;# Marcar um processo "sacrificável" (ex.: um cache reconstruível) para morrer primeiro&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;800 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /proc/&amp;lt;PID_do_cache&amp;gt;/oom_score_adj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é útil para proteger o processo essencial e direcionar o OOM Killer a matar algo reconstruível. Quando o OOM dispara, o kernel registra tudo no log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dmesg | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'killed process'&lt;/span&gt;
&lt;span class="c"&gt;# Out of memory: Killed process 12345 (python) total-vm:8000000kB,&lt;/span&gt;
&lt;span class="c"&gt;#   anon-rss:7500000kB, file-rss:0kB, ... score 850&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-k&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; oom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  OOM em cgroups
&lt;/h3&gt;

&lt;p&gt;Em containers, o OOM mais comum &lt;strong&gt;não&lt;/strong&gt; é o global — é o &lt;strong&gt;OOM do cgroup&lt;/strong&gt;, disparado quando o grupo estoura seu &lt;code&gt;memory.max&lt;/code&gt; (Parte VIII). O kernel mata um processo &lt;strong&gt;dentro daquele cgroup&lt;/strong&gt;, sem afetar o resto do host. É o clássico "meu pod reiniciou com &lt;code&gt;OOMKilled&lt;/code&gt; no Kubernetes":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Contadores de eventos de OOM do cgroup&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/&amp;lt;meu-pod&amp;gt;/memory.events
&lt;span class="c"&gt;# low 0&lt;/span&gt;
&lt;span class="c"&gt;# high 152      ← quantas vezes bateu memory.high (throttling)&lt;/span&gt;
&lt;span class="c"&gt;# max 8         ← quantas vezes bateu memory.max&lt;/span&gt;
&lt;span class="c"&gt;# oom 2         ← quantas vezes o OOM do cgroup disparou&lt;/span&gt;
&lt;span class="c"&gt;# oom_kill 2    ← quantos processos foram mortos&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kernels 5.x melhoraram o OOM Killer para &lt;strong&gt;evitar thrashing prolongado&lt;/strong&gt;: em vez de deixar o sistema agonizar minutos perto do limite (tudo em reclaim, nada progredindo), ele age mais decisivamente. E, com &lt;strong&gt;PSI + systemd-oomd/oomd&lt;/strong&gt;, é possível agir em user space &lt;strong&gt;antes&lt;/strong&gt; do OOM do kernel, matando de forma mais graciosa e previsível.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
No Kubernetes, defina &lt;strong&gt;requests e limits&lt;/strong&gt; de memória conscientemente e configure &lt;code&gt;GOMEMLIMIT&lt;/code&gt; (Go), &lt;code&gt;HeapHardLimitPercent&lt;/code&gt; (.NET) ou reciclagem de workers (Python) &lt;strong&gt;abaixo&lt;/strong&gt; do &lt;code&gt;memory.max&lt;/code&gt;. Assim seu runtime faz GC/limpeza antes de o cgroup estourar, transformando um OOMKill abrupto numa degradação controlada. Detalhamos isso na Parte VIII.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;a href="https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-viii-2e35"&gt;&lt;strong&gt;Continua na Parte VIII&lt;/strong&gt;&lt;/a&gt; — a última do Artigo 3 —, onde amarramos toda a teoria de controle de memória com os &lt;strong&gt;cgroups v2&lt;/strong&gt; (&lt;code&gt;memory.max/high/low&lt;/code&gt;, accounting, &lt;code&gt;memory.pressure&lt;/code&gt;) e mergulhamos na &lt;strong&gt;conexão com Python, .NET e Go&lt;/strong&gt;: page cache em ETL, &lt;code&gt;MemoryMappedFile&lt;/code&gt;, &lt;code&gt;mmap&lt;/code&gt; em Go, tuning de containers, diagnóstico e estudos de caso reais.&lt;br&gt;
&lt;strong&gt;:D&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conteúdo parcialmente gerado com auxílio de IA generativa (me ajudou a organizar tudo isso kkkk)&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referências Bibliográficas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Desenvolvimento-do-Kernel-Linux/dp/8573933410" rel="noopener noreferrer"&gt;Desenvolvimento Do Kernel Do Linux - David Cram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/accounting/psi.html" rel="noopener noreferrer"&gt;PSI - Pressure Stall Information (kernel.org)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lwn.net/Articles/317814/" rel="noopener noreferrer"&gt;Taming the OOM killer - LWN.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/gorman/html/understand/" rel="noopener noreferrer"&gt;Understanding the Linux Virtual Memory Manager - Mel Gorman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Sistemas-Operacionais-Modernos-Andrew-Tanenbaum/dp/8582606168" rel="noopener noreferrer"&gt;Sistemas Operacionais Modernos - Tanenbaum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>dotnet</category>
      <category>go</category>
      <category>python</category>
    </item>
    <item>
      <title>Kernel Linux para Desenvolvedores Backend - Gerenciamento de Memória Parte VI</title>
      <dc:creator>Alex Volnei Galante</dc:creator>
      <pubDate>Fri, 21 Aug 2026 00:54:32 +0000</pubDate>
      <link>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-vi-5h0m</link>
      <guid>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-vi-5h0m</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Uffaaa cansativo não? bem eu quiz trazer com bastante profundidade o tema pra dar a devida imersão, só os fortes sobrevivem kkkkk.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Sumário
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Princípios de cache e buffering&lt;/li&gt;
&lt;li&gt;
O Page Cache

&lt;ul&gt;
&lt;li&gt;"Free memory is wasted memory"&lt;/li&gt;
&lt;li&gt;Read-ahead: leitura especulativa&lt;/li&gt;
&lt;li&gt;Write-back: escritas atrasadas e dirty pages&lt;/li&gt;
&lt;li&gt;Multi-Generational LRU (MGLRU)&lt;/li&gt;
&lt;li&gt;Buffered I/O vs Direct I/O&lt;/li&gt;
&lt;li&gt;madvise e fadvise: dando dicas ao kernel&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
O Slab Allocator

&lt;ul&gt;
&lt;li&gt;O buddy system: a base&lt;/li&gt;
&lt;li&gt;Por que slab? O problema dos objetos pequenos&lt;/li&gt;
&lt;li&gt;SLUB vs SLAB vs SLOB&lt;/li&gt;
&lt;li&gt;Shrinkers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Referências Bibliográficas&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Princípios de cache e buffering
&lt;/h2&gt;

&lt;p&gt;Antes do page cache do Linux, vale relembrar os princípios de cache que Tanenbaum apresenta — porque o page cache é uma aplicação direta deles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache hit vs miss&lt;/strong&gt;: o dado buscado está no cache (hit, rápido) ou precisa vir da fonte lenta (miss).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write-through&lt;/strong&gt;: toda escrita vai imediatamente para a camada lenta (disco). Seguro, mas lento.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write-back&lt;/strong&gt;: escritas ficam no cache e são gravadas no disco &lt;strong&gt;mais tarde&lt;/strong&gt;, em lote. Rápido, mas há uma janela de risco (dados na RAM ainda não persistidos).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-ahead (prefetching)&lt;/strong&gt;: trazer dados &lt;strong&gt;antes&lt;/strong&gt; de serem pedidos, apostando na localidade espacial.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Historicamente, o Unix tinha dois caches separados: o &lt;strong&gt;buffer cache&lt;/strong&gt; (para blocos do disco) e o &lt;strong&gt;page cache&lt;/strong&gt; (para páginas de arquivos mapeados). Isso causava duplicação e incoerência. Desde o &lt;strong&gt;Kernel 2.4&lt;/strong&gt;, o Linux os &lt;strong&gt;unificou&lt;/strong&gt;: hoje há um único &lt;strong&gt;page cache&lt;/strong&gt; que serve tanto o I/O de arquivos quanto o mapeamento de memória.&lt;/p&gt;




&lt;h2&gt;
  
  
  O Page Cache
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;page cache&lt;/strong&gt; é a estrutura que guarda, na RAM, páginas de arquivos que foram lidos ou escritos. É, provavelmente, o mecanismo com &lt;strong&gt;maior impacto na performance de I/O&lt;/strong&gt; de qualquer aplicação backend — e o mais mal compreendido.&lt;/p&gt;

&lt;p&gt;Quando sua aplicação lê um arquivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1ª leitura de arquivo.dat:
  read() → page cache MISS → busca no disco (lento, major cost) →
           copia para o page cache → entrega à aplicação

2ª leitura do MESMO arquivo (por qualquer processo!):
  read() → page cache HIT → entrega direto da RAM (nanossegundos)
           SEM tocar o disco
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O page cache é &lt;strong&gt;compartilhado por todo o sistema&lt;/strong&gt;. Se o processo A lê um arquivo e o processo B lê o mesmo arquivo depois, B aproveita o cache aquecido por A. É por isso que a segunda execução de um teste, um build, ou uma query que lê os mesmos arquivos é sempre mais rápida.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Free memory is wasted memory"
&lt;/h3&gt;

&lt;p&gt;Aqui está a fonte de confusão número um de quem administra servidores Linux. Você roda &lt;code&gt;free -h&lt;/code&gt; e vê:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              total   usado   livre   compart.  buff/cache  disponível
Mem:          15Gi    3Gi     500Mi   200Mi     11Gi        11Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Só 500 MB livres?! Vou tomar OOM!" — &lt;strong&gt;não&lt;/strong&gt;. Aqueles 11 GB em &lt;code&gt;buff/cache&lt;/code&gt; são &lt;strong&gt;page cache&lt;/strong&gt;: cópias de arquivos que o kernel manteve na RAM porque a memória estava ociosa. Essa memória é &lt;strong&gt;disponível&lt;/strong&gt; (coluna &lt;code&gt;disponível&lt;/code&gt;/&lt;code&gt;available&lt;/code&gt;): no instante em que a aplicação precisar, o kernel descarta o cache limpo e entrega os frames. É por isso que a coluna que importa é a &lt;strong&gt;&lt;code&gt;available&lt;/code&gt;&lt;/strong&gt;, não a &lt;code&gt;free&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A filosofia do Linux é: &lt;strong&gt;memória livre é memória desperdiçada&lt;/strong&gt;. RAM parada não ajuda ninguém; melhor usá-la como cache e liberá-la sob demanda. Um servidor "saudável" com uptime longo naturalmente tem quase toda a RAM em page cache.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# A coluna que realmente importa é 'available'&lt;/span&gt;
free &lt;span class="nt"&gt;-h&lt;/span&gt;

&lt;span class="c"&gt;# Detalhe do page cache no /proc/meminfo&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'Cached|Buffers|Dirty|Writeback'&lt;/span&gt; /proc/meminfo
&lt;span class="c"&gt;# Cached:        11534336 kB   ← page cache&lt;/span&gt;
&lt;span class="c"&gt;# Dirty:            45000 kB   ← modificado, ainda não gravado no disco&lt;/span&gt;
&lt;span class="c"&gt;# Writeback:            0 kB   ← sendo gravado agora&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Read-ahead: leitura especulativa
&lt;/h3&gt;

&lt;p&gt;Quando o kernel detecta um padrão de &lt;strong&gt;leitura sequencial&lt;/strong&gt;, ele antecipa: em vez de trazer só a página pedida, traz também as &lt;strong&gt;próximas&lt;/strong&gt; para o page cache, apostando que serão lidas em breve (localidade espacial em ação). Quando o pedido chega, a página já está lá.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ver/ajustar o tamanho do read-ahead de um dispositivo (em KB)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/block/nvme0n1/queue/read_ahead_kb   &lt;span class="c"&gt;# ex.: 128&lt;/span&gt;

&lt;span class="c"&gt;# Leituras sequenciais grandes (ETL, backup, scan de tabela) se beneficiam&lt;/span&gt;
&lt;span class="c"&gt;# de read-ahead maior; acesso aleatório (banco OLTP) prefere valores menores.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Write-back: escritas atrasadas e dirty pages
&lt;/h3&gt;

&lt;p&gt;Quando você escreve num arquivo, o dado &lt;strong&gt;não vai imediatamente ao disco&lt;/strong&gt;. Ele é marcado como &lt;strong&gt;dirty&lt;/strong&gt; (página suja) no page cache, e um conjunto de threads do kernel (historicamente &lt;code&gt;pdflush&lt;/code&gt;, hoje as &lt;strong&gt;flusher threads&lt;/strong&gt; por dispositivo) grava as páginas sujas no disco &lt;strong&gt;assincronamente&lt;/strong&gt;, mais tarde. Isso é write-back, e é o que torna &lt;code&gt;write()&lt;/code&gt; tão rápido.&lt;/p&gt;

&lt;p&gt;O trade-off é durabilidade: dados sujos vivem só na RAM até o flush. Um crash ou queda de energia nessa janela perde os dados. É por isso que bancos de dados chamam &lt;strong&gt;&lt;code&gt;fsync()&lt;/code&gt;&lt;/strong&gt; — para forçar a gravação imediata e garantir persistência (voltaremos a fsync no Artigo 4).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Parâmetros que controlam o write-back&lt;/span&gt;
sysctl vm.dirty_ratio            &lt;span class="c"&gt;# % de RAM suja que BLOQUEIA a aplicação escrevendo&lt;/span&gt;
sysctl vm.dirty_background_ratio &lt;span class="c"&gt;# % de RAM suja que ACORDA as flusher threads&lt;/span&gt;
sysctl vm.dirty_expire_centisecs &lt;span class="c"&gt;# idade máxima de uma página suja antes do flush&lt;/span&gt;

&lt;span class="c"&gt;# Forçar flush de tudo agora&lt;/span&gt;
&lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;br&gt;
Em servidores com muita RAM, &lt;code&gt;dirty_ratio&lt;/code&gt; alto pode acumular gigabytes de dados sujos. Quando o flush finalmente dispara, ele satura o disco e causa picos de latência ("write storms"). Em workloads write-heavy, muitas vezes reduz-se &lt;code&gt;dirty_ratio&lt;/code&gt;/&lt;code&gt;dirty_background_ratio&lt;/code&gt; para suavizar a gravação.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Multi-Generational LRU (MGLRU)
&lt;/h3&gt;

&lt;p&gt;Por décadas, o Linux usou um esquema de duas listas LRU (active/inactive) para decidir quais páginas do cache manter — uma aproximação do LRU que estudamos na Parte V. A partir do &lt;strong&gt;Kernel 6.1&lt;/strong&gt;, chegou o &lt;strong&gt;MGLRU (Multi-Generational LRU)&lt;/strong&gt;, que organiza as páginas em &lt;strong&gt;múltiplas gerações&lt;/strong&gt; por idade, aproximando o LRU real com muito mais precisão e menor custo de CPU.&lt;/p&gt;

&lt;p&gt;O MGLRU melhora significativamente a decisão de reclaim sob pressão, reduzindo tanto o thrashing quanto o uso de CPU em varredura. Para backend, o ganho é transparente: sob pressão de memória, o kernel toma decisões melhores sobre o que manter em cache. Distros modernas o habilitam por padrão.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Verificar se o MGLRU está habilitado&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/kernel/mm/lru_gen/enabled   &lt;span class="c"&gt;# 0x0007 = habilitado&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Buffered I/O vs Direct I/O
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Buffered I/O&lt;/strong&gt; (o padrão): passa pelo page cache. Ganha caching, read-ahead e write-back automáticos. É o que você quer 95% do tempo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct I/O&lt;/strong&gt; (&lt;code&gt;O_DIRECT&lt;/code&gt;): &lt;strong&gt;contorna&lt;/strong&gt; o page cache, transferindo direto entre o disco e o buffer da aplicação. Faz sentido quando a aplicação &lt;strong&gt;gerencia seu próprio cache&lt;/strong&gt; (bancos de dados como Oracle/PostgreSQL com grande shared_buffers) e o page cache seria só duplicação desperdiçada — ou para não "poluir" o cache com dados que só serão lidos uma vez.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Buffered:  app ──▶ page cache ──▶ disco     (caching, mas dupla cópia)
Direct:    app ──────────────────▶ disco     (zero-copy, mas sem caching)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  madvise e fadvise: dando dicas ao kernel
&lt;/h3&gt;

&lt;p&gt;Você pode &lt;strong&gt;orientar&lt;/strong&gt; o kernel sobre seu padrão de acesso, para ele otimizar o page cache:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;madvise(MADV_SEQUENTIAL)&lt;/code&gt; — "vou ler sequencialmente" → read-ahead agressivo.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;madvise(MADV_RANDOM)&lt;/code&gt; — "acesso aleatório" → desliga read-ahead (evita trazer páginas inúteis).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;madvise(MADV_DONTNEED)&lt;/code&gt; — "não preciso mais disto" → libera as páginas.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;posix_fadvise(POSIX_FADV_DONTNEED)&lt;/code&gt; — descarta do page cache um trecho de arquivo já processado (ótimo em ETL para não entupir o cache).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;madvise(MADV_HUGEPAGE)&lt;/code&gt; — pede huge pages para aquela região (lembra da Parte II).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arquivo_grande.dat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_RDONLY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# "Vou ler isto sequencialmente, faça read-ahead agressivo"
&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posix_fadvise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POSIX_FADV_SEQUENTIAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ... processa o arquivo ...
# "Já terminei, pode descartar do page cache" (não entope a RAM)
&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posix_fadvise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POSIX_FADV_DONTNEED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essas dicas serão centrais nos exemplos de ETL e processamento de arquivos da Parte VIII.&lt;/p&gt;




&lt;h2&gt;
  
  
  O Slab Allocator
&lt;/h2&gt;

&lt;p&gt;Até agora falamos da memória das &lt;strong&gt;aplicações&lt;/strong&gt;. Mas o &lt;strong&gt;próprio kernel&lt;/strong&gt; precisa alocar memória constantemente para suas estruturas internas: &lt;code&gt;task_struct&lt;/code&gt; (lembra do Artigo 1?), inodes, dentries, buffers de rede (&lt;code&gt;sk_buff&lt;/code&gt;), descritores de arquivo. Como ele faz isso?&lt;/p&gt;

&lt;h3&gt;
  
  
  O buddy system: a base
&lt;/h3&gt;

&lt;p&gt;Na camada mais baixa, o kernel gerencia memória física em &lt;strong&gt;potências de 2&lt;/strong&gt; de páginas (1, 2, 4, 8... frames), usando o &lt;strong&gt;buddy system&lt;/strong&gt;. Quando precisa de um bloco, ele encontra o menor bloco disponível que sirva; se for grande demais, o &lt;strong&gt;divide pela metade&lt;/strong&gt; (os dois "buddies"). Ao liberar, blocos buddy livres são &lt;strong&gt;fundidos&lt;/strong&gt; de volta.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pedido de 1 bloco (4 KB), só há um bloco de 16 KB livre:

  [       16 KB       ]
        │ split
  [  8 KB  ][  8 KB  ]
     │ split
  [4KB][4KB][  8 KB  ]
   ▲
   entrega este; os buddies ficam livres para futuras fusões
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O buddy system combate a &lt;strong&gt;fragmentação externa&lt;/strong&gt; (juntando buddies), mas sofre de &lt;strong&gt;fragmentação interna&lt;/strong&gt;: pedir 5 KB obriga a alocar um bloco de 8 KB, desperdiçando 3 KB. E aqui está o problema: o kernel raramente precisa de páginas inteiras — ele precisa de milhares de &lt;strong&gt;objetos pequenos&lt;/strong&gt; (uma &lt;code&gt;task_struct&lt;/code&gt; tem alguns KB; um inode, centenas de bytes). Usar o buddy system direto para isso seria um desperdício gigantesco.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Estado do buddy system (blocos livres por ordem/potência de 2)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/buddyinfo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Por que slab? O problema dos objetos pequenos
&lt;/h3&gt;

&lt;p&gt;O &lt;strong&gt;slab allocator&lt;/strong&gt; (conceito originado no Solaris, por Jeff Bonwick) resolve isso com duas ideias:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Caching de objetos por tipo&lt;/strong&gt;: mantém "caches" dedicados para cada tipo de objeto frequente (um cache de &lt;code&gt;task_struct&lt;/code&gt;, um de &lt;code&gt;inode&lt;/code&gt;, etc.). Cada cache pega páginas inteiras do buddy system (os "slabs") e as fatia em objetos daquele tamanho, sem desperdício.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reaproveitamento com estado&lt;/strong&gt;: objetos liberados não são destruídos — voltam ao cache &lt;strong&gt;já parcialmente inicializados&lt;/strong&gt;, prontos para reuso. Alocar/liberar vira quase de graça: só pegar/devolver da lista livre do slab.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cache slab de 'task_struct':

  ┌──────── slab (1+ páginas do buddy) ────────┐
  │ [obj][obj][obj][obj][obj][obj][obj][obj]   │
  │  usado livre usado livre livre usado ...   │
  └────────────────────────────────────────────┘
        │
   listas: slabs cheios / parciais / vazios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso dá dois ganhos enormes: elimina a fragmentação interna para objetos pequenos e melhora a &lt;strong&gt;localidade de cache&lt;/strong&gt; (objetos do mesmo tipo ficam agrupados). O &lt;code&gt;kmalloc&lt;/code&gt; do kernel é implementado sobre caches slab de tamanhos padronizados (kmalloc-8, kmalloc-16, kmalloc-32...).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ver todos os caches slab, uso e número de objetos&lt;/span&gt;
&lt;span class="nb"&gt;sudo cat&lt;/span&gt; /proc/slabinfo
&lt;span class="c"&gt;# name            &amp;lt;active_objs&amp;gt; &amp;lt;num_objs&amp;gt; &amp;lt;objsize&amp;gt; ...&lt;/span&gt;
&lt;span class="c"&gt;# task_struct           142        160       9088   ...&lt;/span&gt;
&lt;span class="c"&gt;# dentry              45230      48300        192   ...&lt;/span&gt;
&lt;span class="c"&gt;# inode_cache         30120      31000        600   ...&lt;/span&gt;

&lt;span class="c"&gt;# Visão amigável e ordenada por consumo&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;slabtop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SLUB vs SLAB vs SLOB
&lt;/h3&gt;

&lt;p&gt;O Linux teve três implementações da ideia:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Implementação&lt;/th&gt;
&lt;th&gt;Característica&lt;/th&gt;
&lt;th&gt;Uso&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SLAB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Original, com filas por CPU complexas. Bom em cache, mas com muito overhead de metadados.&lt;/td&gt;
&lt;td&gt;Legado&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SLUB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simplificado, menos metadados, escala melhor em muitos cores, melhor para debugging.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Padrão atual&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SLOB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Minimalista, para sistemas embarcados com pouquíssima RAM.&lt;/td&gt;
&lt;td&gt;Removido em kernels recentes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;O &lt;strong&gt;SLUB&lt;/strong&gt; é o padrão em praticamente todas as distribuições modernas — mais simples, mais escalável em máquinas com muitos núcleos, e com melhores ferramentas de diagnóstico.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shrinkers
&lt;/h3&gt;

&lt;p&gt;Quando a memória fica escassa (Parte VII), o kernel precisa recuperar memória não só do page cache, mas também dos caches slab. Os &lt;strong&gt;shrinkers&lt;/strong&gt; são callbacks que cada subsistema registra para liberar objetos recuperáveis sob pressão — por exemplo, o cache de &lt;code&gt;dentry&lt;/code&gt;/&lt;code&gt;inode&lt;/code&gt; (o &lt;strong&gt;slab reclaimable&lt;/strong&gt;) pode encolher para devolver páginas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Slab reclaimable (pode ser recuperado sob pressão) vs unreclaimable&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'SReclaimable|SUnreclaim'&lt;/span&gt; /proc/meminfo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso conecta diretamente com a Parte VII: sob pressão de memória, o reclaim aciona os shrinkers para encolher slabs recuperáveis, junto com o descarte de page cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging: poisoning e leak detection
&lt;/h3&gt;

&lt;p&gt;O SLUB oferece ferramentas para caçar bugs de memória do kernel: &lt;strong&gt;poisoning&lt;/strong&gt; (preenche objetos livres com um padrão conhecido, tipo &lt;code&gt;0x6b&lt;/code&gt;, para detectar use-after-free) e detecção de vazamentos. Úteis para quem escreve drivers/módulos, mas bom saber que existem quando você investiga um &lt;code&gt;SUnreclaim&lt;/code&gt; que só cresce (um vazamento de slab no kernel).&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-vii-253f"&gt;&lt;strong&gt;Continua na Parte VII&lt;/strong&gt;&lt;/a&gt;, onde juntamos tudo: como o kernel realmente &lt;strong&gt;recupera memória&lt;/strong&gt; sob pressão (kswapd, direct reclaim, watermarks, LRU lists), a métrica moderna de &lt;strong&gt;PSI (Pressure Stall Information)&lt;/strong&gt;, e o processo que todo dev backend teme e precisa entender — o &lt;strong&gt;OOM Killer&lt;/strong&gt;.&lt;br&gt;
&lt;strong&gt;:D&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conteúdo parcialmente gerado com auxílio de IA generativa (me ajudou a organizar tudo isso kkkk)&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referências Bibliográficas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Desenvolvimento-do-Kernel-Linux/dp/8573933410" rel="noopener noreferrer"&gt;Desenvolvimento Do Kernel Do Linux - David Cram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://people.eecs.berkeley.edu/~kubitron/courses/cs194-24-S13/hand-outs/bonwick_slab.pdf" rel="noopener noreferrer"&gt;The Slab Allocator - Jeff Bonwick (1994)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lwn.net/Articles/856931/" rel="noopener noreferrer"&gt;Multi-generational LRU - LWN.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/gorman/html/understand/" rel="noopener noreferrer"&gt;Understanding the Linux Virtual Memory Manager - Mel Gorman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Sistemas-Operacionais-Modernos-Andrew-Tanenbaum/dp/8582606168" rel="noopener noreferrer"&gt;Sistemas Operacionais Modernos - Tanenbaum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>dotnet</category>
      <category>go</category>
      <category>python</category>
    </item>
    <item>
      <title>Kernel Linux para Desenvolvedores Backend - Gerenciamento de Memória Parte V</title>
      <dc:creator>Alex Volnei Galante</dc:creator>
      <pubDate>Fri, 21 Aug 2026 00:53:15 +0000</pubDate>
      <link>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-v-553j</link>
      <guid>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-v-553j</guid>
      <description>&lt;p&gt;Chegamos à pergunta que os artigos passados deixaram no ar. Vimos que o kernel pode mover páginas "frias" para o swap quando a memória fica escassa. Mas &lt;strong&gt;como ele decide quais páginas são frias&lt;/strong&gt;? Qual página remover quando você precisa de um frame livre e todos estão ocupados?&lt;/p&gt;

&lt;p&gt;Essa é uma das perguntas mais estudadas em Sistemas Operacionais, e a resposta certa faz a diferença entre uma aplicação que voa e uma que passa a vida esperando o disco :D.&lt;/p&gt;

&lt;p&gt;Vamos dos algoritmos teóricos (Tanenbaum) até o que o Linux realmente implementa, &lt;strong&gt;DISCLAIMER&lt;/strong&gt;: Mano isso é muito nerdice mais eu adoro kkk&lt;/p&gt;




&lt;h2&gt;
  
  
  Sumário
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;O problema da substituição de páginas&lt;/li&gt;
&lt;li&gt;Algoritmo Ótimo (OPT)&lt;/li&gt;
&lt;li&gt;NRU: Not Recently Used&lt;/li&gt;
&lt;li&gt;FIFO e a Anomalia de Belady&lt;/li&gt;
&lt;li&gt;Segunda Chance e o Algoritmo do Relógio&lt;/li&gt;
&lt;li&gt;LRU: Least Recently Used&lt;/li&gt;
&lt;li&gt;O Modelo do Working Set&lt;/li&gt;
&lt;li&gt;WSClock&lt;/li&gt;
&lt;li&gt;Thrashing&lt;/li&gt;
&lt;li&gt;Políticas de Alocação de Frames&lt;/li&gt;
&lt;li&gt;Referências Bibliográficas&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  O problema da substituição de páginas
&lt;/h2&gt;

&lt;p&gt;O cenário é o seguinte: a memória física está cheia, e ocorre um page fault que exige trazer uma nova página do disco. Não há frames livres. O kernel &lt;strong&gt;precisa escolher uma página vítima&lt;/strong&gt; para despejar (evict), liberando um frame.&lt;/p&gt;

&lt;p&gt;Se a página vítima estiver &lt;strong&gt;suja&lt;/strong&gt; (dirty bit = 1, lembra da Parte II?), ela precisa ser gravada no disco antes de descartada. Se estiver &lt;strong&gt;limpa&lt;/strong&gt;, pode ser simplesmente descartada (o conteúdo pode ser relido da origem). A escolha da vítima é crucial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Se removermos uma página que será acessada &lt;strong&gt;logo em seguida&lt;/strong&gt;, causamos um novo page fault imediato — desperdício.&lt;/li&gt;
&lt;li&gt;Se removermos uma página que &lt;strong&gt;não será mais usada&lt;/strong&gt;, acertamos em cheio.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O objetivo de todo algoritmo de substituição é, portanto, &lt;strong&gt;prever o futuro&lt;/strong&gt;: remover a página que ficará mais tempo sem ser acessada. Como não temos bola de cristal, os algoritmos são aproximações dessa previsão.&lt;/p&gt;




&lt;h2&gt;
  
  
  Algoritmo Ótimo (OPT)
&lt;/h2&gt;

&lt;p&gt;O algoritmo ótimo é simples de enunciar: &lt;strong&gt;remova a página que só será usada mais longe no futuro&lt;/strong&gt; (ou nunca mais). Ele é comprovadamente ótimo — gera o menor número possível de page faults.&lt;/p&gt;

&lt;p&gt;O problema? É &lt;strong&gt;impossível de implementar&lt;/strong&gt;. Exigiria saber, no momento da decisão, todos os acessos futuros do programa. Ele só pode ser calculado &lt;em&gt;a posteriori&lt;/em&gt;, rodando o programa uma vez, registrando os acessos, e então analisando.&lt;/p&gt;

&lt;p&gt;Então por que estudá-lo? Porque ele é o &lt;strong&gt;baseline teórico&lt;/strong&gt;. Ao avaliar um algoritmo real, comparamos seu número de faults com o do OPT: quanto mais próximo, melhor o algoritmo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Referências: 7 0 1 2 0 3 0 4 2 3 0 3 2
3 frames. Quando precisa despejar, OPT olha o FUTURO:

Acesso ao '3' com frames [7,0,1] cheios:
  - 7 será usado? Não aparece mais → despeja o 7. ✔
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  NRU: Not Recently Used
&lt;/h2&gt;

&lt;p&gt;O NRU é a primeira aproximação prática, e usa dois bits que já conhecemos da PTE (Parte II):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;R (Referenced/Accessed)&lt;/strong&gt;: ligado pelo hardware quando a página é lida ou escrita.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;M (Modified/Dirty)&lt;/strong&gt;: ligado pelo hardware quando a página é escrita.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Periodicamente (a cada tick de clock), o kernel &lt;strong&gt;zera o bit R&lt;/strong&gt; de todas as páginas. Assim, R indica se a página foi acessada &lt;em&gt;recentemente&lt;/em&gt;. Combinando R e M, formam-se 4 classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Classe 0: não referenciada, não modificada  (R=0, M=0) ← melhor vítima
Classe 1: não referenciada, modificada      (R=0, M=1)
Classe 2: referenciada, não modificada      (R=1, M=0)
Classe 3: referenciada, modificada          (R=1, M=1) ← pior vítima
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O NRU remove uma página aleatória da &lt;strong&gt;classe não-vazia mais baixa&lt;/strong&gt;. A intuição: prefira despejar páginas não usadas recentemente (R=0) e, entre elas, as limpas (M=0, que não precisam de write-back). Simples e razoável, ainda que grosseiro.&lt;/p&gt;




&lt;h2&gt;
  
  
  FIFO e a Anomalia de Belady
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;FIFO (First-In, First-Out)&lt;/strong&gt; é o mais ingênuo: mantém uma fila das páginas na ordem em que entraram e despeja a &lt;strong&gt;mais antiga&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O problema é óbvio: a página mais antiga pode ser justamente a mais usada (pense numa variável de configuração carregada no início e acessada o tempo todo). O FIFO a despejaria cegamente.&lt;/p&gt;

&lt;p&gt;Mas o FIFO tem um defeito ainda mais bizarro e contraintuitivo: a &lt;strong&gt;Anomalia de Belady&lt;/strong&gt;. Normalmente esperamos que &lt;strong&gt;mais frames de memória → menos page faults&lt;/strong&gt;. Com o FIFO, em certas sequências de acesso, &lt;strong&gt;adicionar frames pode aumentar o número de page faults&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sequência: 1 2 3 4 1 2 5 1 2 3 4 5

Com 3 frames: 9 page faults
Com 4 frames: 10 page faults  ← MAIS frames, MAIS faults! (anomalia)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa anomalia é o que motivou o estudo dos &lt;strong&gt;stack algorithms&lt;/strong&gt; (algoritmos de pilha), como o LRU, que possuem a &lt;strong&gt;propriedade de inclusão&lt;/strong&gt;: o conjunto de páginas com $n$ frames é sempre um subconjunto do conjunto com $n+1$ frames. Algoritmos de pilha &lt;strong&gt;nunca&lt;/strong&gt; sofrem da Anomalia de Belady.&lt;/p&gt;




&lt;h2&gt;
  
  
  Segunda Chance e o Algoritmo do Relógio
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;Segunda Chance&lt;/strong&gt; é um FIFO esperto. Antes de despejar a página mais antiga, ele checa o bit &lt;strong&gt;R&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Se &lt;strong&gt;R = 0&lt;/strong&gt;: a página não foi usada recentemente → despeja.&lt;/li&gt;
&lt;li&gt;Se &lt;strong&gt;R = 1&lt;/strong&gt;: dá uma "segunda chance" — zera o R, move a página para o fim da fila (como se tivesse acabado de chegar) e continua procurando.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Assim, páginas frequentemente usadas sobrevivem, enquanto páginas realmente antigas e não usadas são despejadas.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;Algoritmo do Relógio (Clock)&lt;/strong&gt; é a implementação eficiente do Segunda Chance. Em vez de mover páginas numa fila (caro), organiza os frames num &lt;strong&gt;anel&lt;/strong&gt; com um ponteiro (o "ponteiro do relógio"):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              ┌──────┐
         ┌───▶│ P0 R1│───┐
         │    └──────┘   │
    ┌──────┐         ┌──────┐
    │ P7 R0│         │ P1 R1│
    └──────┘         └──────┘
         ▲               │
         │    ┌──────┐   ▼
         └────│ P... │◀──┘
              └──────┘
                 ▲
              ponteiro

Ao precisar de vítima, o ponteiro avança:
  - R=1 → zera R e avança (segunda chance)
  - R=0 → despeja esta página, ponteiro para no próximo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O Clock é a base conceitual do que o Linux realmente usa (com refinamentos que veremos na Parte VII, nas LRU lists).&lt;/p&gt;




&lt;h2&gt;
  
  
  LRU: Least Recently Used
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;LRU (Least Recently Used)&lt;/strong&gt; parte de uma aposta poderosa: &lt;em&gt;o passado recente é um bom preditor do futuro próximo&lt;/em&gt;. Ele despeja a página que &lt;strong&gt;há mais tempo não é acessada&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O LRU é excelente — muito próximo do ótimo na prática — mas &lt;strong&gt;caro de implementar exatamente&lt;/strong&gt;. Manter a ordem exata de último acesso exigiria atualizar uma estrutura de dados &lt;strong&gt;a cada acesso à memória&lt;/strong&gt;. Tanenbaum descreve as opções:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Com timestamps/contador&lt;/strong&gt;: cada página guarda o "tempo" do último acesso. Despejar = achar o menor timestamp. Requer hardware que escreva o contador a cada acesso — caro.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Com matriz de bits&lt;/strong&gt;: uma matriz $n \times n$ para $n$ frames, manipulada a cada referência. Inviável para muitos frames.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por isso, sistemas reais usam &lt;strong&gt;LRU aproximado&lt;/strong&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Aging (envelhecimento)
&lt;/h3&gt;

&lt;p&gt;O algoritmo de &lt;strong&gt;aging&lt;/strong&gt; aproxima o LRU com um contador por página. A cada tick, o kernel &lt;strong&gt;desloca o contador à direita&lt;/strong&gt; e insere o bit R no topo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Página A, ao longo de 5 ticks (bit R de cada tick à esquerda):
  tick 1 (R=1): 10000000
  tick 2 (R=0): 01000000
  tick 3 (R=1): 10100000
  tick 4 (R=0): 01010000
  tick 5 (R=0): 00101000

Página com MENOR valor de contador = usada há mais tempo = vítima.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O aging captura bem a noção de "recentemente usada" com custo baixo, e é a inspiração direta do mecanismo de LRU do Linux. Uma limitação: o contador tem tamanho finito, então ele "esquece" o histórico além de N ticks — mas na prática isso é suficiente.&lt;/p&gt;




&lt;h2&gt;
  
  
  O Modelo do Working Set
&lt;/h2&gt;

&lt;p&gt;Os algoritmos acima decidem &lt;strong&gt;qual&lt;/strong&gt; página despejar. Mas Peter Denning propôs uma pergunta mais profunda: &lt;strong&gt;quantas&lt;/strong&gt; páginas um processo realmente precisa ter na memória para rodar bem?&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;working set&lt;/strong&gt; $W(t, \Delta)$ é o conjunto de páginas que um processo acessou nas últimas $\Delta$ unidades de tempo (ou referências). É a formalização do "conjunto de trabalho atual":&lt;/p&gt;

&lt;p&gt;$$W(t, \Delta) = {\text{páginas referenciadas no intervalo } (t - \Delta,\ t)}$$&lt;/p&gt;

&lt;p&gt;A ideia-chave: se mantivermos o working set inteiro de um processo na memória, ele terá &lt;strong&gt;pouquíssimos page faults&lt;/strong&gt;, porque suas referências, pela localidade, caem quase todas dentro do working set. Se não conseguirmos manter o working set na memória, o processo sofre — muito.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyq1zhm1d8k739gsezkhp.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyq1zhm1d8k739gsezkhp.jpeg" alt="WorkingSet" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O tamanho do working set &lt;strong&gt;varia&lt;/strong&gt; conforme o programa muda de fase (inicialização, hot loop, shutdown). O parâmetro $\Delta$ (a janela) é um trade-off: muito pequeno perde páginas ainda úteis; muito grande retém páginas já frias.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conexão backend&lt;/strong&gt;: aquele exemplo da Parte I volta com força. Uma API pode ter 500 MB carregados, mas seu working set num dado segundo é talvez 50 MB. Dimensionar a RAM (ou o limite do container) &lt;strong&gt;abaixo do working set&lt;/strong&gt; é a receita para thrashing.&lt;/p&gt;




&lt;h2&gt;
  
  
  WSClock
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;WSClock&lt;/strong&gt; combina o melhor de dois mundos: a eficiência do algoritmo do relógio com a inteligência do working set. É o algoritmo prático de referência em muitos sistemas.&lt;/p&gt;

&lt;p&gt;Ele percorre os frames em anel (como o Clock), mas para cada página examina:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Para a página sob o ponteiro:
  - R = 1?  → zera R, atualiza "tempo do último uso", avança.
  - R = 0?  → calcula idade = tempo_atual - tempo_último_uso
      - idade ≤ Δ?  → está no working set, NÃO despeja, avança.
      - idade &amp;gt; Δ?  → fora do working set:
          - limpa?  → despeja (ótima vítima!)
          - suja?   → agenda write-back, avança (despeja na próxima volta)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O WSClock só despeja páginas que estão &lt;strong&gt;fora do working set&lt;/strong&gt; (não usadas há mais que $\Delta$), respeitando a localidade e evitando despejar páginas que serão logo reusadas. É, em essência, a filosofia que o Linux persegue com suas LRU lists e o mecanismo de reclaim (Parte VII).&lt;/p&gt;




&lt;h2&gt;
  
  
  Thrashing
&lt;/h2&gt;

&lt;p&gt;Quando o working set combinado de todos os processos &lt;strong&gt;excede&lt;/strong&gt; a memória física disponível, acontece o pesadelo: &lt;strong&gt;thrashing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No thrashing, o sistema passa mais tempo movendo páginas entre RAM e disco do que executando trabalho útil. Cada processo despeja páginas de que ainda precisa; ao acessá-las de novo, dispara major faults; para atendê-los, despeja páginas de outro processo, que também sofre — uma reação em cadeia:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPU útil
  100% │────────╲
       │         ╲
       │          ╲          ← "penhasco" do thrashing
       │           ╲________________
     0%│                            
       └────────────────────────────▶ grau de multiprogramação
                    ▲
              ponto de thrashing: demanda de memória &amp;gt; capacidade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Como detectar&lt;/strong&gt;: a assinatura clássica do thrashing é &lt;strong&gt;CPU baixa + I/O de disco (swap) altíssimo&lt;/strong&gt;. A CPU está ociosa não por falta de trabalho, mas porque todo mundo está esperando o disco.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Sinais de thrashing:&lt;/span&gt;
vmstat 1
&lt;span class="c"&gt;#  r  b   swpd   free  ...  si   so   ...  us sy id wa&lt;/span&gt;
&lt;span class="c"&gt;#  1 8  512000   2000      15000 18000     3  5  2 90&lt;/span&gt;
&lt;span class="c"&gt;#             ▲                ▲    ▲                ▲&lt;/span&gt;
&lt;span class="c"&gt;#    muito swap usado   swap in/out ALTOS    CPU 90% em I/O wait!&lt;/span&gt;

&lt;span class="c"&gt;# A ferramenta moderna e muito mais precisa (PSI) veremos na Parte VII.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prevenção: PFF (Page Fault Frequency)
&lt;/h3&gt;

&lt;p&gt;Uma técnica de controle é o &lt;strong&gt;PFF (Page Fault Frequency)&lt;/strong&gt;: monitorar a taxa de page faults de cada processo e ajustar dinamicamente quantos frames ele recebe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Taxa de faults &lt;strong&gt;acima&lt;/strong&gt; de um limite superior → o processo precisa de &lt;strong&gt;mais&lt;/strong&gt; frames.&lt;/li&gt;
&lt;li&gt;Taxa &lt;strong&gt;abaixo&lt;/strong&gt; de um limite inferior → o processo tem frames &lt;strong&gt;demais&lt;/strong&gt;; pode ceder alguns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se não há frames suficientes para todos, o remédio drástico é &lt;strong&gt;suspender&lt;/strong&gt; (fazer swap-out completo de) um processo inteiro, liberando seus frames para os demais rodarem sem thrashing — melhor sacrificar um do que travar todos. Essa ideia ecoa no &lt;strong&gt;OOM Killer&lt;/strong&gt; do Linux (Parte VII).&lt;/p&gt;




&lt;h2&gt;
  
  
  Políticas de Alocação de Frames
&lt;/h2&gt;

&lt;p&gt;Por fim: como distribuir os frames disponíveis entre os processos?&lt;/p&gt;

&lt;h3&gt;
  
  
  Alocação Global vs Local
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Alocação local&lt;/strong&gt;: cada processo tem um conjunto &lt;strong&gt;fixo&lt;/strong&gt; de frames; ao precisar despejar, escolhe a vítima &lt;strong&gt;apenas entre suas próprias páginas&lt;/strong&gt;. Oferece isolamento (o comportamento de um processo não afeta os outros), mas pode ser ineficiente — um processo pode ter frames sobrando enquanto outro passa fome.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Alocação global&lt;/strong&gt;: a vítima é escolhida entre as páginas de &lt;strong&gt;todos&lt;/strong&gt; os processos. Mais eficiente no uso agregado da memória, mas processos competem entre si — um processo "faminto" pode roubar frames de outros.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O Linux usa fundamentalmente uma abordagem &lt;strong&gt;global&lt;/strong&gt; (o reclaim opera sobre LRU lists globais por zona/nó), mas os &lt;strong&gt;cgroups v2&lt;/strong&gt; (Parte VIII) reintroduzem fronteiras locais, permitindo limitar e isolar a memória por grupo de processos — o melhor dos dois mundos para containers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Igual vs Proporcional
&lt;/h3&gt;

&lt;p&gt;Ao alocar globalmente, como dividir? &lt;strong&gt;Igualmente&lt;/strong&gt; entre processos é injusto (um processo pequeno não precisa do mesmo que um grande). A &lt;strong&gt;alocação proporcional&lt;/strong&gt; dá frames na proporção do tamanho de cada processo, e variantes consideram &lt;strong&gt;prioridade&lt;/strong&gt; — processos mais importantes recebem mais memória.&lt;/p&gt;

&lt;p&gt;Esses conceitos teóricos são exatamente o que os controles de cgroup (&lt;code&gt;memory.low&lt;/code&gt;, &lt;code&gt;memory.high&lt;/code&gt;, &lt;code&gt;memory.max&lt;/code&gt;) implementam na prática moderna — veremos na Parte VIII.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-vi-5h0m"&gt;&lt;strong&gt;Continua na Parte VI&lt;/strong&gt;&lt;/a&gt;, onde saímos da teoria e entramos numa das estruturas mais importantes (e menos compreendidas) do Linux: o &lt;strong&gt;page cache&lt;/strong&gt; — o motivo pelo qual sua segunda leitura de um arquivo é instantânea — e o &lt;strong&gt;slab allocator&lt;/strong&gt;, como o próprio kernel gerencia sua memória interna.&lt;br&gt;
&lt;strong&gt;:D&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conteúdo parcialmente gerado com auxílio de IA generativa (me ajudou a organizar tudo isso kkkk)&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referências Bibliográficas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Desenvolvimento-do-Kernel-Linux/dp/8573933410" rel="noopener noreferrer"&gt;Desenvolvimento Do Kernel Do Linux - David Cram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dl.acm.org/doi/10.1145/363095.363141" rel="noopener noreferrer"&gt;The Working Set Model for Program Behavior - Peter Denning (1968)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/gorman/html/understand/" rel="noopener noreferrer"&gt;Understanding the Linux Virtual Memory Manager - Mel Gorman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Sistemas-Operacionais-Modernos-Andrew-Tanenbaum/dp/8582606168" rel="noopener noreferrer"&gt;Sistemas Operacionais Modernos - Tanenbaum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>dotnet</category>
      <category>go</category>
      <category>python</category>
    </item>
    <item>
      <title>Kernel Linux para Desenvolvedores Backend - Gerenciamento de Memória Parte IV</title>
      <dc:creator>Alex Volnei Galante</dc:creator>
      <pubDate>Fri, 21 Aug 2026 00:49:49 +0000</pubDate>
      <link>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-iv-kd7</link>
      <guid>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-iv-kd7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
Última parte do &lt;strong&gt;Artigo 2&lt;/strong&gt; (Fundamentos e Virtual Memory). Nas partes anteriores cobrimos hierarquia de memória, paginação, TLB, page faults, COW e swapping. Agora fechamos com &lt;strong&gt;zones e NUMA&lt;/strong&gt;, a conexão com os runtimes de &lt;strong&gt;Python, .NET e Go&lt;/strong&gt;, diagnóstico e boas práticas.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Sumário
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Memory Zones&lt;/li&gt;
&lt;li&gt;
NUMA: quando a memória tem geografia

&lt;ul&gt;
&lt;li&gt;Automatic NUMA Balancing&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Conexão com Backend: Python (CPython)&lt;/li&gt;
&lt;li&gt;Conexão com Backend: .NET (CLR)&lt;/li&gt;
&lt;li&gt;Conexão com Backend: Go&lt;/li&gt;
&lt;li&gt;Técnicas de Diagnóstico&lt;/li&gt;
&lt;li&gt;Boas Práticas&lt;/li&gt;
&lt;li&gt;Estudos de Caso&lt;/li&gt;
&lt;li&gt;Referências Bibliográficas&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Memory Zones
&lt;/h2&gt;

&lt;p&gt;Até agora tratamos a RAM como um bloco uniforme de frames. Mas o kernel precisa lidar com restrições de hardware que tornam certas regiões da memória física &lt;strong&gt;especiais&lt;/strong&gt;. Por isso ele divide a memória física em &lt;strong&gt;zones&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────┬─────────────────────────────────────────────────────┐
│ ZONE_DMA     │ Memória para dispositivos antigos com endereçamento  │
│              │ limitado (ex.: primeiros 16 MB em x86)               │
├──────────────┼─────────────────────────────────────────────────────┤
│ ZONE_DMA32   │ Memória endereçável por dispositivos de 32 bits      │
│              │ (primeiros 4 GB)                                     │
├──────────────┼─────────────────────────────────────────────────────┤
│ ZONE_NORMAL  │ Memória "normal", diretamente mapeada pelo kernel.   │
│              │ Onde vive a maioria das alocações.                  │
├──────────────┼─────────────────────────────────────────────────────┤
│ ZONE_HIGHMEM │ (Relíquia do x86 32-bit; irrelevante em 64 bits)     │
└──────────────┴─────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por que isso existe? Porque alguns dispositivos de hardware (especialmente antigos) só conseguem fazer DMA (acesso direto à memória) em faixas baixas de endereço. O kernel precisa garantir que buffers de DMA venham dessas zonas específicas. Em servidores modernos de 64 bits, a esmagadora maioria das alocações vem de &lt;strong&gt;ZONE_NORMAL&lt;/strong&gt;, e você raramente pensa nas outras — mas elas aparecem quando você lê &lt;code&gt;/proc/zoneinfo&lt;/code&gt; diagnosticando pressão de memória.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ver as zonas e suas watermarks (min/low/high — importantes no Artigo 3)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/zoneinfo | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'Node|zone|free|min|low|high'&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada zona tem suas próprias &lt;strong&gt;watermarks&lt;/strong&gt; (min, low, high) que governam quando o kernel começa a recuperar memória — um mecanismo central que detalharemos no Artigo 3 (memory reclaim).&lt;/p&gt;




&lt;h2&gt;
  
  
  NUMA: quando a memória tem geografia
&lt;/h2&gt;

&lt;p&gt;Em servidores com múltiplos sockets de CPU, a memória deixa de ser uniforme numa dimensão nova: a &lt;strong&gt;distância&lt;/strong&gt;. Isso é o &lt;strong&gt;NUMA (Non-Uniform Memory Access)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Num sistema NUMA, cada socket de CPU (um &lt;strong&gt;nó NUMA&lt;/strong&gt;) tem sua própria memória local, fisicamente próxima. Acessar a memória local é rápido; acessar a memória de &lt;strong&gt;outro&lt;/strong&gt; nó (remota) passa pela interconexão entre sockets e é &lt;strong&gt;mais lento&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        Nó 0                             Nó 1
┌───────────────────┐                ┌───────────────────┐
│   CPUs 0-15       │                │   CPUs 16-31      │
│        │          │                │        │          │
│   ┌────┴────┐     │◀──────────────▶│   ┌────┴────┐     │
│   │ RAM     │     │  interconexão  │   │  RAM    │     │
│   │ local   │     │  (mais lenta)  │   │  local  │     │
│   └─────────┘     │                │   └─────────┘     │
└───────────────────┘                └───────────────────┘

 Acesso local:  ~100 ns          Acesso remoto: ~150-200 ns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A diferença de latência entre acesso local e remoto pode ser de 1.5x a 2x. Para aplicações sensíveis a latência, isso importa &lt;strong&gt;muito&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O kernel expõe a topologia e tenta, por padrão, alocar memória no mesmo nó onde a thread está rodando (política &lt;em&gt;first-touch&lt;/em&gt;: a página vai para o nó que a &lt;strong&gt;tocou primeiro&lt;/strong&gt;, não o que a alocou).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ver a topologia NUMA da máquina&lt;/span&gt;
numactl &lt;span class="nt"&gt;--hardware&lt;/span&gt;
&lt;span class="c"&gt;# available: 2 nodes (0-1)&lt;/span&gt;
&lt;span class="c"&gt;# node 0 cpus: 0 1 2 ... 15&lt;/span&gt;
&lt;span class="c"&gt;# node 0 size: 64000 MB&lt;/span&gt;
&lt;span class="c"&gt;# node 1 cpus: 16 17 ... 31&lt;/span&gt;
&lt;span class="c"&gt;# node distances:&lt;/span&gt;
&lt;span class="c"&gt;# node   0   1&lt;/span&gt;
&lt;span class="c"&gt;#   0:  10  21     ← distância relativa (10 = local, 21 = ~2x mais lento)&lt;/span&gt;
&lt;span class="c"&gt;#   1:  21  10&lt;/span&gt;

&lt;span class="c"&gt;# Estatísticas NUMA por nó (hits locais vs faltas remotas)&lt;/span&gt;
numastat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;strong&gt;distance matrix&lt;/strong&gt; (&lt;code&gt;node distances&lt;/code&gt;) quantifica o custo relativo: 10 é o baseline local, e valores maiores indicam nós mais "distantes".&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic NUMA Balancing
&lt;/h3&gt;

&lt;p&gt;Desde o Kernel 3.8, o Linux tem &lt;strong&gt;balanceamento automático de NUMA&lt;/strong&gt;: ele monitora quais nós acessam quais páginas e &lt;strong&gt;migra&lt;/strong&gt; páginas e threads para melhorar a localidade, dinamicamente. Isso usa uma técnica engenhosa: periodicamente desmapeia páginas (marcando PTEs como não-presentes) para forçar page faults "de amostragem" (NUMA hinting faults) que revelam o padrão de acesso.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Verificar se está ativo (1 = ativo)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/sys/kernel/numa_balancing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para muitas aplicações isso é ótimo e transparente. Mas para workloads de altíssima performance e latência previsível (bancos de dados, trading, caches grandes), o balanceamento automático pode introduzir jitter — nesses casos, faz-se &lt;strong&gt;pinning explícito&lt;/strong&gt; (fixar processo e memória a um nó via &lt;code&gt;numactl&lt;/code&gt;), que veremos nas boas práticas.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conexão com Backend: Python (CPython)
&lt;/h2&gt;

&lt;p&gt;Agora conectamos tudo com o que roda de fato em produção. Começando pelo CPython.&lt;/p&gt;

&lt;h3&gt;
  
  
  O modelo de memória do CPython
&lt;/h3&gt;

&lt;p&gt;O CPython tem seu próprio alocador em camadas &lt;strong&gt;sobre&lt;/strong&gt; o &lt;code&gt;malloc&lt;/code&gt; do sistema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Objetos Python
      │
      ▼
┌──────────────┐  pymalloc: alocador especializado para objetos pequenos (&amp;lt; 512 bytes)
│  pymalloc    │  organiza memória em "arenas" (256 KB), "pools" (4 KB = 1 página!)
└──────┬───────┘  e "blocks". Reduz overhead de malloc para os milhões de objetinhos.
       │ (objetos grandes)
       ▼
┌──────────────┐
│  malloc/mmap │  → brk/mmap → kernel → page faults → frames físicos
└──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repare que um &lt;strong&gt;pool&lt;/strong&gt; do pymalloc tem exatamente &lt;strong&gt;4 KB&lt;/strong&gt; — o tamanho de uma página. Não é coincidência: alinhar com a página evita desperdício e melhora a localidade.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reference counting e page faults
&lt;/h3&gt;

&lt;p&gt;Como vimos na Parte III, o refcount do CPython é uma &lt;strong&gt;escrita&lt;/strong&gt; a cada toque num objeto. Isso tem duas consequências de memória:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Quebra de COW&lt;/strong&gt; em workers pre-forked (discutido na Parte III) — o grande vilão do inchaço de memória em Gunicorn/uWSGI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dirty pages constantes&lt;/strong&gt;: mesmo objetos "só de leitura" ficam sujos (dirty bit ligado), o que afeta reclaim e swap.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Fragmentação em aplicações long-running
&lt;/h3&gt;

&lt;p&gt;Um problema clássico do CPython: memória liberada nem sempre volta ao SO. O pymalloc segura arenas, e a fragmentação da heap pode fazer o RSS de um worker &lt;strong&gt;só crescer&lt;/strong&gt;, nunca diminuir, mesmo após picos de carga terminarem. É por isso que muitas equipes configuram Gunicorn com &lt;code&gt;max_requests&lt;/code&gt; (recicla o worker após N requisições, devolvendo memória ao SO).&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagnóstico prático
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tracemalloc&lt;/span&gt;

&lt;span class="n"&gt;tracemalloc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# ... executa a carga de trabalho ...
&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracemalloc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take_snapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lineno&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;stat&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# mostra as linhas que mais alocam memória
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Correlacionar com o kernel: page faults do processo Python&lt;/span&gt;
perf &lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; minor-faults,major-faults &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;PID_python&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mitigando o inchaço por COW
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;gc&lt;/span&gt;

&lt;span class="c1"&gt;# ANTES do fork (no master), congela os objetos já criados na geração
# permanente, evitando que o GC toque seus headers e quebre o COW.
&lt;/span&gt;&lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# ... então o Gunicorn/uWSGI faz fork dos workers ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Estratégia complementar: manter dados grandes e imutáveis &lt;strong&gt;fora&lt;/strong&gt; do heap gerenciado do Python — em arrays NumPy (buffer contíguo, sem refcount por elemento), em memória mapeada (&lt;code&gt;mmap&lt;/code&gt;), ou num serviço externo (Redis). Assim os workers realmente compartilham, sem cópia.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conexão com Backend: .NET (CLR)
&lt;/h2&gt;

&lt;p&gt;O CLR (CoreCLR) no Linux tem um modelo de memória sofisticado, dominado pelo &lt;strong&gt;Garbage Collector&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managed heap vs unmanaged memory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────┐
│                Managed Heap                 │
│  ┌──────────────┐  ┌──────────────────────┐ │
│  │ SOH          │  │ LOH (Large Object    │ │
│  │ (Gen0/1/2)   │  │  Heap, ≥ 85 KB)      │ │
│  └──────────────┘  └──────────────────────┘ │
└─────────────────────────────────────────────┘
        │ o GC gerencia; sob o capô é tudo
        ▼ mmap/VirtualAlloc → páginas do kernel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;O &lt;strong&gt;Small Object Heap (SOH)&lt;/strong&gt; é geracional (Gen0, Gen1, Gen2), apostando que a maioria dos objetos morre jovem.&lt;/li&gt;
&lt;li&gt;O &lt;strong&gt;Large Object Heap (LOH)&lt;/strong&gt; guarda objetos ≥ 85 KB. Ele &lt;strong&gt;não é compactado por padrão&lt;/strong&gt; (compactar objetos grandes é caro), então sofre de &lt;strong&gt;fragmentação externa&lt;/strong&gt; — exatamente o conceito da Parte I reaparecendo. Buracos no LOH podem inflar o RSS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GC e page faults
&lt;/h3&gt;

&lt;p&gt;Um ciclo de GC "toca" muita memória (varrendo objetos, movendo-os na compactação do SOH). Isso pode disparar rajadas de page faults — inclusive &lt;strong&gt;major faults&lt;/strong&gt; se parte da heap tiver ido para o swap. Um GC que precisa "puxar" a heap de volta do swap é uma fonte clássica de pausas longas em produção.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workstation GC vs Server GC no Linux
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workstation GC&lt;/strong&gt;: uma thread de GC, menor footprint. Bom para apps pequenas/desktop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server GC&lt;/strong&gt;: uma heap e uma thread de GC &lt;strong&gt;por core&lt;/strong&gt;, muito mais throughput — mas consome mais memória. Em containers, o Server GC pode alocar heaps por CPU do &lt;strong&gt;host&lt;/strong&gt;, não do container, causando surpresas de consumo.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// runtimeconfig.json — atenção crítica em containers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"configProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"System.GC.Server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c1"&gt;// Faz o CLR respeitar os limites de cgroup do container:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"System.GC.HeapHardLimitPercent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]&lt;br&gt;
Em Kubernetes/containers, garanta que o CLR "enxerga" o limite de memória do cgroup. Versões modernas do .NET detectam cgroups automaticamente, mas vale verificar — senão o Server GC dimensiona a heap pela RAM do host e você toma OOM Kill (Artigo 3).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Diagnóstico
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Contadores de GC e memória ao vivo&lt;/span&gt;
dotnet-counters monitor &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;PID&amp;gt; &lt;span class="nt"&gt;--counters&lt;/span&gt; System.Runtime

&lt;span class="c"&gt;# Coletar um dump para analisar a heap&lt;/span&gt;
dotnet-dump collect &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;PID&amp;gt;
dotnet-dump analyze core_dump
&lt;span class="c"&gt;# &amp;gt; dumpheap -stat        (objetos por tipo/tamanho)&lt;/span&gt;
&lt;span class="c"&gt;# &amp;gt; gcheapstat            (fragmentação por geração/LOH)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conexão com Backend: Go
&lt;/h2&gt;

&lt;p&gt;O runtime do Go tem um alocador inspirado no &lt;strong&gt;TCMalloc&lt;/strong&gt; e um GC concorrente, com decisões que impactam diretamente o padrão de páginas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stack vs heap e escape analysis
&lt;/h3&gt;

&lt;p&gt;O compilador Go decide, em tempo de compilação, se um valor vive na &lt;strong&gt;stack&lt;/strong&gt; (barato, liberado ao retornar da função) ou "escapa" para a &lt;strong&gt;heap&lt;/strong&gt; (gerenciado pelo GC). Isso é a &lt;strong&gt;escape analysis&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// NÃO escapa: 'x' vive na stack, zero pressão sobre o GC/heap.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;soma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// ESCAPA: o ponteiro sobrevive à função → vai para a heap → pressão de GC.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;criaUsuario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Usuario&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Usuario&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Nome&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;   &lt;span class="c"&gt;// &amp;amp;u escapa&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ver as decisões de escape analysis do compilador&lt;/span&gt;
go build &lt;span class="nt"&gt;-gcflags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'-m'&lt;/span&gt; ./...
&lt;span class="c"&gt;# ./main.go:12:9: &amp;amp;u escapes to heap&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Menos alocações na heap = menos trabalho do GC = menos páginas sujas = menos pressão de memória no kernel. Otimizar escape analysis é otimizar a interação com o subsistema de memória do kernel.&lt;/p&gt;

&lt;h3&gt;
  
  
  O alocador e o kernel
&lt;/h3&gt;

&lt;p&gt;O Go pede memória ao kernel em grandes blocos via &lt;code&gt;mmap&lt;/code&gt; e os subdivide internamente em &lt;em&gt;size classes&lt;/em&gt; (semelhante ao pymalloc e ao slab do kernel). O runtime tem seu próprio conceito de páginas (8 KB, dois frames de 4 KB) organizadas em spans.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagnóstico com pprof e GODEBUG
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"net/http/pprof"&lt;/span&gt;   &lt;span class="c"&gt;// expõe profiles em /debug/pprof/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Profile de heap: quem está alocando&lt;/span&gt;
go tool pprof http://localhost:6060/debug/pprof/heap

&lt;span class="c"&gt;# Rastreio de cada ciclo de GC (pausa, heap, etc.)&lt;/span&gt;
&lt;span class="nv"&gt;GODEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;gctrace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ./minha-app
&lt;span class="c"&gt;# gc 1 @0.012s 2%: 0.018+1.2+0.003 ms clock, ... 4-&amp;gt;5-&amp;gt;2 MB, 5 MB goal&lt;/span&gt;

&lt;span class="c"&gt;# Correlacionar com page faults do kernel&lt;/span&gt;
perf &lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; minor-faults,major-faults ./minha-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;GOGC&lt;/code&gt; (padrão 100) controla o trade-off: valores maiores = GC menos frequente = mais RAM usada, menos CPU em GC. O &lt;code&gt;GOMEMLIMIT&lt;/code&gt; (Go 1.19+) define um teto suave de memória — &lt;strong&gt;essencial em containers&lt;/strong&gt; para evitar OOM Kill, funcionando como um "freio" antes de o cgroup estourar.&lt;/p&gt;




&lt;h2&gt;
  
  
  Técnicas de Diagnóstico
&lt;/h2&gt;

&lt;p&gt;Um resumo do arsenal para investigar memória em produção, conectando com tudo que vimos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Visão geral do processo: virtual vs residente&lt;/span&gt;
pmap &lt;span class="nt"&gt;-x&lt;/span&gt; &amp;lt;PID&amp;gt;                    &lt;span class="c"&gt;# mapa detalhado com RSS por região&lt;/span&gt;
ps &lt;span class="nt"&gt;-o&lt;/span&gt; pid,vsz,rss,cmd &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;PID&amp;gt;   &lt;span class="c"&gt;# VSZ (virtual) vs RSS (físico)&lt;/span&gt;

&lt;span class="c"&gt;# 2. Detalhamento por VMA: dirty pages, PSS (proporcional), swap&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&amp;lt;PID&amp;gt;/smaps            &lt;span class="c"&gt;# rico, porém verboso&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'Rss|Pss|Swap'&lt;/span&gt; /proc/&amp;lt;PID&amp;gt;/smaps_rollup   &lt;span class="c"&gt;# resumo agregado&lt;/span&gt;

&lt;span class="c"&gt;# 3. Page faults (minor = RAM, major = disco!)&lt;/span&gt;
perf &lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; minor-faults,major-faults &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;PID&amp;gt;

&lt;span class="c"&gt;# 4. Pressão de memória do sistema&lt;/span&gt;
vmstat 1                         &lt;span class="c"&gt;# colunas si/so (swap in/out) → alerta!&lt;/span&gt;
free &lt;span class="nt"&gt;-h&lt;/span&gt;

&lt;span class="c"&gt;# 5. Transparent Huge Pages em uso&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; AnonHugePages /proc/&amp;lt;PID&amp;gt;/smaps_rollup

&lt;span class="c"&gt;# 6. NUMA: acessos locais vs remotos&lt;/span&gt;
numastat &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;PID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;PSS (Proportional Set Size)&lt;/strong&gt; merece destaque: quando várias instâncias compartilham páginas (código, COW), o RSS conta a página inteira para cada processo, superestimando o total. O PSS divide páginas compartilhadas proporcionalmente, dando uma medida mais honesta de "quanto este processo realmente custa" de memória.&lt;/p&gt;




&lt;h2&gt;
  
  
  Boas Práticas
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dimensione heaps explicitamente em containers.&lt;/strong&gt; Configure &lt;code&gt;GOMEMLIMIT&lt;/code&gt; (Go), &lt;code&gt;HeapHardLimitPercent&lt;/code&gt; (.NET) e limites de worker (Python/Gunicorn &lt;code&gt;max_requests&lt;/code&gt;). Nunca confie que o runtime vai adivinhar o limite do cgroup — verifique.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use huge pages para databases e caches grandes&lt;/strong&gt;, mas com critério. Habilite HugeTLB explícito para o banco; &lt;strong&gt;desabilite THP&lt;/strong&gt; (&lt;code&gt;madvise&lt;/code&gt; ou &lt;code&gt;never&lt;/code&gt;) se sua stack (Redis, Mongo, PostgreSQL) recomenda, para evitar jitter de latência.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;NUMA pinning para aplicações latency-sensitive.&lt;/strong&gt; Fixe processo e memória ao mesmo nó:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   numactl &lt;span class="nt"&gt;--cpunodebind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;--membind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 ./minha-app-critica
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Preserve o COW em workers Python pre-forked&lt;/strong&gt;: &lt;code&gt;gc.freeze()&lt;/code&gt; antes do fork, dados grandes fora do heap gerenciado.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure swap conscientemente em containers.&lt;/strong&gt; Em SSD/NVMe, um swap moderado pode ser um amortecedor útil (kernels 5.14+ o tornaram eficiente), mas ajuste &lt;code&gt;vm.swappiness&lt;/code&gt; conforme o workload (aprofundamos no Artigo 3).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Estudos de Caso
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Python — Otimizando um cliente Redis com huge pages
&lt;/h3&gt;

&lt;p&gt;Uma aplicação de cache intensivo com grandes estruturas em memória sofria com TLB misses (working set de vários GB, muitas páginas de 4 KB). Habilitar huge pages para a região de dados reduziu a pressão sobre a TLB, cortando a latência de cauda (p99) de acessos ao cache. Diagnóstico feito com &lt;code&gt;perf stat -e dTLB-load-misses&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. .NET — Tuning de GC para uma API de alta vazão
&lt;/h3&gt;

&lt;p&gt;Uma API ASP.NET Core em Kubernetes tomava OOM Kill esporádico. Investigação com &lt;code&gt;dotnet-counters&lt;/code&gt; mostrou o Server GC dimensionando a heap pela RAM do host (não do container). Correção: &lt;code&gt;HeapHardLimitPercent&lt;/code&gt; no runtimeconfig + verificação da detecção de cgroup. RSS estabilizou abaixo do limite e o OOM sumiu.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Go — Reduzindo allocation pressure em um stream processor
&lt;/h3&gt;

&lt;p&gt;Um processador de streams tinha ciclos de GC frequentes (visíveis em &lt;code&gt;GODEBUG=gctrace=1&lt;/code&gt;) causando picos de CPU. &lt;code&gt;go build -gcflags='-m'&lt;/code&gt; revelou allocations desnecessárias na heap (buffers escapando em hot path). Reuso via &lt;code&gt;sync.Pool&lt;/code&gt; e ajuste de &lt;code&gt;GOGC&lt;/code&gt; reduziram a frequência de GC e a quantidade de páginas sujas por segundo.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-v-553j"&gt;&lt;strong&gt;Continua na Parte V&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;:D&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conteúdo parcialmente gerado com auxílio de IA generativa (me ajudou a organizar tudo isso kkkk)&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referências Bibliográficas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Desenvolvimento-do-Kernel-Linux/dp/8573933410" rel="noopener noreferrer"&gt;Desenvolvimento Do Kernel Do Linux - David Cram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/gorman/html/understand/" rel="noopener noreferrer"&gt;Understanding the Linux Virtual Memory Manager - Mel Gorman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://people.freebsd.org/~lstewart/articles/cpumemory.pdf" rel="noopener noreferrer"&gt;What Every Programmer Should Know About Memory - Ulrich Drepper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/dotnet/core/runtime-config/garbage-collector" rel="noopener noreferrer"&gt;.NET GC e Containers - Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.dev/doc/gc-guide" rel="noopener noreferrer"&gt;Go Memory Management - The Go Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Sistemas-Operacionais-Modernos-Andrew-Tanenbaum/dp/8582606168" rel="noopener noreferrer"&gt;Sistemas Operacionais Modernos - Tanenbaum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>dotnet</category>
      <category>go</category>
      <category>python</category>
    </item>
    <item>
      <title>Kernel Linux para Desenvolvedores Backend - Gerenciamento de Memória Parte III</title>
      <dc:creator>Alex Volnei Galante</dc:creator>
      <pubDate>Fri, 21 Aug 2026 00:46:12 +0000</pubDate>
      <link>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-iii-15b6</link>
      <guid>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-iii-15b6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
Continuação da série. Na &lt;strong&gt;Parte II&lt;/strong&gt; cobrimos paginação, tabelas de páginas multinível (PGD/PUD/PMD/PTE), TLB e segmentação. Agora entramos na parte &lt;strong&gt;dinâmica&lt;/strong&gt;: o que acontece em tempo de execução quando seu processo toca a memória.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Sumário
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Page Faults: o coração da memória virtual

&lt;ul&gt;
&lt;li&gt;Minor vs Major faults&lt;/li&gt;
&lt;li&gt;A sequência de tratamento&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Demand Paging&lt;/li&gt;
&lt;li&gt;Copy-on-Write: por que fork() é barato&lt;/li&gt;
&lt;li&gt;Zero-fill on Demand&lt;/li&gt;
&lt;li&gt;
O Memory Layout de um processo real

&lt;ul&gt;
&lt;li&gt;Lendo /proc/pid/maps&lt;/li&gt;
&lt;li&gt;mmap: o canivete suíço da memória&lt;/li&gt;
&lt;li&gt;ASLR&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Paging e Swapping modernos

&lt;ul&gt;
&lt;li&gt;Páginas anônimas vs file-backed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Referências Bibliográficas&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Page Faults: o coração da memória virtual
&lt;/h2&gt;

&lt;p&gt;Na Parte II vimos o bit &lt;strong&gt;Present&lt;/strong&gt; na PTE. Quando a MMU tenta traduzir um endereço virtual e encontra uma PTE com Present = 0 (ou uma violação de permissão), ela dispara uma exceção de hardware: o &lt;strong&gt;page fault&lt;/strong&gt;. O controle passa imediatamente ao kernel, que decide o que fazer.&lt;/p&gt;

&lt;p&gt;É crucial entender: &lt;strong&gt;page faults são normais e esperados&lt;/strong&gt;. Eles não são erros — são o mecanismo pelo qual a memória virtual funciona. Seu processo dispara milhares deles por segundo, e a maioria é resolvida em nanossegundos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Minor vs Major faults
&lt;/h3&gt;

&lt;p&gt;A distinção mais importante para diagnóstico em produção:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Minor fault (soft fault)&lt;/strong&gt;: a página já está na RAM, mas não havia mapeamento na tabela de páginas daquele processo. O kernel só precisa criar a PTE. É &lt;strong&gt;rápido&lt;/strong&gt; — não envolve disco. Exemplos: primeira escrita numa página recém-alocada, compartilhamento de uma biblioteca já carregada por outro processo, acesso a uma página que estava no page cache.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Major fault (hard fault)&lt;/strong&gt;: a página &lt;strong&gt;não está na RAM&lt;/strong&gt; e precisa ser trazida do disco (do swap, ou de um arquivo mapeado). Envolve I/O de disco, então é &lt;strong&gt;ordens de magnitude mais lento&lt;/strong&gt; — pode custar milissegundos. Uma rajada de major faults é um sintoma clássico de pressão de memória ou thrashing.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ver minor e major faults de um processo (colunas min_flt e maj_flt)&lt;/span&gt;
ps &lt;span class="nt"&gt;-o&lt;/span&gt; min_flt,maj_flt,cmd &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;PID&amp;gt;

&lt;span class="c"&gt;# Monitorar page faults ao vivo&lt;/span&gt;
perf &lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; minor-faults,major-faults &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;PID&amp;gt;

&lt;span class="c"&gt;# Faults do sistema inteiro (campos pgfault, pgmajfault)&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'pgfault|pgmajfault'&lt;/span&gt; /proc/vmstat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Regra de ouro para produção&lt;/strong&gt;: um número alto de &lt;strong&gt;minor faults&lt;/strong&gt; é geralmente inofensivo (é só a aplicação alocando memória). Um número alto e crescente de &lt;strong&gt;major faults&lt;/strong&gt; é um alarme — significa que seu processo está batendo no disco para acessar memória, o que destrói a latência.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Agora vamos falar a verdade pura, 99% dos desenvolvedores nunca fizeram esse tipo de troubleshooting, mas é &lt;strong&gt;fundamental&lt;/strong&gt; para backend: entender page faults, demand paging, COW e zero-fill on demand é o que separa um engenheiro que entende de memória de um que só sabe alocar objetos e esperar que o GC resolva.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  A sequência de tratamento
&lt;/h3&gt;

&lt;p&gt;Quando um page fault ocorre, o handler do kernel (&lt;code&gt;do_page_fault&lt;/code&gt; no x86) segue, simplificadamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. A MMU dispara o fault e informa o endereço que causou o problema (registrador CR2 no x86).
2. O kernel consulta as VMAs (Virtual Memory Areas) do processo:
     - O endereço pertence a alguma região válida do processo?
        └── NÃO → SIGSEGV (segmentation fault). Processo geralmente morre.
        └── SIM → continua.
3. O acesso é permitido? (ex: escrita numa página read-only)
        └── Violação real → SIGSEGV
        └── É Copy-on-Write → copia a página, marca como writable (ver adiante)
4. A página está na RAM?
        └── SIM (minor) → cria/atualiza a PTE, retoma a execução
        └── NÃO (major) → aloca frame, busca do disco/swap, cria PTE, retoma
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repare que o mesmo mecanismo — o page fault — serve a &lt;strong&gt;vários propósitos&lt;/strong&gt;: detectar acessos inválidos (SIGSEGV), implementar demand paging, viabilizar Copy-on-Write e trazer páginas do swap. É um dos designs mais elegantes de todo o kernel.&lt;/p&gt;




&lt;h2&gt;
  
  
  Demand Paging
&lt;/h2&gt;

&lt;p&gt;Munidos do conceito de page fault, entendemos o &lt;strong&gt;demand paging&lt;/strong&gt;: o kernel &lt;strong&gt;não&lt;/strong&gt; carrega um programa inteiro na memória quando ele inicia. Em vez disso, carrega páginas &lt;strong&gt;sob demanda&lt;/strong&gt;, conforme são realmente acessadas.&lt;/p&gt;

&lt;p&gt;Quando você executa um binário de 200 MB:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;O kernel mapeia o arquivo no espaço virtual do processo (via &lt;code&gt;mmap&lt;/code&gt;), mas &lt;strong&gt;quase nada&lt;/strong&gt; é carregado na RAM ainda. As PTEs estão com Present = 0.&lt;/li&gt;
&lt;li&gt;A execução começa. A primeira instrução dispara um page fault → o kernel carrega &lt;strong&gt;aquela página&lt;/strong&gt; do binário.&lt;/li&gt;
&lt;li&gt;Conforme a execução avança e toca novas páginas, cada uma é trazida sob demanda.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O resultado: um processo com 200 MB de binário pode iniciar tocando só alguns MB — exatamente o seu &lt;strong&gt;working set&lt;/strong&gt; inicial. Isso torna a inicialização rápida e economiza RAM enormemente, especialmente quando várias instâncias do mesmo binário rodam (elas compartilham as páginas de código, read-only, na RAM).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conexão backend&lt;/strong&gt;: é por isso que o RSS (Resident Set Size) de um processo recém-iniciado é muito menor que o tamanho virtual (VSZ). O processo "reservou" um espaço virtual grande, mas só uma fração está fisicamente residente.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# VSZ (virtual) vs RSS (residente físico)&lt;/span&gt;
ps &lt;span class="nt"&gt;-o&lt;/span&gt; vsz,rss,cmd &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;PID&amp;gt;
&lt;span class="c"&gt;#   VSZ      RSS   CMD&lt;/span&gt;
&lt;span class="c"&gt;# 2415616   85320  python app.py   → reservou ~2.4 GB virtual, usa ~85 MB físico&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Copy-on-Write: por que fork() é barato
&lt;/h2&gt;

&lt;p&gt;Este é, talvez, o conceito de memória virtual com &lt;strong&gt;maior impacto direto&lt;/strong&gt; em backend, especialmente no mundo Python.&lt;/p&gt;

&lt;p&gt;Quando um processo faz &lt;code&gt;fork()&lt;/code&gt;, o kernel precisa criar um filho com uma cópia do espaço de endereçamento do pai. Copiar gigabytes de memória a cada &lt;code&gt;fork()&lt;/code&gt; seria proibitivo. A solução é o &lt;strong&gt;Copy-on-Write (COW)&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Logo após fork():

  Pai  ──┐
         ├──▶ [Página compartilhada, marcada READ-ONLY na PTE de ambos]
  Filho ─┘

Quando um deles TENTA ESCREVER:
  1. A escrita numa página read-only dispara um page fault
  2. O kernel percebe que é COW (não uma violação real)
  3. Copia a página para um novo frame
  4. Marca a cópia como writable para quem escreveu
  5. Retoma a execução

  Pai  ──▶ [Página original]
  Filho ──▶ [Cópia privada]     ← só agora houve cópia real
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou seja: pai e filho &lt;strong&gt;compartilham fisicamente&lt;/strong&gt; todas as páginas até que uma delas seja modificada. Só as páginas efetivamente escritas são copiadas. Páginas apenas lidas (como o código) nunca são copiadas.&lt;/p&gt;

&lt;h3&gt;
  
  
  O impacto em workers pre-forked (Python/Gunicorn/uWSGI)
&lt;/h3&gt;

&lt;p&gt;Servidores Python usam o modelo pre-fork: um processo master carrega a aplicação e faz &lt;code&gt;fork()&lt;/code&gt; de vários workers. Em teoria, graças ao COW, os workers &lt;strong&gt;compartilham&lt;/strong&gt; toda a memória do master (código, dados carregados no import), consumindo pouca RAM adicional cada.&lt;/p&gt;

&lt;p&gt;Na prática, há uma armadilha traiçoeira: o &lt;strong&gt;reference counting&lt;/strong&gt; do CPython. Toda vez que o Python toca um objeto — mesmo só para &lt;strong&gt;ler&lt;/strong&gt; — ele incrementa e decrementa o campo &lt;code&gt;ob_refcnt&lt;/code&gt; na struct do objeto. Isso é uma &lt;strong&gt;escrita&lt;/strong&gt; na página onde o objeto vive, o que &lt;strong&gt;quebra o COW&lt;/strong&gt;: a página é copiada, mesmo que você só quisesse ler o objeto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Antipadrão para COW em Python pre-forked:
# um objeto grande no master, "só de leitura" nos workers.
&lt;/span&gt;
&lt;span class="n"&gt;CONFIG_GIGANTE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;carregar_dados_enormes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;# no master, antes do fork
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Mesmo APENAS LENDO, o refcount de CONFIG_GIGANTE (e dos objetos
&lt;/span&gt;    &lt;span class="c1"&gt;# internos tocados) é incrementado/decrementado → escrita na página
&lt;/span&gt;    &lt;span class="c1"&gt;# → COW dispara → a página "compartilhada" é copiada para o worker.
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;CONFIG_GIGANTE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chave&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O resultado é o famoso fenômeno de workers Python que &lt;strong&gt;incham com o tempo&lt;/strong&gt;: memória que deveria ser compartilhada acaba sendo copiada worker a worker. Estratégias de mitigação (usar estruturas fora do heap gerenciado, como arrays NumPy ou &lt;code&gt;mmap&lt;/code&gt;, mover dados para um processo/serviço externo como Redis) serão discutidas na Parte IV, junto com a conexão CPython.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
O &lt;code&gt;gc.freeze()&lt;/code&gt; (Python 3.7+) move os objetos existentes para uma geração "permanente" antes do fork, evitando que o garbage collector toque os headers deles e quebre o COW. É uma otimização valiosa para workers pre-forked.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Zero-fill on Demand
&lt;/h2&gt;

&lt;p&gt;Quando seu programa pede memória nova (via &lt;code&gt;malloc&lt;/code&gt;/&lt;code&gt;brk&lt;/code&gt;/&lt;code&gt;mmap&lt;/code&gt; anônimo), o kernel &lt;strong&gt;não&lt;/strong&gt; aloca frames físicos imediatamente. Ele apenas registra que aquela região virtual existe. Mais que isso: por segurança, memória nova precisa ser zerada (senão você leria lixo — possivelmente dados sensíveis de outro processo).&lt;/p&gt;

&lt;p&gt;O kernel otimiza isso com uma página especial: a &lt;strong&gt;zero page&lt;/strong&gt;. Todas as páginas anônimas recém-alocadas são inicialmente mapeadas (read-only) para uma &lt;strong&gt;única&lt;/strong&gt; página física preenchida com zeros:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;malloc(1 GB):
  - Nenhum frame físico é alocado ainda.
  - Todas as páginas virtuais apontam (read-only) para a MESMA zero page.
  - RSS quase não muda!

Primeira ESCRITA em uma página:
  - Page fault (COW sobre a zero page)
  - Aí sim o kernel aloca um frame real, zerado, e o torna privado/writable.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conexão backend&lt;/strong&gt;: é por isso que &lt;code&gt;malloc&lt;/code&gt; de 1 GB retorna instantaneamente e o RSS mal se move. A memória só é "materializada" quando &lt;strong&gt;escrita&lt;/strong&gt; pela primeira vez. Isso explica por que ferramentas de monitoramento mostram um processo "alocando" muita memória virtual mas com RSS baixo — e por que o consumo real de RAM cresce conforme a aplicação &lt;strong&gt;usa&lt;/strong&gt; (escreve) a memória, não quando a &lt;strong&gt;reserva&lt;/strong&gt;. Isso é o &lt;em&gt;overcommit&lt;/em&gt; de memória, e é também por que o OOM Killer (Artigo 3) pode aparecer "do nada": o kernel prometeu mais memória do que tem, apostando que nem todos vão escrever tudo.&lt;/p&gt;




&lt;h2&gt;
  
  
  O Memory Layout de um processo real
&lt;/h2&gt;

&lt;p&gt;Vamos juntar tudo e olhar como um processo Linux organiza seu espaço virtual. Aqueles "segmentos" lógicos da Parte II aparecem aqui, implementados sobre paginação:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F66anbgc0jrkeml73qzbn.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F66anbgc0jrkeml73qzbn.jpeg" alt="Layout Básico de Memoria" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Text&lt;/strong&gt;: o código executável. Read-only e compartilhável entre processos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data / BSS&lt;/strong&gt;: variáveis globais. Data tem valores iniciais no binário; BSS é zero-fill on demand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heap&lt;/strong&gt;: cresce "para cima" via &lt;code&gt;brk&lt;/code&gt;/&lt;code&gt;sbrk&lt;/code&gt;. É onde vivem os objetos dinâmicos (o heap do &lt;code&gt;malloc&lt;/code&gt;, do CPython, do CLR, do Go).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mmap region&lt;/strong&gt;: onde ficam bibliotecas compartilhadas, arquivos mapeados e grandes alocações (allocadores modernos usam &lt;code&gt;mmap&lt;/code&gt; para blocos grandes em vez de &lt;code&gt;brk&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stack&lt;/strong&gt;: cresce "para baixo". Cada thread tem sua própria stack.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Lendo /proc/pid/maps
&lt;/h3&gt;

&lt;p&gt;O Linux expõe esse layout num arquivo legível. É uma das ferramentas de diagnóstico de memória mais úteis que existem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/self/maps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;55a3c0000000-55a3c0021000 r--p 00000000 08:01 1311  /usr/bin/python3.11   ← text (read-only)
55a3c0021000-55a3c0200000 r-xp 00021000 08:01 1311  /usr/bin/python3.11   ← código executável
55a3c0400000-55a3c0500000 rw-p 00000000 00:00 0                           ← heap (anônimo, rw)
7f2a4c000000-7f2a4c021000 r-xp 00000000 08:01 2201  /lib/libc.so.6        ← libc mapeada
7ffe1a3c0000-7ffe1a3e1000 rw-p 00000000 00:00 0     [stack]               ← stack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada linha é uma &lt;strong&gt;VMA (Virtual Memory Area)&lt;/strong&gt; — uma região contígua com as mesmas permissões. As colunas de permissão (&lt;code&gt;r&lt;/code&gt;, &lt;code&gt;w&lt;/code&gt;, &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;p&lt;/code&gt;=private/&lt;code&gt;s&lt;/code&gt;=shared) refletem exatamente os bits de proteção das PTEs que vimos na Parte II. Para detalhamento de uso físico por região, existe &lt;code&gt;/proc/pid/smaps&lt;/code&gt; (mostra RSS, PSS, dirty pages por VMA) — voltaremos a ele nas técnicas de diagnóstico da Parte IV.&lt;/p&gt;

&lt;h3&gt;
  
  
  mmap: o canivete suíço da memória
&lt;/h3&gt;

&lt;p&gt;A syscall &lt;code&gt;mmap&lt;/code&gt; mapeia algo diretamente no espaço de endereçamento do processo. Ela é fundamental e aparece em todo lugar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;mmap anônimo&lt;/strong&gt;: aloca memória "pura" (é o que &lt;code&gt;malloc&lt;/code&gt; usa para blocos grandes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mmap de arquivo&lt;/strong&gt;: mapeia um arquivo na memória, permitindo lê-lo/escrevê-lo como se fosse um array — o kernel cuida do I/O via page faults e page cache (tema central do Artigo 3).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mmap compartilhado&lt;/strong&gt;: várias instâncias mapeiam a mesma região → memória compartilhada entre processos.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mmap&lt;/span&gt;

&lt;span class="c1"&gt;# Mapeia um arquivo grande sem carregá-lo inteiro na RAM.
# O acesso dispara page faults que trazem só as páginas tocadas.
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dados_gigantes.bin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fileno&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prot&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROT_READ&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;primeiro_registro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mm&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# só esta página é trazida do disco
&lt;/span&gt;    &lt;span class="n"&gt;mm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada linguagem tem seu equivalente: &lt;code&gt;MemoryMappedFile&lt;/code&gt; no .NET, &lt;code&gt;syscall.Mmap&lt;/code&gt;/&lt;code&gt;mmap&lt;/code&gt; no Go. Exploraremos esses padrões no Artigo 3, sobre page cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  ASLR
&lt;/h3&gt;

&lt;p&gt;O &lt;strong&gt;ASLR (Address Space Layout Randomization)&lt;/strong&gt; é uma defesa de segurança: a cada execução, o kernel &lt;strong&gt;randomiza&lt;/strong&gt; as posições da stack, do heap, das bibliotecas e (com PIE) do próprio código. Isso dificulta ataques que dependem de saber endereços fixos (como ROP/return-to-libc).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Status global do ASLR (2 = totalmente ativado, o padrão)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/sys/kernel/randomize_va_space

&lt;span class="c"&gt;# Rode 'cat /proc/self/maps | grep stack' duas vezes:&lt;/span&gt;
&lt;span class="c"&gt;# o endereço da [stack] muda a cada execução → ASLR em ação.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kernels 6.x trouxeram melhorias na entropia e granularidade do ASLR. Para backend, o impacto é quase sempre transparente — mas vale saber que ele existe quando você vê endereços diferentes a cada boot da aplicação, e que &lt;strong&gt;desabilitá-lo&lt;/strong&gt; (algo que às vezes se faz para debugging) reduz a segurança.&lt;/p&gt;




&lt;h2&gt;
  
  
  Paging e Swapping modernos
&lt;/h2&gt;

&lt;p&gt;Fechamos com o swapping — mas agora, com paginação, ele é &lt;strong&gt;muito&lt;/strong&gt; mais refinado que o swapping clássico de processos inteiros da Parte I. O kernel move &lt;strong&gt;páginas individuais&lt;/strong&gt; entre a RAM e o disco, não processos inteiros.&lt;/p&gt;

&lt;p&gt;Quando a memória fica escassa, o kernel escolhe páginas "frias" (pouco usadas — lembra dos bits Accessed/Dirty?) e as move para a área de &lt;strong&gt;swap&lt;/strong&gt; no disco, liberando frames. Se a página for acessada depois, um &lt;strong&gt;major fault&lt;/strong&gt; a traz de volta. Os algoritmos que decidem &lt;strong&gt;quais&lt;/strong&gt; páginas remover são um tópico rico — e são exatamente o assunto de abertura do Artigo 3 (algoritmos de substituição, working set, thrashing).&lt;/p&gt;

&lt;h3&gt;
  
  
  Páginas anônimas vs file-backed
&lt;/h3&gt;

&lt;p&gt;Uma distinção que o kernel faz o tempo todo, e que você precisa conhecer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Páginas file-backed&lt;/strong&gt;: têm uma "casa" no disco — um arquivo. Código, bibliotecas, arquivos mapeados. Quando o kernel precisa liberá-las, se estiverem limpas (não modificadas), basta &lt;strong&gt;descartá-las&lt;/strong&gt; — o conteúdo pode ser relido do arquivo original quando necessário. Não precisam de swap.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Páginas anônimas&lt;/strong&gt;: não têm arquivo de origem — heap, stack, memória de &lt;code&gt;malloc&lt;/code&gt;. Elas &lt;strong&gt;não têm para onde voltar&lt;/strong&gt; a não ser o &lt;strong&gt;swap&lt;/strong&gt;. Se não há swap configurado e a memória acaba, o kernel não tem como liberá-las → é aí que o &lt;strong&gt;OOM Killer&lt;/strong&gt; entra em cena.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ver uso de swap do sistema&lt;/span&gt;
free &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;span class="c"&gt;#               total   usado   livre   compart.  buff/cache&lt;/span&gt;
&lt;span class="c"&gt;# Mem:          15Gi    8Gi     1Gi     200Mi     6Gi&lt;/span&gt;
&lt;span class="c"&gt;# Swap:         4Gi     512Mi   3Gi&lt;/span&gt;

&lt;span class="c"&gt;# Swap por processo (VmSwap)&lt;/span&gt;
&lt;span class="nb"&gt;grep &lt;/span&gt;VmSwap /proc/&amp;lt;PID&amp;gt;/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O parâmetro &lt;code&gt;vm.swappiness&lt;/code&gt; controla o quão agressivamente o kernel prefere fazer swap de páginas anônimas versus descartar páginas de cache — mas esse é um assunto de tuning que aprofundaremos no Artigo 3, junto com memory reclaim e pressão de memória.&lt;/p&gt;

&lt;p&gt;Kernels &lt;strong&gt;5.14+&lt;/strong&gt; trouxeram melhorias importantes no swap para dispositivos rápidos (SSD/NVMe), tornando o swap muito menos "assustador" do que era na era dos HDDs — em SSD, o swap pode ser uma ferramenta legítima de gestão de memória, não apenas um último recurso.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-iv-kd7"&gt;&lt;strong&gt;Continua na Parte IV&lt;/strong&gt;&lt;/a&gt;, onde fechamos o Artigo 2 com &lt;strong&gt;Memory Zones e NUMA&lt;/strong&gt;, a conexão profunda com os runtimes de &lt;strong&gt;Python, .NET e Go&lt;/strong&gt;, técnicas de diagnóstico (&lt;code&gt;/proc/pid/smaps&lt;/code&gt;, &lt;code&gt;pmap&lt;/code&gt;, &lt;code&gt;perf&lt;/code&gt;, &lt;code&gt;numastat&lt;/code&gt;), boas práticas e estudos de caso reais.&lt;br&gt;
&lt;strong&gt;:D&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conteúdo parcialmente gerado com auxílio de IA generativa (me ajudou a organizar tudo isso kkkk)&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referências Bibliográficas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Desenvolvimento-do-Kernel-Linux/dp/8573933410" rel="noopener noreferrer"&gt;Desenvolvimento Do Kernel Do Linux - David Cram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/gorman/html/understand/" rel="noopener noreferrer"&gt;Understanding the Linux Virtual Memory Manager - Mel Gorman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://instagram-engineering.com/dismissing-python-garbage-collection-at-instagram-4dca40b29172" rel="noopener noreferrer"&gt;Copy-on-Write e gc.freeze() - Instagram Engineering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Sistemas-Operacionais-Modernos-Andrew-Tanenbaum/dp/8582606168" rel="noopener noreferrer"&gt;Sistemas Operacionais Modernos - Tanenbaum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>dotnet</category>
      <category>go</category>
      <category>python</category>
    </item>
    <item>
      <title>Kernel Linux para Desenvolvedores Backend - Gerenciamento de Memória Parte II</title>
      <dc:creator>Alex Volnei Galante</dc:creator>
      <pubDate>Fri, 21 Aug 2026 00:41:37 +0000</pubDate>
      <link>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-ii-3ooi</link>
      <guid>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-ii-3ooi</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
Este artigo é a continuação da &lt;strong&gt;Parte I&lt;/strong&gt;, onde estabelecemos a hierarquia de memória, a evolução do gerenciamento sem abstração, o conceito de espaço de endereçamento e a introdução à MMU. Se você ainda não leu, recomendo começar por lá.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Sumário
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Paginação: a ideia central

&lt;ul&gt;
&lt;li&gt;Páginas e Frames&lt;/li&gt;
&lt;li&gt;Anatomia de um endereço virtual&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
A Tabela de Páginas

&lt;ul&gt;
&lt;li&gt;O formato de uma entrada (PTE)&lt;/li&gt;
&lt;li&gt;O problema do tamanho&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Tabelas de Páginas Multinível

&lt;ul&gt;
&lt;li&gt;A hierarquia do Linux: PGD, PUD, PMD, PTE&lt;/li&gt;
&lt;li&gt;4 e 5 níveis: endereçamento de 48 e 57 bits&lt;/li&gt;
&lt;li&gt;Alternativas: tabelas invertidas e hashed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
A TLB: Translation Lookaside Buffer

&lt;ul&gt;
&lt;li&gt;Cálculo do tempo efetivo de acesso&lt;/li&gt;
&lt;li&gt;TLB e context switch: flush vs ASID&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Huge Pages e THP&lt;/li&gt;
&lt;li&gt;Segmentação&lt;/li&gt;
&lt;li&gt;Referências Bibliográficas&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Paginação: a ideia central
&lt;/h2&gt;

&lt;p&gt;Na Parte I vimos que o swapping clássico tinha um defeito fatal: sua granularidade grosseira, movendo processos inteiros entre RAM e disco. A &lt;strong&gt;paginação&lt;/strong&gt; resolve isso quebrando a memória em pedaços pequenos e de tamanho fixo, permitindo mover, mapear e proteger memória com granularidade fina.&lt;/p&gt;

&lt;h3&gt;
  
  
  Páginas e Frames
&lt;/h3&gt;

&lt;p&gt;A paginação divide os dois mundos em unidades de mesmo tamanho:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O &lt;strong&gt;espaço virtual&lt;/strong&gt; é dividido em &lt;strong&gt;páginas&lt;/strong&gt; (pages).&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;memória física&lt;/strong&gt; é dividida em &lt;strong&gt;frames&lt;/strong&gt; (page frames).&lt;/li&gt;
&lt;li&gt;Página e frame têm o &lt;strong&gt;mesmo tamanho&lt;/strong&gt; — no x86-64, o padrão é &lt;strong&gt;4 KB&lt;/strong&gt; (4096 bytes).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A tarefa da paginação é manter um mapeamento: &lt;strong&gt;qual página virtual está em qual frame físico&lt;/strong&gt;. Como página e frame têm o mesmo tamanho, esse mapeamento é uma simples tabela de correspondência.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftaykealyr956399sdow7.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftaykealyr956399sdow7.jpeg" alt="Mapeamento de Memoria" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Anatomia de um endereço virtual
&lt;/h3&gt;

&lt;p&gt;A grande sacada da paginação é como um endereço virtual é interpretado. Ele é dividido em duas partes: o &lt;strong&gt;número da página&lt;/strong&gt; e o &lt;strong&gt;offset&lt;/strong&gt; dentro da página.&lt;/p&gt;

&lt;p&gt;Com páginas de 4 KB ($2^{12}$ bytes), os &lt;strong&gt;12 bits mais baixos&lt;/strong&gt; do endereço são o offset (0 a 4095), e os bits restantes identificam a página:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Endereço virtual de 48 bits (x86-64 típico):

 47                                  12 11                    0
┌──────────────────────────────────────┬───────────────────────┐
│         Número da Página (36 bits)   │    Offset (12 bits)   │
└──────────────────────────────────────┴───────────────────────┘

Tradução:
  - Número da página → consulta tabela → Número do Frame (PFN)
  - Offset → copiado DIRETAMENTE, sem tradução
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O detalhe elegante: o &lt;strong&gt;offset não é traduzido&lt;/strong&gt;. Se um endereço virtual está a 100 bytes do início de sua página, o endereço físico correspondente está a 100 bytes do início do frame. Só o número da página passa pela tradução. Isso simplifica enormemente o hardware.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Tabela de Páginas
&lt;/h2&gt;

&lt;p&gt;A estrutura que guarda o mapeamento página → frame é a &lt;strong&gt;tabela de páginas&lt;/strong&gt; (page table). Cada processo tem a sua própria — é isso que garante que o endereço virtual &lt;code&gt;0x1000&lt;/code&gt; do processo A e do processo B apontem para frames físicos diferentes.&lt;/p&gt;

&lt;h3&gt;
  
  
  O formato de uma entrada (PTE)
&lt;/h3&gt;

&lt;p&gt;Cada entrada da tabela de páginas — a &lt;strong&gt;PTE (Page Table Entry)&lt;/strong&gt; — contém muito mais que apenas o número do frame. Ela carrega bits de controle que o hardware e o kernel usam constantemente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Estrutura simplificada de uma PTE no x86-64:

 63    62         52 51            12 11        9 8 7 6 5 4 3 2 1 0
┌──┬─────────────────┬───────────────┬──────────┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│NX│  reservado      │  PFN (frame)  │  livre   │G│P│D│A│C│W│U│W│P│P│
└──┴─────────────────┴───────────────┴──────────┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘

Bits de controle essenciais:
  P  (Present)     → a página está na RAM? (0 = page fault ao acessar)
  W  (Writable)    → pode escrever? (proteção de escrita)
  U  (User)        → acessível em user mode? (proteção kernel/user)
  A  (Accessed)    → foi referenciada? (usado por algoritmos de reposição)
  D  (Dirty)       → foi modificada? (precisa gravar no disco antes de descartar)
  NX (No eXecute)  → impede execução de código (proteção contra exploits)
  PFN              → o Page Frame Number: qual frame físico
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esses bits não são detalhes acadêmicos — eles são a base de mecanismos que impactam produção diretamente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O bit &lt;strong&gt;Present&lt;/strong&gt; é o que dispara &lt;strong&gt;page faults&lt;/strong&gt; e viabiliza demand paging e swapping (Parte III).&lt;/li&gt;
&lt;li&gt;O bit &lt;strong&gt;Writable&lt;/strong&gt; é o que torna possível o &lt;strong&gt;Copy-on-Write&lt;/strong&gt; no &lt;code&gt;fork()&lt;/code&gt; (Parte III).&lt;/li&gt;
&lt;li&gt;Os bits &lt;strong&gt;Accessed&lt;/strong&gt; e &lt;strong&gt;Dirty&lt;/strong&gt; alimentam os &lt;strong&gt;algoritmos de reposição de página&lt;/strong&gt; (Parte V do Artigo 3).&lt;/li&gt;
&lt;li&gt;O bit &lt;strong&gt;NX&lt;/strong&gt; é uma defesa de segurança fundamental contra ataques de execução de código na stack/heap.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  O problema do tamanho
&lt;/h3&gt;

&lt;p&gt;Aqui surge um problema sério. Imagine uma tabela de páginas "plana" (single-level) para um espaço de 48 bits com páginas de 4 KB:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Número de páginas: $2^{48} / 2^{12} = 2^{36}$ páginas.&lt;/li&gt;
&lt;li&gt;Se cada PTE ocupa 8 bytes: $2^{36} \times 8 = 2^{39}$ bytes = &lt;strong&gt;512 GB&lt;/strong&gt; de tabela de páginas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Por processo!&lt;/strong&gt; Isso é obviamente inviável. E o pior: a maioria dos processos usa apenas uma fração ínfima do seu espaço virtual, então a tabela seria quase toda preenchida com entradas vazias. Precisamos de algo mais inteligente.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tabelas de Páginas Multinível
&lt;/h2&gt;

&lt;p&gt;A solução é não alocar a tabela inteira de uma vez, mas organizá-la em uma &lt;strong&gt;hierarquia de níveis&lt;/strong&gt;, alocando apenas os pedaços realmente usados. É o mesmo princípio de um índice de livro com capítulos e seções: você não lista todas as páginas, mas navega por níveis.&lt;/p&gt;

&lt;h3&gt;
  
  
  A hierarquia do Linux: PGD, PUD, PMD, PTE
&lt;/h3&gt;

&lt;p&gt;O Linux implementa uma tabela de páginas de &lt;strong&gt;4 níveis&lt;/strong&gt; (no x86-64 clássico). O endereço virtual é fatiado em vários campos, cada um indexando um nível:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Endereço virtual de 48 bits, fatiado em 4 níveis + offset:

 47      39 38      30 29      21 20      12 11         0
┌──────────┬──────────┬──────────┬──────────┬────────────┐
│  PGD idx │  PUD idx │  PMD idx │  PTE idx │   offset   │
│ (9 bits) │ (9 bits) │ (9 bits) │ (9 bits) │ (12 bits)  │
└──────────┴──────────┴──────────┴──────────┴────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os nomes usados no código-fonte do kernel Linux:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PGD&lt;/strong&gt; — Page Global Directory (nível mais alto; o registrador &lt;code&gt;CR3&lt;/code&gt; aponta para ele)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PUD&lt;/strong&gt; — Page Upper Directory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PMD&lt;/strong&gt; — Page Middle Directory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PTE&lt;/strong&gt; — Page Table Entry (nível mais baixo, aponta para o frame físico)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O processo de tradução (o "page table walk") segue os níveis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CR3 ──▶ [PGD] ──idx──▶ [PUD] ──idx──▶ [PMD] ──idx──▶ [PTE] ──▶ Frame físico
         │                                                          │
         └── cada nível tem 512 entradas (2^9)                      └── + offset = endereço físico
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A economia é brutal: um processo que usa poucos MB de memória precisa de apenas alguns níveis alocados, não os 512 GB da tabela plana. Os ramos não utilizados simplesmente não existem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O custo?&lt;/strong&gt; Cada acesso à memória agora exige &lt;strong&gt;4 acessos adicionais&lt;/strong&gt; à RAM (um por nível) só para traduzir o endereço. Um único &lt;code&gt;mov&lt;/code&gt; do seu programa pode virar 5 acessos à memória. É exatamente esse custo que a TLB, que veremos a seguir, existe para eliminar.&lt;/p&gt;

&lt;h3&gt;
  
  
  4 e 5 níveis: endereçamento de 48 e 57 bits
&lt;/h3&gt;

&lt;p&gt;Por muito tempo, 48 bits de endereçamento virtual (256 TB) foram mais que suficientes. Mas servidores com muitos terabytes de RAM e workloads como bancos de dados in-memory começaram a esbarrar nesse teto.&lt;/p&gt;

&lt;p&gt;A partir do &lt;strong&gt;Kernel 4.14&lt;/strong&gt; (e amplamente disponível nos kernels 5.x), o Linux passou a suportar &lt;strong&gt;tabelas de páginas de 5 níveis&lt;/strong&gt;, adicionando um nível chamado &lt;strong&gt;P4D&lt;/strong&gt; entre o PGD e o PUD:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5 níveis → endereçamento virtual de 57 bits → 128 PB de espaço virtual

 56    48 47    39 38    30 29    21 20    12 11      0
┌────────┬────────┬────────┬────────┬────────┬──────────┐
│ PGD    │ P4D    │ PUD    │ PMD    │ PTE    │  offset  │
└────────┴────────┴────────┴────────┴────────┴──────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para a maioria das aplicações backend isso é transparente — você não muda uma linha de código. Mas se você opera bancos de dados gigantes, caches in-memory de múltiplos terabytes ou máquinas com RAM massiva, é o kernel removendo um teto que antes existia.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alternativas: tabelas invertidas e hashed
&lt;/h3&gt;

&lt;p&gt;O modelo multinível é o dominante, mas vale conhecer as alternativas que Tanenbaum discute:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tabela de páginas invertida&lt;/strong&gt;: em vez de uma entrada por página virtual (que pode ser enorme), há uma entrada por &lt;strong&gt;frame físico&lt;/strong&gt;. O tamanho passa a ser proporcional à RAM, não ao espaço virtual. A desvantagem é que a busca vira uma pesquisa (geralmente via hash), não uma indexação direta. Usada em arquiteturas como PowerPC e Itanium.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hashed page tables&lt;/strong&gt;: usam uma função hash sobre o número da página virtual para encontrar a entrada. Comuns em espaços de endereçamento esparsos de 64 bits.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O x86-64, arquitetura da esmagadora maioria dos servidores, usa o modelo &lt;strong&gt;multinível hierárquico&lt;/strong&gt; que detalhamos acima.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Gosto de pensar no Tanenbaum como cara com mesmo senso crítico que Albert Einstein, que não se contenta com o que é popular, mas sim com o que é elegante e eficiente. Ele discute alternativas, mas o x86-64 escolheu a hierarquia multinível por ser simples e eficiente na prática." Alex Volnei Galante (Eu mesmo kkkk)&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  A TLB: Translation Lookaside Buffer
&lt;/h2&gt;

&lt;p&gt;Se cada acesso à memória exige um page table walk de 4 ou 5 acessos adicionais à RAM, a paginação seria proibitivamente lenta. A solução é um cache dedicado a traduções: a &lt;strong&gt;TLB (Translation Lookaside Buffer)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A TLB é um cache pequeno e ultrarrápido, &lt;strong&gt;dentro da MMU&lt;/strong&gt;, que guarda as traduções página→frame usadas recentemente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Acesso à memória com TLB:

  endereço virtual
        │
        ▼
   ┌─────────┐   TLB hit (~1 ciclo)
   │   TLB   │─────────────────────▶ endereço físico   ✔ rápido!
   └─────────┘
        │ TLB miss
        ▼
   ┌─────────────┐
   │ Page Table  │  walk de 4-5 níveis (~100+ ciclos)
   │   Walk      │─────────────────────▶ endereço físico + preenche TLB
   └─────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Graças à localidade (de novo ela!), a TLB tem uma &lt;strong&gt;taxa de acerto (hit ratio)&lt;/strong&gt; altíssima na prática — tipicamente acima de 99%. A maioria dos acessos nunca chega a fazer o walk completo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cálculo do tempo efetivo de acesso
&lt;/h3&gt;

&lt;p&gt;Vale fazer a conta para sentir o impacto. Suponha:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Acesso à RAM: 100 ns&lt;/li&gt;
&lt;li&gt;TLB hit: adiciona ~0 ns (acontece em paralelo)&lt;/li&gt;
&lt;li&gt;TLB miss: exige um page walk que custa, digamos, 4 acessos à RAM = 400 ns extras&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A lição prática: &lt;strong&gt;TLB misses são caros&lt;/strong&gt;. Workloads que "pulam" por muita memória de forma aleatória (bancos de dados percorrendo índices grandes, por exemplo) sofrem com TLB misses. É por isso que huge pages, que veremos adiante, podem dar ganhos expressivos.&lt;/p&gt;

&lt;h3&gt;
  
  
  TLB e context switch: flush vs ASID
&lt;/h3&gt;

&lt;p&gt;Aqui está um ponto que conecta diretamente com o Artigo 1 (context switching). Cada processo tem sua própria tabela de páginas, então o endereço virtual &lt;code&gt;0x1000&lt;/code&gt; significa coisas diferentes para processos diferentes. O que acontece com a TLB quando o kernel troca de processo?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Abordagem ingênua (flush)&lt;/strong&gt;: esvaziar a TLB inteira a cada context switch. Correto, mas caro — o novo processo começa com a TLB "fria" e sofre uma rajada de TLB misses até reaquecê-la. Esse é um dos &lt;strong&gt;custos indiretos do context switch&lt;/strong&gt; que mencionamos no Artigo 1.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Abordagem moderna (ASID/PCID)&lt;/strong&gt;: cada entrada da TLB é etiquetada com um identificador de espaço de endereçamento — &lt;strong&gt;ASID&lt;/strong&gt; (Address Space ID) na terminologia genérica, &lt;strong&gt;PCID&lt;/strong&gt; (Process Context ID) no x86-64. Assim, entradas de processos diferentes coexistem na TLB sem conflito, e o flush deixa de ser necessário na maioria dos switches.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Isso reforça uma ideia da série: aplicações com &lt;strong&gt;muitos context switches&lt;/strong&gt; (por exemplo, muitas threads competindo, ou muito chaveamento entre goroutines que bloqueiam em syscalls) pagam um custo em memória, não só em CPU. Menos switches = TLB mais quente = menos latência.&lt;/p&gt;




&lt;h2&gt;
  
  
  Huge Pages e THP
&lt;/h2&gt;

&lt;p&gt;Se páginas de 4 KB geram pressão sobre a TLB (cada entrada cobre só 4 KB), a solução é usar &lt;strong&gt;páginas maiores&lt;/strong&gt;. O x86-64 suporta:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Páginas de 2 MB&lt;/strong&gt; (usando um nível a menos no walk — o PMD aponta direto para o frame)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Páginas de 1 GB&lt;/strong&gt; (o PUD aponta direto para o frame)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Uma única entrada de TLB para uma página de 2 MB cobre o que exigiria &lt;strong&gt;512 entradas&lt;/strong&gt; de 4 KB. Para workloads com grande footprint de memória, isso reduz drasticamente os TLB misses.&lt;/p&gt;

&lt;p&gt;Há duas formas de usá-las no Linux:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HugeTLB (explícito)&lt;/strong&gt;: você reserva páginas grandes antecipadamente e a aplicação as requisita explicitamente. É o modelo usado por bancos de dados como Oracle e PostgreSQL (via &lt;code&gt;huge_pages&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Transparent Huge Pages (THP)&lt;/strong&gt;: o kernel tenta usar huge pages &lt;strong&gt;automaticamente&lt;/strong&gt;, promovendo regiões de 4 KB para 2 MB quando possível, sem intervenção da aplicação.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Verificar status do THP no sistema&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/kernel/mm/transparent_hugepage/enabled
&lt;span class="c"&gt;# [always] madvise never&lt;/span&gt;

&lt;span class="c"&gt;# Ver quanta memória está em huge pages&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; huge /proc/meminfo
&lt;span class="c"&gt;# AnonHugePages:    524288 kB&lt;/span&gt;
&lt;span class="c"&gt;# HugePages_Total:       0&lt;/span&gt;
&lt;span class="c"&gt;# HugePages_Free:        0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ &lt;strong&gt;Cuidado em produção&lt;/strong&gt;: THP nem sempre é benéfico. Muitos bancos de dados (Redis, MongoDB, PostgreSQL) &lt;strong&gt;recomendam desabilitar&lt;/strong&gt; o THP, porque a promoção/compactação de páginas em background pode causar picos de latência imprevisíveis e fragmentação. A recomendação comum é usar &lt;code&gt;madvise&lt;/code&gt; (só onde a aplicação pede via &lt;code&gt;madvise(MADV_HUGEPAGE)&lt;/code&gt;) em vez de &lt;code&gt;always&lt;/code&gt;. Voltaremos a huge pages nas boas práticas da Parte IV.&lt;/p&gt;




&lt;h2&gt;
  
  
  Segmentação
&lt;/h2&gt;

&lt;p&gt;Antes de fechar, vale entender a &lt;strong&gt;segmentação&lt;/strong&gt; — uma abordagem alternativa (e complementar) à paginação, que Tanenbaum trata em detalhe.&lt;/p&gt;

&lt;p&gt;Enquanto a paginação divide a memória em pedaços de tamanho &lt;strong&gt;fixo e sem significado semântico&lt;/strong&gt; (uma página é só 4 KB de bytes), a segmentação divide o espaço de endereçamento em unidades &lt;strong&gt;lógicas de tamanho variável&lt;/strong&gt; que correspondem à estrutura do programa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visão segmentada de um processo:

┌──────────────────┐  Segmento 0: código (text)
├──────────────────┤  Segmento 1: dados globais (data)
├──────────────────┤  Segmento 2: heap
├──────────────────┤  Segmento 3: stack
└──────────────────┘

Cada segmento tem base, limite e permissões próprias.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A vantagem da segmentação é semântica: cada segmento pode ter proteções próprias (código = read-only + executável; stack = read-write + no-execute) e crescer independentemente. A desvantagem é que segmentos de tamanho variável trazem de volta o fantasma da &lt;strong&gt;fragmentação externa&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Segmentação vs Paginação
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspecto&lt;/th&gt;
&lt;th&gt;Paginação&lt;/th&gt;
&lt;th&gt;Segmentação&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tamanho da unidade&lt;/td&gt;
&lt;td&gt;Fixo (4 KB)&lt;/td&gt;
&lt;td&gt;Variável&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fragmentação&lt;/td&gt;
&lt;td&gt;Interna&lt;/td&gt;
&lt;td&gt;Externa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visão do programador&lt;/td&gt;
&lt;td&gt;Transparente&lt;/td&gt;
&lt;td&gt;Explícita (às vezes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proteção&lt;/td&gt;
&lt;td&gt;Por página&lt;/td&gt;
&lt;td&gt;Por segmento (semântica)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Segmentação paginada (x86)
&lt;/h3&gt;

&lt;p&gt;A arquitetura x86 historicamente combinou as duas: &lt;strong&gt;segmentação paginada&lt;/strong&gt;. Um endereço passava primeiro pela segmentação (segmento + offset → endereço linear) e depois pela paginação (endereço linear → físico).&lt;/p&gt;

&lt;p&gt;Na prática, porém, o &lt;strong&gt;x86-64 praticamente aposentou a segmentação&lt;/strong&gt;. No modo de 64 bits, a maioria dos registradores de segmento tem base fixada em 0 e limite "infinito" — ou seja, a segmentação é neutralizada, e o Linux usa essencialmente um &lt;strong&gt;modelo de memória plano baseado só em paginação&lt;/strong&gt;. Os conceitos de "segmentos" que sobrevivem (text, data, heap, stack) são hoje uma organização lógica implementada &lt;strong&gt;sobre paginação&lt;/strong&gt;, não segmentação de hardware. Veremos esse memory layout na próxima parte.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-iii-15b6"&gt;&lt;strong&gt;Continua na Parte III&lt;/strong&gt;&lt;/a&gt;, onde entramos no coração dinâmico da memória virtual: page faults (minor e major), demand paging, o Copy-on-Write que torna o &lt;code&gt;fork()&lt;/code&gt; barato, o memory layout de um processo real (com &lt;code&gt;mmap&lt;/code&gt;, ASLR) e o swapping moderno.&lt;br&gt;
&lt;strong&gt;:D&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conteúdo parcialmente gerado com auxílio de IA generativa (me ajudou a organizar tudo isso kkkk)&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referências Bibliográficas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Desenvolvimento-do-Kernel-Linux/dp/8573933410" rel="noopener noreferrer"&gt;Desenvolvimento Do Kernel Do Linux - David Cram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/gorman/html/understand/" rel="noopener noreferrer"&gt;Understanding the Linux Virtual Memory Manager - Mel Gorman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lwn.net/Articles/717293/" rel="noopener noreferrer"&gt;Five-level page tables - LWN.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Sistemas-Operacionais-Modernos-Andrew-Tanenbaum/dp/8582606168" rel="noopener noreferrer"&gt;Sistemas Operacionais Modernos - Tanenbaum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>dotnet</category>
      <category>go</category>
      <category>python</category>
    </item>
    <item>
      <title>Kernel Linux para Desenvolvedores Backend - Gerenciamento de Memória Parte I</title>
      <dc:creator>Alex Volnei Galante</dc:creator>
      <pubDate>Fri, 21 Aug 2026 00:33:34 +0000</pubDate>
      <link>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-i-o2l</link>
      <guid>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-i-o2l</guid>
      <description>&lt;p&gt;Se você acompanhou a série sobre &lt;strong&gt;Processos &amp;amp; Threads&lt;/strong&gt;, já entende como o kernel do Linux cria a ilusão de que cada processo tem uma CPU só para si. Agora vamos atacar a segunda grande ilusão que o kernel constrói: a de que cada processo tem uma &lt;strong&gt;memória infinita e exclusiva&lt;/strong&gt; só para ele.&lt;/p&gt;

&lt;p&gt;Assim como fizemos com processos, o objetivo aqui não é decorar estruturas do kernel, mas entender &lt;strong&gt;como o código que escrevemos em produção&lt;/strong&gt; — seja em Python, .NET ou Go — interage com o subsistema de gerenciamento de memória, e como esse entendimento nos torna melhores em diagnosticar problemas de performance, consumo de RAM e latência.&lt;/p&gt;




&lt;h2&gt;
  
  
  Motivação
&lt;/h2&gt;

&lt;p&gt;Toda aplicação backend, sem exceção, é limitada por dois recursos fundamentais: &lt;strong&gt;CPU&lt;/strong&gt; e &lt;strong&gt;memória&lt;/strong&gt;. Já cobrimos CPU na série anterior. Agora vamos falar de memória — o recurso que, quando mal compreendido, causa os problemas mais silenciosos e traiçoeiros em produção: memory leaks que derrubam containers no meio da madrugada, o temido OOM Killer matando seu worker sem explicação aparente, latência de cauda (tail latency) inexplicável causada por page faults, e consumo de RAM que "cresce sozinho" em aplicações long-running.&lt;/p&gt;

&lt;p&gt;A verdade é que a maior parte dos desenvolvedores backend trata memória como uma caixa-preta: "aloco um objeto e o garbage collector cuida do resto". Mas essa abstração vaza. "E quando vaza — geralmente às 3h da manhã, com o pager tocando — quem entende o que acontece por baixo tem uma vantagem enorme".&lt;/p&gt;

&lt;p&gt;Meu objetivo com este artigo é destrinchar como a memória virtual funciona no kernel Linux, partindo dos fundamentos teóricos (aqueles que aprendemos, ou deveríamos ter aprendido, na disciplina de Sistemas Operacionais) e conectando cada conceito com o que realmente acontece quando sua API responde a uma requisição.&lt;/p&gt;

&lt;p&gt;Como sempre: estou escrevendo para consolidar meu próprio entendimento, mas se você tem interesse em ir além do CRUD, fique comigo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sumário da Parte I
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Por que memória virtual existe?&lt;/li&gt;
&lt;li&gt;A Hierarquia de Memória&lt;/li&gt;
&lt;li&gt;Gerenciamento de Memória sem Abstração&lt;/li&gt;
&lt;li&gt;A Abstração do Espaço de Endereçamento&lt;/li&gt;
&lt;li&gt;Memória Virtual: os Fundamentos&lt;/li&gt;
&lt;li&gt;A MMU e a Tradução de Endereços&lt;/li&gt;
&lt;li&gt;Referências Bibliográficas&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Por que memória virtual existe?
&lt;/h2&gt;

&lt;p&gt;Antes de mergulhar em tabelas de páginas e MMUs, precisamos entender &lt;strong&gt;qual problema&lt;/strong&gt; a memória virtual resolve. E, como quase tudo em sistemas operacionais, a resposta está na história.&lt;/p&gt;

&lt;p&gt;Imagine um mundo sem memória virtual — que, aliás, foi o mundo real dos computadores até os anos 60. Nesse mundo, quando seu programa acessa o endereço &lt;code&gt;0x1000&lt;/code&gt;, ele acessa &lt;strong&gt;fisicamente&lt;/strong&gt; a posição &lt;code&gt;0x1000&lt;/code&gt; do chip de RAM. Simples, direto e cheio de problemas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sem proteção&lt;/strong&gt;: qualquer processo pode ler e escrever na memória de qualquer outro processo. Um bug em um programa pode corromper outro. Um programa malicioso pode ler suas senhas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sem isolamento&lt;/strong&gt;: dois programas compilados para rodar no endereço &lt;code&gt;0x1000&lt;/code&gt; não podem coexistir.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limitação física rígida&lt;/strong&gt;: você não pode executar um programa que precise de mais memória do que a RAM instalada.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fragmentação&lt;/strong&gt;: conforme processos entram e saem, a memória vira um queijo suíço de buracos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A memória virtual resolve &lt;strong&gt;todos&lt;/strong&gt; esses problemas de uma vez, introduzindo uma camada de indireção entre os endereços que o programa usa (virtuais) e os endereços físicos da RAM:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Todos os problemas em ciência da computação podem ser resolvidos com mais um nível de indireção." David Wheeler&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  A Hierarquia de Memória
&lt;/h2&gt;

&lt;p&gt;Para entender por que o kernel se dá ao trabalho de gerenciar memória de forma tão elaborada, é preciso entender uma verdade desconfortável do hardware: &lt;strong&gt;memória rápida é cara e pequena; memória barata é grande e lenta&lt;/strong&gt;. Não existe (ainda) uma tecnologia que seja simultaneamente rápida, barata e abundante.&lt;br&gt;
não me diga que chip de DDR5 que voce comprou no Kabun é rápido :D&lt;/p&gt;

&lt;p&gt;O resultado é uma &lt;strong&gt;hierarquia&lt;/strong&gt;, onde cada nível troca velocidade por capacidade:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F33leekvelwlzxa682abm.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F33leekvelwlzxa682abm.jpeg" alt="Hirarquia de Memoria" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repare na diferença brutal de escala: um acesso à RAM (~100 ns) é cerca de &lt;strong&gt;300 vezes mais lento&lt;/strong&gt; que um acesso a registrador. E um acesso a disco (HDD, ~10 ms) é &lt;strong&gt;100.000 vezes mais lento&lt;/strong&gt; que a RAM. Para colocar em perspectiva humana: se um acesso a registrador fosse 1 segundo, um acesso a disco seria equivalente a &lt;strong&gt;mais de um dia&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  O Princípio da Localidade
&lt;/h3&gt;

&lt;p&gt;O que torna essa hierarquia viável — em vez de um desastre de performance — é o &lt;strong&gt;princípio da localidade&lt;/strong&gt;, uma observação empírica sobre como programas reais acessam memória:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Localidade temporal&lt;/strong&gt;: se você acessou um endereço agora, provavelmente vai acessá-lo de novo em breve. Pense em uma variável de contador dentro de um loop, ou um objeto de configuração lido a cada requisição.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Localidade espacial&lt;/strong&gt;: se você acessou um endereço, provavelmente vai acessar endereços vizinhos em breve. Pense em iterar sobre um array, ou percorrer os campos de uma struct.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É por causa da localidade que caches funcionam. Quando você acessa um byte da RAM, o hardware não traz apenas aquele byte — traz uma &lt;strong&gt;linha de cache&lt;/strong&gt; inteira (tipicamente 64 bytes) para os níveis mais rápidos, apostando que você vai precisar dos vizinhos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Por que isso importa para backend?&lt;/strong&gt; Estruturas de dados que respeitam a localidade são dramaticamente mais rápidas. Um array contíguo (&lt;code&gt;list&lt;/code&gt; do Python com tipos primitivos via NumPy, &lt;code&gt;[]T&lt;/code&gt; em Go, &lt;code&gt;Array&amp;lt;T&amp;gt;&lt;/code&gt; em .NET) é percorrido muito mais rápido que uma lista encadeada espalhada pela heap, mesmo que ambos tenham complexidade $O(n)$. A constante escondida na notação Big-O é, muitas vezes, a hierarquia de memória se manifestando.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Estes dois loops têm a MESMA complexidade O(n),
# mas performance radicalmente diferente por causa da localidade espacial.
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="c1"&gt;# Array contíguo: localidade espacial ótima, cache-friendly
&lt;/span&gt;&lt;span class="n"&gt;dados_contiguos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;soma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dados_contiguos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;# o hardware faz prefetch das linhas de cache
&lt;/span&gt;
&lt;span class="c1"&gt;# Objetos Python espalhados pela heap: cada acesso pode ser um cache miss
&lt;/span&gt;&lt;span class="n"&gt;dados_espalhados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# cada int é um objeto no heap
&lt;/span&gt;&lt;span class="n"&gt;soma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dados_espalhados&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ~10-50x mais lento, entre outros motivos, por cache misses
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Gerenciamento de Memória sem Abstração
&lt;/h2&gt;

&lt;p&gt;Para valorizar a memória virtual, vale a pena entender como se vivia sem ela. Tanenbaum organiza essa evolução em etapas, e cada uma resolve uma limitação da anterior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monoprogramação
&lt;/h3&gt;

&lt;p&gt;O modelo mais simples possível: &lt;strong&gt;um processo por vez&lt;/strong&gt;. A memória é dividida entre o sistema operacional e o único programa do usuário. Quando você quer rodar outro programa, o atual precisa terminar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────┐  0xFFFF
│  Sistema Operacional │
├──────────────────────┤
│                      │
│  Programa do Usuário │
│                      │
└──────────────────────┘  0x0000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é essencialmente o MS-DOS. Funciona, mas desperdiça CPU: enquanto o único processo espera I/O (disco, rede), a CPU fica ociosa. Para um servidor backend moderno, que passa boa parte do tempo esperando o banco de dados responder, isso seria catastrófico.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiprogramação com Partições Fixas
&lt;/h3&gt;

&lt;p&gt;Para manter a CPU ocupada, precisamos de &lt;strong&gt;vários processos na memória ao mesmo tempo&lt;/strong&gt; — quando um bloqueia em I/O, outro roda. A primeira tentativa foi dividir a memória em &lt;strong&gt;partições fixas&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────┐
│  Sistema Operacional │
├──────────────────────┤
│  Partição 1 (8 MB)   │ ← Processo A
├──────────────────────┤
│  Partição 2 (4 MB)   │ ← Processo B
├──────────────────────┤
│  Partição 3 (4 MB)   │ ← livre
└──────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aqui surge o primeiro fantasma que assombra o gerenciamento de memória até hoje: a &lt;strong&gt;fragmentação&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fragmentação Interna vs Externa
&lt;/h3&gt;

&lt;p&gt;Estes dois conceitos vão reaparecer o artigo inteiro, então vale fixá-los bem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fragmentação interna&lt;/strong&gt;: acontece quando alocamos mais memória do que o necessário e o excesso, &lt;em&gt;dentro&lt;/em&gt; do bloco alocado, é desperdiçado. Se um processo de 3 MB é colocado numa partição de 4 MB, aquele 1 MB restante fica preso e inutilizável — está "dentro" da partição, mas ninguém pode usá-lo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fragmentação externa&lt;/strong&gt;: acontece quando há memória livre suficiente no total, mas ela está espalhada em pequenos buracos não-contíguos, nenhum grande o bastante para atender uma requisição. Você tem 10 MB livres, mas em pedaços de 1 MB, e chega um processo que precisa de 5 MB contíguos — não cabe, mesmo havendo espaço.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fragmentação Externa:

┌────┬──────┬────┬──────┬────┐
│ P1 │ LIVRE│ P2 │ LIVRE│ P3 │
└────┴──────┴────┴──────┴────┘
        3MB          3MB

Total livre: 6 MB, mas o maior bloco contíguo é 3 MB.
Um processo de 5 MB não cabe, mesmo havendo 6 MB livres!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esses conceitos não são história antiga — eles reaparecem no &lt;strong&gt;buddy allocator&lt;/strong&gt; do kernel, na fragmentação da heap do seu processo, na fragmentação do Large Object Heap do .NET e na pressão de alocação do Go. Guarde-os.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compactação de Memória
&lt;/h3&gt;

&lt;p&gt;Uma solução para fragmentação externa é a &lt;strong&gt;compactação&lt;/strong&gt;: mover todos os processos para um lado da memória, juntando os buracos livres em um único bloco grande.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Antes:  [P1][ livre ][P2][ livre ][P3]
Depois: [P1][P2][P3][      livre       ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O problema? Compactação é &lt;strong&gt;caríssima&lt;/strong&gt;. Mover gigabytes de RAM leva tempo e, durante a operação, o sistema praticamente para. Além disso, esbarramos num problema fundamental: se movemos um processo de lugar, todos os endereços que ele usa internamente mudam. Como o programa continua funcionando se o endereço &lt;code&gt;0x1000&lt;/code&gt; agora é fisicamente &lt;code&gt;0x5000&lt;/code&gt;? Isso nos leva à necessidade de &lt;strong&gt;abstração&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Abstração do Espaço de Endereçamento
&lt;/h2&gt;

&lt;p&gt;O problema central de todos os modelos acima é que os programas usam &lt;strong&gt;endereços físicos diretamente&lt;/strong&gt;. A solução é introduzir uma abstração: o &lt;strong&gt;espaço de endereçamento&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Um espaço de endereçamento é o conjunto de endereços que um processo pode usar para endereçar memória. A ideia genial é: cada processo tem seu &lt;strong&gt;próprio&lt;/strong&gt; espaço de endereçamento, isolado dos demais, e os endereços que ele usa (&lt;strong&gt;virtuais&lt;/strong&gt;) são traduzidos para endereços &lt;strong&gt;físicos&lt;/strong&gt; por baixo dos panos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Base e Limite: a proteção primitiva
&lt;/h3&gt;

&lt;p&gt;A primeira implementação dessa ideia usava dois registradores de hardware: &lt;strong&gt;base&lt;/strong&gt; e &lt;strong&gt;limite&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quando um processo é carregado, o registrador &lt;strong&gt;base&lt;/strong&gt; recebe o endereço físico onde ele começa.&lt;/li&gt;
&lt;li&gt;Todo endereço que o programa gera é &lt;strong&gt;somado&lt;/strong&gt; à base automaticamente pelo hardware.&lt;/li&gt;
&lt;li&gt;O registrador &lt;strong&gt;limite&lt;/strong&gt; guarda o tamanho do processo; qualquer acesso além dele gera uma falha (proteção).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Endereço virtual gerado pelo programa:  0x1000
Registrador base:                     + 0x5000
                                       ────────
Endereço físico real:                   0x6000   (se &amp;lt; limite, OK)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso resolve dois problemas de uma vez: &lt;strong&gt;relocação&lt;/strong&gt; (o programa pode ser carregado em qualquer lugar, basta ajustar a base) e &lt;strong&gt;proteção&lt;/strong&gt; (o limite impede que um processo acesse memória fora da sua região).&lt;/p&gt;

&lt;h3&gt;
  
  
  Relocação: estática vs dinâmica
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Relocação estática&lt;/strong&gt;: os endereços são ajustados &lt;strong&gt;uma vez&lt;/strong&gt;, no momento da carga do programa. Simples, mas o processo não pode ser movido depois.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relocação dinâmica&lt;/strong&gt;: a tradução acontece &lt;strong&gt;a cada acesso&lt;/strong&gt;, em tempo de execução, via hardware (base + limite, ou a MMU). É o que permite mover, fazer swap e compartilhar memória de forma flexível. É a abordagem dos sistemas modernos.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Swapping
&lt;/h3&gt;

&lt;p&gt;Base e limite ainda esbarram num limite físico: e se a soma da memória de todos os processos exceder a RAM? A resposta clássica foi o &lt;strong&gt;swapping&lt;/strong&gt;: mover processos inteiros da RAM para o disco quando não estão rodando, e trazê-los de volta quando necessário.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RAM:   [SO][ Processo A ][ Processo B ]
                              │  swap out
                              ▼
Disco: [ ............ Processo C ............ ]
                              │  swap in
                              ▼
RAM:   [SO][ Processo A ][ Processo C ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O problema do swapping clássico é sua granularidade grosseira: mover um &lt;strong&gt;processo inteiro&lt;/strong&gt; para o disco é lento e desperdiça banda se o processo só precisava de uma fração da sua memória. É aqui que a &lt;strong&gt;paginação&lt;/strong&gt; e a &lt;strong&gt;memória virtual&lt;/strong&gt; entram para revolucionar tudo — mas isso é o assunto central da próxima parte.&lt;/p&gt;




&lt;h2&gt;
  
  
  Memória Virtual: os Fundamentos
&lt;/h2&gt;

&lt;p&gt;Chegamos ao conceito central. A &lt;strong&gt;memória virtual&lt;/strong&gt; parte de uma constatação: raramente um programa precisa de &lt;strong&gt;toda&lt;/strong&gt; a sua memória ao mesmo tempo.&lt;/p&gt;

&lt;h3&gt;
  
  
  A motivação: executar o impossível
&lt;/h3&gt;

&lt;p&gt;A ideia original (Fotheringham, 1961) era permitir executar programas &lt;strong&gt;maiores que a memória física disponível&lt;/strong&gt;. Em vez de exigir que o programa inteiro esteja na RAM, mantemos na memória apenas as partes que estão sendo usadas &lt;strong&gt;agora&lt;/strong&gt;, deixando o resto no disco. Quando o programa acessa uma parte que não está na RAM, o kernel a traz do disco sob demanda.&lt;/p&gt;

&lt;h3&gt;
  
  
  O Working Set
&lt;/h3&gt;

&lt;p&gt;Isso funciona por causa — de novo — do &lt;strong&gt;princípio da localidade&lt;/strong&gt;. Peter Denning formalizou essa intuição no conceito de &lt;strong&gt;working set&lt;/strong&gt;: o conjunto de páginas que um processo está ativamente usando em um dado intervalo de tempo. O working set costuma ser bem menor que o total de memória alocada pelo processo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exemplo backend concreto&lt;/strong&gt;: uma API .NET pode ter 500 MB de assemblies, bibliotecas e código carregados, mas em um dado segundo, respondendo a requisições, ela toca apenas talvez 50 MB — o hot path do código, os objetos das requisições ativas, as conexões do pool. Esse é o working set. O resto pode ficar tranquilamente no disco (ou no page cache) até ser necessário.&lt;/p&gt;

&lt;h3&gt;
  
  
  Espaço de endereçamento virtual
&lt;/h3&gt;

&lt;p&gt;Com memória virtual, cada processo enxerga um espaço de endereçamento contíguo e enorme — em sistemas de 64 bits, teoricamente $2^{64}$ bytes, na prática 48 ou 57 bits de endereçamento (falaremos disso na próxima parte). Esse espaço é &lt;strong&gt;independente do hardware físico&lt;/strong&gt;: o processo não sabe (nem precisa saber) quanta RAM a máquina realmente tem, nem onde fisicamente suas páginas estão.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3k7j6nqnv66iv67xtauq.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3k7j6nqnv66iv67xtauq.jpeg" alt="Endereçamento de Memoria" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repare que endereços virtuais &lt;strong&gt;contíguos&lt;/strong&gt; podem mapear para frames físicos &lt;strong&gt;espalhados&lt;/strong&gt; — e que dois processos podem ter o mesmo endereço virtual apontando para frames físicos diferentes. Essa é exatamente a mágica do isolamento.&lt;/p&gt;




&lt;h2&gt;
  
  
  A MMU e a Tradução de Endereços
&lt;/h2&gt;

&lt;p&gt;Se a cada acesso à memória é preciso traduzir um endereço virtual para físico, essa tradução &lt;strong&gt;precisa&lt;/strong&gt; ser rápida — rápida a ponto de acontecer em hardware, não em software. Quem faz isso é a &lt;strong&gt;MMU (Memory Management Unit)&lt;/strong&gt;, um componente do processador.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzpi7hycmusqe5ni4g2f0.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzpi7hycmusqe5ni4g2f0.jpeg" alt="MMU e TLB" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O fluxo, simplificado:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;O núcleo da CPU gera um &lt;strong&gt;endereço virtual&lt;/strong&gt; (por exemplo, ao executar &lt;code&gt;mov rax, [0x00401000]&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;A MMU intercepta esse endereço &lt;strong&gt;antes&lt;/strong&gt; que ele chegue ao barramento de memória.&lt;/li&gt;
&lt;li&gt;A MMU consulta a &lt;strong&gt;tabela de páginas&lt;/strong&gt; (mantida pelo kernel) para descobrir qual frame físico corresponde àquela página virtual.&lt;/li&gt;
&lt;li&gt;Se a tradução existe e é válida, a MMU emite o &lt;strong&gt;endereço físico&lt;/strong&gt; correspondente para a RAM.&lt;/li&gt;
&lt;li&gt;Se a página não está mapeada na memória física, a MMU dispara um &lt;strong&gt;page fault&lt;/strong&gt; — uma interrupção que transfere o controle ao kernel para resolver a situação (buscar a página no disco, alocar uma nova página, ou matar o processo se o acesso for inválido).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Esse mecanismo é o coração de tudo o que veremos adiante. A tabela de páginas, o formato das suas entradas, como ela é organizada em múltiplos níveis, como a TLB acelera essa tradução, e o que exatamente acontece durante um page fault — tudo isso é o assunto das &lt;strong&gt;próximas partes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Por ora, o essencial é ter internalizado a grande sacada: &lt;strong&gt;existe uma camada de tradução, feita em hardware, entre os endereços que seu programa usa e a memória física real&lt;/strong&gt;. Toda a gestão de memória do Linux — proteção, isolamento, swap, copy-on-write, memory-mapped files, compartilhamento de bibliotecas — é construída sobre essa fundação.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-gerenciamento-de-memoria-parte-ii-3ooi"&gt;&lt;strong&gt;Continua na Parte II&lt;/strong&gt;&lt;/a&gt;, onde vamos abrir a tabela de páginas, entender a hierarquia de 4 e 5 níveis do Linux (PGD, PUD, PMD, PTE), o papel da TLB na performance e como a segmentação se encaixa nessa história.&lt;br&gt;
&lt;strong&gt;:D&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conteúdo parcialmente gerado com auxílio de IA generativa (me ajudou a organizar tudo isso kkkk)&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referências Bibliográficas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Desenvolvimento-do-Kernel-Linux/dp/8573933410" rel="noopener noreferrer"&gt;Desenvolvimento Do Kernel Do Linux - David Cram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Linux-Bible-Christopher-Negus/dp/1394317468" rel="noopener noreferrer"&gt;Linux Bible - Christopher Negus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kernel.org/doc/gorman/html/understand/" rel="noopener noreferrer"&gt;Understanding the Linux Virtual Memory Manager - Mel Gorman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Sistemas-Operacionais-Modernos-Andrew-Tanenbaum/dp/8582606168" rel="noopener noreferrer"&gt;Sistemas Operacionais Modernos - Tanenbaum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>dotnet</category>
      <category>go</category>
      <category>python</category>
    </item>
    <item>
      <title>Kernel Linux para Desenvolvedores Backend - Processos &amp; Threads Parte IV</title>
      <dc:creator>Alex Volnei Galante</dc:creator>
      <pubDate>Tue, 09 Jun 2026 15:02:40 +0000</pubDate>
      <link>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-processos-threads-parte-iv-41j</link>
      <guid>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-processos-threads-parte-iv-41j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Este artigo é a continuação da &lt;strong&gt;Parte III&lt;/strong&gt;, recomendo começar por lá:&lt;br&gt;
&lt;a href="https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-processos-threads-parte-iii-1187"&gt;Kernel Linux para Desenvolvedores Backend — Processos &amp;amp; Threads Parte III&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Sumário
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Sequência de um Context Switch&lt;/li&gt;
&lt;li&gt;Overhead de Context Switch: Custos Diretos e Indiretos&lt;/li&gt;
&lt;li&gt;TLB Flush: Impacto na Performance&lt;/li&gt;
&lt;li&gt;Cache Pollution: Efeitos em L1, L2 e L3&lt;/li&gt;
&lt;li&gt;Reduzindo o Impacto de Context Switches&lt;/li&gt;
&lt;li&gt;
Conexão com Desenvolvimento Backend: .NET

&lt;ul&gt;
&lt;li&gt;Thread Pool do .NET e Escalonamento do Kernel&lt;/li&gt;
&lt;li&gt;Task Parallel Library (TPL) e Cooperação com o Scheduler&lt;/li&gt;
&lt;li&gt;Async/Await e SynchronizationContext no Linux&lt;/li&gt;
&lt;li&gt;Exemplo Prático: Otimizando Aplicações ASP.NET Core em Containers&lt;/li&gt;
&lt;li&gt;CoreCLR e Interação com o Scheduler do Linux&lt;/li&gt;
&lt;li&gt;NUMA Awareness em Aplicações .NET&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Conexão com Desenvolvimento Backend: Golang

&lt;ul&gt;
&lt;li&gt;Goroutines vs Kernel Threads (M:N Threading Model)&lt;/li&gt;
&lt;li&gt;O Scheduler do Go Runtime e sua Relação com o Kernel&lt;/li&gt;
&lt;li&gt;GOMAXPROCS e CPU Affinity&lt;/li&gt;
&lt;li&gt;Análise de Performance: Blocking Syscalls e Goroutines&lt;/li&gt;
&lt;li&gt;Exemplo Prático: Microserviços Go e Tuning de Concorrência&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Referências Bibliográficas&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Sequência de um Context Switch
&lt;/h3&gt;

&lt;p&gt;O context switch ocorre em resposta a diferentes triggers — preempção por timer, bloqueio em I/O, yield voluntário, ou chegada de processo de maior prioridade.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Thread A (executando)                Kernel                          Thread B (pronta)
        │                               │                                    │
        │   ← timer interrupt →         │                                    │
        │──────────────────────────────►│                                    │
        │                               │  1. Salva registradores de A       │
        │                               │     na kernel stack de A           │
        │                               │                                    │
        │                               │  2. Chama schedule()               │
        │                               │     → CFS seleciona B              │
        │                               │     (menor vruntime)               │
        │                               │                                    │
        │                               │  3. Chama context_switch()         │
        │                               │     a) switch_mm() — se processo   │
        │                               │        diferente: troca CR3        │
        │                               │        (page tables)               │
        │                               │     b) switch_to() — troca         │
        │                               │        kernel stack pointer        │
        │                               │        (RSP para stack de B)       │
        │                               │                                    │
        │                               │  4. Restaura registradores de B    │
        │                               │     da kernel stack de B           │
        │                               │                                    │
        │                               │  5. Retorna para userspace         │
        │                               │──────────────────────────────────► │
        │                               │                                    │
        │  (suspenso)                   │      (executando)                  │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No código do kernel Linux, a função central é &lt;code&gt;context_switch()&lt;/code&gt; em &lt;code&gt;kernel/sched/core.c&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/*
 * context_switch - troca para o novo contexto de MM e para
 * a nova thread (task_struct do processo que será executado).
 */&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;__always_inline&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rq&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nf"&gt;context_switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;rq&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;task_struct&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;task_struct&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Troca do espaço de endereçamento (memory descriptor)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                          &lt;span class="c1"&gt;// kernel thread&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;active_mm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;active_mm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// empresta mm do anterior&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                  &lt;span class="c1"&gt;// user process&lt;/span&gt;
        &lt;span class="n"&gt;switch_mm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;active_mm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// troca page tables&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Troca do contexto de execução (registradores, stack)&lt;/span&gt;
    &lt;span class="n"&gt;switch_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;finish_task_switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Overhead de Context Switch: Custos Diretos e Indiretos
&lt;/h3&gt;

&lt;p&gt;O custo de um context switch vai muito além da simples operação de salvar/restaurar registradores.&lt;/p&gt;

&lt;h4&gt;
  
  
  Custos diretos (tempo gasto no switch em si)
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Componente&lt;/th&gt;
&lt;th&gt;Custo típico&lt;/th&gt;
&lt;th&gt;Notas&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Salvar/restaurar registradores gerais&lt;/td&gt;
&lt;td&gt;~100-200ns&lt;/td&gt;
&lt;td&gt;16 registradores de 64 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Salvar/restaurar FPU/SSE/AVX&lt;/td&gt;
&lt;td&gt;~200-500ns&lt;/td&gt;
&lt;td&gt;Depende do tamanho do estado SIMD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chamada a &lt;code&gt;schedule()&lt;/code&gt; + decisão&lt;/td&gt;
&lt;td&gt;~200-500ns&lt;/td&gt;
&lt;td&gt;Percorrer a red-black tree do CFS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;switch_mm()&lt;/code&gt; (troca de CR3)&lt;/td&gt;
&lt;td&gt;~100-300ns&lt;/td&gt;
&lt;td&gt;Apenas entre processos diferentes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overhead de kernel entry/exit&lt;/td&gt;
&lt;td&gt;~100-200ns&lt;/td&gt;
&lt;td&gt;Transição user↔kernel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total direto (threads mesmo processo)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~0.5-1.5μs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sem troca de address space&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total direto (processos diferentes)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~1-3μs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Com troca de address space&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Custos indiretos (efeitos colaterais — frequentemente maiores que custos diretos)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Context Switch: Custos Indiretos

┌────────────────────────────────────────────────────────────────┐
│                                                                │
│  ┌──────────────┐     ┌───────────────┐     ┌───────────────┐  │
│  │  TLB Flush   │     │Cache Pollution│     │Pipeline Flush │  │
│  │              │     │               │     │               │  │
│  │ Custo: ~5μs  │     │ Custo: ~10μs  │     │ Custo: ~1μs   │  │
│  │ (warm-up)    │     │ (warm-up)     │     │ (imediato)    │  │
│  └──────────────┘     └───────────────┘     └───────────────┘  │
│         │                    │                    │            │
│         ▼                    ▼                    ▼            │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ Custo TOTAL efetivo: 5-50μs (dependendo do working set) │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                │
└────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os custos indiretos podem ser &lt;strong&gt;10-50x maiores&lt;/strong&gt; que os custos diretos, porque refletem o tempo necessário para "aquecer" as caches depois que o novo processo começa a executar.&lt;/p&gt;

&lt;h3&gt;
  
  
  TLB Flush: Impacto na Performance
&lt;/h3&gt;

&lt;p&gt;O &lt;strong&gt;TLB (Translation Lookaside Buffer)&lt;/strong&gt; é um cache de traduções de endereços virtuais para físicos. Como cada processo tem seu próprio espaço de endereçamento (page tables diferentes), as entradas do TLB de um processo são inválidas para outro.&lt;/p&gt;

&lt;h4&gt;
  
  
  O problema
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Antes do context switch:
TLB (processo A):
┌────────────────────────────────────┐
│ VPN 0x7f0001 → PFN 0x3A2 (hit!)    │  ← acesso rápido (~1 ciclo)
│ VPN 0x7f0002 → PFN 0x1B5 (hit!)    │
│ VPN 0x400000 → PFN 0x089 (hit!)    │
│ ... (centenas de entradas)         │
└────────────────────────────────────┘

Após context switch para processo B (TLB flush):
TLB:
┌────────────────────────────────────┐
│ (vazio)                            │  ← TODOS os acessos são miss
│ (vazio)                            │     cada miss = page table walk
│ (vazio)                            │     (~10-100 ciclos por miss)
│ ...                                │
└────────────────────────────────────┘

Processo B precisa "aquecer" o TLB:
Acesso 1: VPN 0x500000 → TLB miss → page walk → PFN 0x2C1 (lento!)
Acesso 2: VPN 0x500001 → TLB miss → page walk → PFN 0x2C2 (lento!)
...
Após ~100-1000 acessos: TLB aquecido novamente
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Impacto quantitativo
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;TLB L1 (dTLB/iTLB): ~64-128 entradas, miss penalty ~7 ciclos (L2 TLB hit)&lt;/li&gt;
&lt;li&gt;TLB L2 (STLB): ~512-2048 entradas, miss penalty ~20-100 ciclos (page walk)&lt;/li&gt;
&lt;li&gt;Para um working set de 100MB em páginas de 4KB: 25.600 páginas — impossível caber no TLB
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Medindo TLB misses com perf&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;perf &lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; dTLB-load-misses,dTLB-loads,iTLB-load-misses &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;pid&amp;gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;5

Performance counter stats &lt;span class="k"&gt;for &lt;/span&gt;process &lt;span class="s1"&gt;'python3'&lt;/span&gt;:
         1,234,567  dTLB-load-misses    &lt;span class="c"&gt;# 0.15% of all dTLB loads&lt;/span&gt;
       823,456,789  dTLB-loads
           123,456  iTLB-load-misses

&lt;span class="c"&gt;# TLB miss rate alto (&amp;gt;1%) indica impacto significativo de context switches&lt;/span&gt;
&lt;span class="c"&gt;# ou working set muito grande&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Mitigações
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;PCID (Process-Context Identifiers)&lt;/strong&gt;: Processadores modernos (Haswell+) suportam tags no TLB que identificam a qual processo cada entrada pertence. Isso permite manter entradas de múltiplos processos no TLB simultaneamente, evitando flush completo.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TLB com PCID:
┌────────────────────────────────────────────┐
│ PCID=1 VPN 0x7f0001 → PFN 0x3A2 (proc A)   │  ← mantido!
│ PCID=1 VPN 0x7f0002 → PFN 0x1B5 (proc A)   │  ← mantido!
│ PCID=2 VPN 0x500000 → PFN 0x2C1 (proc B)   │  ← novo
│ PCID=2 VPN 0x500001 → PFN 0x2C2 (proc B)   │  ← novo
└────────────────────────────────────────────┘
Quando A volta a executar: TLB hits imediatos!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Huge pages (2MB/1GB)&lt;/strong&gt;: Reduzem o número de entradas TLB necessárias. Com páginas de 2MB, 100MB de working set requer apenas 50 entradas TLB (vs 25.600 com 4KB).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Thread affinity&lt;/strong&gt;: Manter threads no mesmo core reduz TLB pressure — threads do mesmo processo compartilham o address space e portanto as mesmas entradas TLB.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Implicação prática&lt;/strong&gt;: No Linux com PCID habilitado (default desde kernel 4.14+), o custo de TLB flush em context switches é significativamente reduzido. Porém, KPTI (Kernel Page Table Isolation — mitigação para Meltdown) requer flush parcial de TLB em cada syscall, adicionando overhead (~5-10%) mesmo sem context switch.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Cache Pollution: Efeitos em L1, L2 e L3
&lt;/h3&gt;

&lt;p&gt;O segundo grande custo indireto é a &lt;strong&gt;poluição de cache&lt;/strong&gt;. Quando um processo é escalonado, ele começa a acessar suas regiões de memória — que provavelmente não estão nos caches — expulsando dados do processo anterior.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hierarquia de cache e impacto
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hierarquia de Cache (servidor típico):
┌─────────────────────────────────────────────────────────────┐
│ L1 Cache (por core)                                         │
│   ├── L1d (dados): 32-48KB, ~4 ciclos latência              │
│   └── L1i (instruções): 32-48KB, ~4 ciclos                  │
│   → Context switch: 100% invalidado (working set diferente) │
├─────────────────────────────────────────────────────────────┤
│ L2 Cache (por core)                                         │
│   └── Unified: 256KB-1.25MB, ~12 ciclos                     │
│   → Context switch: 80-100% invalidado                      │
├─────────────────────────────────────────────────────────────┤
│ L3 Cache (compartilhado entre cores)                        │
│   └── Shared: 16-64MB, ~30-40 ciclos                        │
│   → Context switch no mesmo core: impacto em L3 parcial     │
│   → Migração entre cores: impacto maior                     │
└─────────────────────────────────────────────────────────────┘

Cache miss penalties:
  L1 hit:  ~4 ciclos  (~1.5ns @ 3GHz)
  L2 hit:  ~12 ciclos (~4ns)
  L3 hit:  ~30-40 ciclos (~12ns)
  RAM:     ~200-300 ciclos (~100ns)  ← 60-70x mais lento que L1!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cenário: API server com context switches frequentes
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Servidor com 16 workers competindo por 8 cores:

Worker A (executando query handler):
  - Hot data em L1/L2: connection pool struct, query buffer, hash map
  - Working set: ~200KB em L2

  ← context switch (preempção por timer) →

Worker B começa a executar:
  - Seu working set (~200KB) substitui dados de A no L2
  - Cada acesso de B é um L2 miss inicialmente (~12 ciclos → RAM ~200 ciclos)

  ← context switch (B bloqueia em I/O) →

Worker A retoma:
  - Seus dados NÃO estão mais no L2!
  - Período de "cache warm-up": ~1000-5000 cache misses
  - Overhead efetivo: 1000 × 100ns = ~100μs de penalidade

Impacto em latência da API:
  - Se timer tick = 4ms e handler leva ~2ms
  - ~1 context switch por request em média
  - Cache warm-up adiciona ~50-100μs por request
  - Em p99: múltiplos context switches → +200-500μs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Medindo cache pollution
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Cache misses por context switch&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;perf &lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; cache-misses,cache-references,context-switches &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;pid&amp;gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;10

Performance counter stats:
        5,234,567  cache-misses        &lt;span class="c"&gt;# 3.2% of cache references&lt;/span&gt;
      163,580,000  cache-references
           12,456  context-switches

&lt;span class="c"&gt;# Cache misses por context switch: 5,234,567 / 12,456 ≈ 420 misses/switch&lt;/span&gt;
&lt;span class="c"&gt;# Custo estimado: 420 × 100ns = 42μs de warm-up por switch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reduzindo o Impacto de Context Switches
&lt;/h3&gt;

&lt;p&gt;Para aplicações backend de alta performance, minimizar context switches (ou seu impacto) é uma otimização significativa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Estratégias de Mitigação:

1. REDUZIR número de context switches:
   ├── Dimensionar workers = cores (evitar oversubscription)
   ├── Usar async I/O (epoll/io_uring) ao invés de thread-per-connection
   ├── Batch processing: processar múltiplos items antes de ceder CPU
   └── Aumentar timeslice para workloads batch (nice, SCHED_BATCH)

2. REDUZIR custo de cada context switch:
   ├── CPU affinity (taskset/sched_setaffinity): manter thread no mesmo core
   ├── NUMA-aware allocation: memória próxima ao core
   ├── Huge pages: menos TLB entries necessárias
   └── Manter working set compacto (cabe no L2/L3)

3. EVITAR migração entre cores:
   ├── cgroups cpuset: pinning de processos a cores específicos
   ├── isolcpus: reservar cores exclusivos para a aplicação
   └── GOMAXPROCS/worker count = cores no cpuset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Pinning de processo a cores específicos&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;taskset &lt;span class="nt"&gt;-c&lt;/span&gt; 0-3 python3 app.py        &lt;span class="c"&gt;# restringe aos cores 0-3&lt;/span&gt;

&lt;span class="c"&gt;# Isolando cores no boot (grub)&lt;/span&gt;
&lt;span class="c"&gt;# GRUB_CMDLINE_LINUX="isolcpus=4-7"    # cores 4-7 isolados do scheduler geral&lt;/span&gt;

&lt;span class="c"&gt;# Verificando context switches de um processo&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;pidstat &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;pid&amp;gt; 1
Linux 5.15.0 &lt;span class="o"&gt;(&lt;/span&gt;server&lt;span class="o"&gt;)&lt;/span&gt;    05/14/2026
09:00:01 AM   PID   cswch/s nvcswch/s  Command
09:00:02 AM  1350    152.00     12.00  python3
              ↑ voluntary   ↑ involuntary &lt;span class="o"&gt;(&lt;/span&gt;preempted&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Regra prática para backend&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Se &lt;code&gt;nvcswch/s&lt;/code&gt; (involuntary) é alto → oversubscription de CPU (mais threads que cores)&lt;/li&gt;
&lt;li&gt;Se &lt;code&gt;cswch/s&lt;/code&gt; (voluntary) é alto → normal para I/O-bound (bloqueia em syscalls)&lt;/li&gt;
&lt;li&gt;Se ambos são altos → redesenhe a arquitetura (async I/O, menos workers, ou CPU affinity)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Conexão com Desenvolvimento Backend: .NET
&lt;/h2&gt;

&lt;p&gt;O .NET runtime (CoreCLR) no Linux é um dos exemplos mais sofisticados de como um runtime gerenciado interage com o escalonador do kernel. Diferente do Python (limitado pelo GIL) ou do Go (que implementa seu próprio scheduler M:N), o .NET adota um modelo 1:1 onde cada thread gerenciada mapeia diretamente para uma kernel thread — mas adiciona uma camada de abstração poderosa: o &lt;strong&gt;ThreadPool&lt;/strong&gt; e o &lt;strong&gt;Task Parallel Library (TPL)&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thread Pool do .NET e Escalonamento do Kernel
&lt;/h3&gt;

&lt;p&gt;O ThreadPool do .NET é o coração da execução assíncrona em aplicações ASP.NET Core. Ele gerencia um conjunto de kernel threads que executam work items enfileirados — incluindo continuações de &lt;code&gt;async/await&lt;/code&gt;, timers, e I/O completion callbacks.&lt;/p&gt;

&lt;h4&gt;
  
  
  Arquitetura do ThreadPool
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Aplicação ASP.NET Core
┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│   Request 1 ──┐     Request 2 ──┐     Request 3 ──┐             │
│               ▼                  ▼                  ▼           │
│   ┌─────────────────────────────────────────────────────┐       │
│   │              Global Work Queue                      │       │
│   │  [Task A] → [Task B] → [Task C] → [Task D] → ...    │       │
│   └────────────────────────┬────────────────────────────┘       │
│                            │                                    │
│   ┌────────────────────────┼────────────────────────────┐       │
│   │         ThreadPool     │                            │       │
│   │  ┌──────────┐  ┌──────┴─────┐  ┌──────────┐         │       │
│   │  │ Worker 1 │  │  Worker 2  │  │ Worker 3 │  ...    │       │
│   │  │(stealing)│  │(executing) │  │(waiting) │         │       │
│   │  └────┬─────┘  └─────┬──────┘  └────┬─────┘         │       │
│   │       │Local Q        │Local Q       │Local Q       │       │
│   └───────┼───────────────┼──────────────┼──────────────┘       │
│           │               │              │                      │
├───────────┼───────────────┼──────────────┼──────────────────────┤
│   Kernel  ▼               ▼              ▼                      │
│   ┌──────────┐     ┌──────────┐   ┌──────────┐                  │
│   │  KThread │     │  KThread │   │  KThread │                  │
│   │  (core 0)│     │  (core 1)│   │  (core 2)│                  │
│   └──────────┘     └──────────┘   └──────────┘                  │
│              CFS Scheduler (kernel)                             │
└─────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Hill Climbing Algorithm
&lt;/h4&gt;

&lt;p&gt;O ThreadPool do .NET usa um algoritmo de &lt;strong&gt;hill climbing&lt;/strong&gt; para ajustar dinamicamente o número de threads. Ao contrário de pools estáticos (como Gunicorn workers), o .NET ThreadPool monitora o throughput e adiciona/remove threads para maximizá-lo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hill Climbing: Ajuste dinâmico de threads

Throughput
    ▲
    │         ╭──── ponto ótimo
    │        ╱│╲
    │       ╱ │ ╲
    │      ╱  │  ╲         ← mais threads = mais context switches
    │     ╱   │   ╲            = menos throughput
    │    ╱    │    ╲
    │   ╱     │     ╲
    │──╱──────┼──────╲────────►
    │         │              Número de threads
    │    under-    over-
    │  subscribed  subscribed

Comportamento:
1. Começa com Environment.ProcessorCount threads
2. Adiciona 1 thread a cada 500ms se work items estão enfileirados
3. Mede throughput (work items completed/sec)
4. Se throughput subiu → continua adicionando
5. Se throughput caiu → remove thread (oversubscription detectada)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Interação com o Kernel Scheduler
&lt;/h4&gt;

&lt;p&gt;Cada worker thread do ThreadPool é uma kernel thread real (&lt;code&gt;clone()&lt;/code&gt; com &lt;code&gt;CLONE_VM | CLONE_FILES | CLONE_SIGHAND&lt;/code&gt;). Isso significa que:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;O CFS escalona cada worker independentemente&lt;/strong&gt; — se você tem 8 workers em 4 cores, o CFS garante distribuição justa&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context switches entre workers são reais&lt;/strong&gt; — com custo de ~1-2μs (threads do mesmo processo, sem TLB flush)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Involuntary preemption ocorre&lt;/strong&gt; — se um handler de request é CPU-bound, será preemptado após seu timeslice
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Monitorando ThreadPool via dotnet-counters&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;dotnet-counters monitor &lt;span class="nt"&gt;--process-id&lt;/span&gt; &amp;lt;pid&amp;gt; System.Runtime

&lt;span class="o"&gt;[&lt;/span&gt;System.Runtime]
    &lt;span class="c"&gt;# of Active Timers                          12&lt;/span&gt;
    ThreadPool Completed Work Item Count    1,847,293
    ThreadPool Queue Length                       0     ← 0 &lt;span class="o"&gt;=&lt;/span&gt; saudável
    ThreadPool Thread Count                     16     ← threads ativas
    Monitor Lock Contention Count              234

&lt;span class="c"&gt;# Se Queue Length &amp;gt; 0 persistentemente:&lt;/span&gt;
&lt;span class="c"&gt;# → ThreadPool está saturado&lt;/span&gt;
&lt;span class="c"&gt;# → Requests estão esperando por thread disponível&lt;/span&gt;
&lt;span class="c"&gt;# → Considere: mais threads, async I/O, ou otimizar handlers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Implicação prática&lt;/strong&gt;: O &lt;code&gt;ThreadPool Queue Length&lt;/code&gt; é o equivalente .NET ao "load average" da aplicação. Se consistentemente &amp;gt; 0, sua aplicação está thread-starved. Possíveis causas: sync-over-async (bloqueando threads com &lt;code&gt;.Result&lt;/code&gt; ou &lt;code&gt;.Wait()&lt;/code&gt;), thread pool exhaustion por I/O bloqueante, ou handlers CPU-bound longos.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Task Parallel Library (TPL) e Cooperação com o Scheduler
&lt;/h3&gt;

&lt;p&gt;O TPL (&lt;code&gt;System.Threading.Tasks&lt;/code&gt;) é a abstração de alto nível que o .NET oferece sobre o ThreadPool. Quando você escreve &lt;code&gt;Task.Run(...)&lt;/code&gt; ou usa &lt;code&gt;async/await&lt;/code&gt;, o TPL decide &lt;strong&gt;quando&lt;/strong&gt; e &lt;strong&gt;onde&lt;/strong&gt; executar o código.&lt;/p&gt;

&lt;h4&gt;
  
  
  Work Stealing e Localidade de Cache
&lt;/h4&gt;

&lt;p&gt;O ThreadPool do .NET implementa &lt;strong&gt;work stealing&lt;/strong&gt; — cada worker thread tem uma fila local (lock-free deque). Quando sua fila esvazia, a thread "rouba" trabalho da fila de outra thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Work Stealing:

Worker 1 (core 0)        Worker 2 (core 1)        Worker 3 (core 2)
┌──────────────┐         ┌──────────────┐         ┌──────────────┐
│ Local Queue  │         │ Local Queue  │         │ Local Queue  │
│ [T1][T2][T3] │         │ [T4][T5]     │         │ (vazia)      │
└──────────────┘         └──────────────┘         └──────┬───────┘
                                                         │
                                                    steal│from Worker 1
                                                         │
                                                         ▼
                                                    executa T3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Impacto no kernel scheduler&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Work stealing mantém todas as threads ocupadas → menos idle time → melhor utilização de CPU&lt;/li&gt;
&lt;li&gt;Porém, roubar trabalho de outra thread pode significar processar dados que estão no cache de &lt;strong&gt;outro core&lt;/strong&gt; → cache misses&lt;/li&gt;
&lt;li&gt;O .NET tenta minimizar isso mantendo continuações (&lt;code&gt;await&lt;/code&gt;) na mesma thread que iniciou a operação&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Parallel.ForEach e Partitioning
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Processamento paralelo de batch&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ForEachAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ParallelOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;MaxDegreeOfParallelism&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessorCount&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessItemAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;MaxDegreeOfParallelism = Environment.ProcessorCount&lt;/code&gt; é a configuração ideal para workloads CPU-bound — evita oversubscription. Para I/O-bound, pode ser maior (as threads bloqueiam em I/O e o kernel escalona outras).&lt;/p&gt;

&lt;h3&gt;
  
  
  Async/Await e SynchronizationContext no Linux
&lt;/h3&gt;

&lt;p&gt;O modelo &lt;code&gt;async/await&lt;/code&gt; do .NET é fundamentalmente diferente de threads — é &lt;strong&gt;concorrência cooperativa&lt;/strong&gt; sobre o ThreadPool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueryAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// ← libera a thread!&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// ← pode executar em OUTRA thread&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// ← libera novamente&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Timeline&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;uma&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="n"&gt;ThreadPool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;Thread&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="p"&gt;|...&lt;/span&gt;
                              &lt;span class="err"&gt;│&lt;/span&gt;                          &lt;span class="err"&gt;↑&lt;/span&gt;
                              &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="n"&gt;devolvida&lt;/span&gt; &lt;span class="err"&gt;──────┘&lt;/span&gt;
                                  &lt;span class="n"&gt;ao&lt;/span&gt; &lt;span class="nf"&gt;pool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kernel&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;
                                  &lt;span class="n"&gt;dispon&lt;/span&gt;&lt;span class="err"&gt;í&lt;/span&gt;&lt;span class="n"&gt;vel&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="n"&gt;outro&lt;/span&gt; &lt;span class="n"&gt;trabalho&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  SynchronizationContext no Linux
&lt;/h4&gt;

&lt;p&gt;No ASP.NET Core (ao contrário do WPF/WinForms), &lt;strong&gt;não há SynchronizationContext&lt;/strong&gt;. Isso significa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Continuações após &lt;code&gt;await&lt;/code&gt; podem executar em &lt;strong&gt;qualquer thread&lt;/strong&gt; do pool&lt;/li&gt;
&lt;li&gt;Não há overhead de marshaling para uma thread específica&lt;/li&gt;
&lt;li&gt;Não há risco de deadlock por contexto de sincronização
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;ASP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NET&lt;/span&gt; &lt;span class="nf"&gt;Core&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="n"&gt;SynchronizationContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

&lt;span class="n"&gt;Thread&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="err"&gt;──&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;antes&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;──&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;   &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="err"&gt;──&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;continua&lt;/span&gt;&lt;span class="err"&gt;çã&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="err"&gt;──&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="n"&gt;Thread&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="err"&gt;──&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;antes&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;──&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;   &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="err"&gt;──&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;continua&lt;/span&gt;&lt;span class="err"&gt;çã&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="err"&gt;──&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
                                              &lt;span class="err"&gt;↑&lt;/span&gt;
                                     &lt;span class="n"&gt;continua&lt;/span&gt;&lt;span class="err"&gt;çã&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="n"&gt;executa&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt; &lt;span class="n"&gt;Thread&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
                                     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qualquer&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="n"&gt;dispon&lt;/span&gt;&lt;span class="err"&gt;í&lt;/span&gt;&lt;span class="n"&gt;vel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;vs&lt;/span&gt; &lt;span class="n"&gt;WPF&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;WinForms&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="n"&gt;SynchronizationContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

&lt;span class="n"&gt;Thread&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UI&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="err"&gt;──&lt;/span&gt; &lt;span class="n"&gt;antes&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="err"&gt;──&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="err"&gt;──&lt;/span&gt; &lt;span class="n"&gt;continua&lt;/span&gt;&lt;span class="err"&gt;çã&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="err"&gt;──&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;  &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="n"&gt;SEMPRE&lt;/span&gt; &lt;span class="n"&gt;na&lt;/span&gt; &lt;span class="n"&gt;UI&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;
                                        &lt;span class="err"&gt;↑&lt;/span&gt;
                                   &lt;span class="n"&gt;marshaled&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="nf"&gt;volta&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;overhead&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;poss&lt;/span&gt;&lt;span class="err"&gt;í&lt;/span&gt;&lt;span class="n"&gt;vel&lt;/span&gt; &lt;span class="n"&gt;deadlock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Implicação para o kernel&lt;/strong&gt;: Sem SynchronizationContext, as continuações de &lt;code&gt;await&lt;/code&gt; são enfileiradas no ThreadPool global. O kernel vê apenas threads do pool pegando trabalho — não há affinity forçada. Isso é bom para throughput (qualquer core pode executar qualquer continuação), mas pode causar mais cache misses (dados de um request processados em cores diferentes).&lt;/p&gt;

&lt;h4&gt;
  
  
  Async I/O no Linux: epoll sob o capô
&lt;/h4&gt;

&lt;p&gt;Quando você faz &lt;code&gt;await httpClient.GetAsync(url)&lt;/code&gt; no Linux, o .NET usa &lt;strong&gt;epoll&lt;/strong&gt; para I/O assíncrono:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Sequ&lt;/span&gt;&lt;span class="err"&gt;ê&lt;/span&gt;&lt;span class="n"&gt;ncia&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NET&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;Linux&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="err"&gt;ó&lt;/span&gt;&lt;span class="n"&gt;digo&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;CoreCLR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;registra&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="nf"&gt;epoll_ctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EPOLL_CTL_ADD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Thread&lt;/span&gt; &lt;span class="n"&gt;devolvida&lt;/span&gt; &lt;span class="n"&gt;ao&lt;/span&gt; &lt;span class="n"&gt;ThreadPool&lt;/span&gt;
&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="n"&gt;chegam&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;
&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nf"&gt;epoll_wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nf"&gt;retorna&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;na&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt; &lt;span class="n"&gt;completion&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;CoreCLR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;enfileira&lt;/span&gt; &lt;span class="n"&gt;continua&lt;/span&gt;&lt;span class="err"&gt;çã&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;ThreadPool&lt;/span&gt;
&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Worker&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="n"&gt;pega&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;continua&lt;/span&gt;&lt;span class="err"&gt;çã&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="n"&gt;executa&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="err"&gt;ó&lt;/span&gt;&lt;span class="n"&gt;digo&lt;/span&gt; &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="err"&gt;ó&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt;

&lt;span class="err"&gt;┌─────────────────────────────────────────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NET&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt; &lt;span class="nf"&gt;Thread&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dedicada&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                      &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                                                 &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="n"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                        &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;epoll_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;              &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                       &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;       &lt;span class="nf"&gt;queue_continuation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="p"&gt;}&lt;/span&gt;                                           &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="p"&gt;}&lt;/span&gt;                                             &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;                                                 &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└─────────────────────────────────────────────────┘&lt;/span&gt;
         &lt;span class="err"&gt;│&lt;/span&gt;
         &lt;span class="err"&gt;▼&lt;/span&gt; &lt;span class="n"&gt;enfileira&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;ThreadPool&lt;/span&gt;
&lt;span class="err"&gt;┌─────────────────────────────────────────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="n"&gt;Worker&lt;/span&gt; &lt;span class="nf"&gt;Threads&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executam&lt;/span&gt; &lt;span class="n"&gt;continua&lt;/span&gt;&lt;span class="err"&gt;çõ&lt;/span&gt;&lt;span class="n"&gt;es&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="n"&gt;Thread&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;executa&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt;      &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="n"&gt;Thread&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;executa&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;DB&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;         &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="n"&gt;Thread&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;executa&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;HTTP&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;    &lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└─────────────────────────────────────────────────┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O .NET mantém 1-2 threads dedicadas chamando &lt;code&gt;epoll_wait()&lt;/code&gt; — essas são as &lt;strong&gt;I/O completion threads&lt;/strong&gt; (diferentes das worker threads). Elas nunca executam código do usuário diretamente — apenas enfileiram continuações.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exemplo Prático: Otimizando Aplicações ASP.NET Core em Containers
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Cenário: API em Kubernetes com latência alta no p99
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Ambiente:
- Kubernetes pod com limits: 4 CPU, 8GB RAM
- ASP.NET Core 8.0 API
- ~2000 req/s
- p50: 15ms, p95: 45ms, p99: 350ms &lt;span class="o"&gt;(!)&lt;/span&gt; ← problema

Diagnóstico:
&lt;span class="nv"&gt;$ &lt;/span&gt;dotnet-counters monitor &lt;span class="nt"&gt;--process-id&lt;/span&gt; 1 System.Runtime
    ThreadPool Thread Count:     87        ← muito alto para 4 cores!
    ThreadPool Queue Length:     12        ← work items esperando
    Monitor Lock Contention Count: 4521   ← contenção de locks

&lt;span class="nv"&gt;$ &lt;/span&gt;pidstat &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 1 1
    cswch/s: 8500    nvcswch/s: 2100      ← muitos involuntary switches!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problema identificado&lt;/strong&gt;: Thread pool cresceu demais (87 threads para 4 cores) → oversubscription severa → context switches excessivos → cache pollution → latência alta no p99.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Causas comuns&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Chamadas síncronas bloqueantes (sync-over-async)&lt;/li&gt;
&lt;li&gt;Lock contention forçando threads a bloquear&lt;/li&gt;
&lt;li&gt;ThreadPool adicionando threads porque as existentes estão bloqueadas&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Solução 1: Eliminar sync-over-async
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ ERRADO: bloqueia thread do pool esperando resultado&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="nf"&gt;GetData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// BLOQUEIA a thread!&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ CORRETO: libera thread durante I/O&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetDataAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// libera thread&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada &lt;code&gt;.Result&lt;/code&gt; ou &lt;code&gt;.Wait()&lt;/code&gt; bloqueia uma thread do pool. O hill climbing detecta threads bloqueadas e injeta novas → mais threads → mais context switches → degradação exponencial.&lt;/p&gt;

&lt;h4&gt;
  
  
  Solução 2: Limitar ThreadPool em containers
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Program.cs — configuração para containers&lt;/span&gt;
&lt;span class="c1"&gt;// Limita threads ao número de cores disponíveis no cgroup&lt;/span&gt;

&lt;span class="c1"&gt;// Para workloads I/O-bound (maioria das APIs):&lt;/span&gt;
&lt;span class="n"&gt;ThreadPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetMinThreads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;workerThreads&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessorCount&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;completionPortThreads&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessorCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;ThreadPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetMaxThreads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;workerThreads&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessorCount&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// cap em 4x cores&lt;/span&gt;
    &lt;span class="n"&gt;completionPortThreads&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessorCount&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Variáveis de ambiente para tuning em containers&lt;/span&gt;
&lt;span class="nv"&gt;DOTNET_ThreadPool_UnfairSemaphoreSpinLimit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0  &lt;span class="c"&gt;# reduz spin-wait (bom para containers)&lt;/span&gt;
&lt;span class="nv"&gt;DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1  &lt;span class="c"&gt;# reduz context switches para I/O&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Solução 3: CPU affinity via cgroups
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Kubernetes pod spec com CPU pinning&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4"&lt;/span&gt;          &lt;span class="c1"&gt;# garante 4 cores dedicados&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8Gi"&lt;/span&gt;
      &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4"&lt;/span&gt;          &lt;span class="c1"&gt;# mesmo valor = guaranteed QoS = CPU pinning&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8Gi"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quando &lt;code&gt;requests.cpu == limits.cpu&lt;/code&gt; no Kubernetes, o kubelet configura o cgroup com &lt;code&gt;cpuset&lt;/code&gt; — cores exclusivos. Isso elimina migração entre cores e reduz cache pollution.&lt;/p&gt;

&lt;h4&gt;
  
  
  Resultado após otimização
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Antes:                          Depois:
ThreadPool Threads: 87          ThreadPool Threads: 12
Queue Length: 12                Queue Length: 0
Context switches: 8500/s        Context switches: 1200/s
p50: 15ms                       p50: 12ms
p95: 45ms                       p95: 25ms
p99: 350ms                      p99: 55ms  ← 6.4x melhor!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CoreCLR e Interação com o Scheduler do Linux
&lt;/h3&gt;

&lt;p&gt;O CoreCLR (runtime do .NET no Linux) interage com o kernel scheduler de várias formas:&lt;/p&gt;

&lt;h4&gt;
  
  
  GC (Garbage Collector) e Escalonamento
&lt;/h4&gt;

&lt;p&gt;O GC do .NET pode causar &lt;strong&gt;stop-the-world pauses&lt;/strong&gt; que afetam o escalonamento:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server GC (recomendado para APIs):
- 1 GC thread por core (dedicadas)
- Durante GC: TODAS as threads da aplicação são suspensas
- Duração típica: 1-50ms (Gen2 full GC pode ser &amp;gt; 100ms)

Timeline durante GC:
Core 0: |── app ──|── GC ──|── app ──|
Core 1: |── app ──|── GC ──|── app ──|
Core 2: |── app ──|── GC ──|── app ──|
Core 3: |── app ──|── GC ──|── app ──|
                   ↑ todas as cores param
                     (visible no perf como pause)

Workstation GC (recomendado para containers com 1-2 cores):
- 1 GC thread compartilhada
- Menos overhead de memória
- Pausas maiores mas menos impacto em poucos cores
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Configuração de GC para containers&lt;/span&gt;
&lt;span class="nv"&gt;DOTNET_gcServer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1                    &lt;span class="c"&gt;# Server GC (se &amp;gt;= 2 cores)&lt;/span&gt;
&lt;span class="nv"&gt;DOTNET_GCHeapCount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4                 &lt;span class="c"&gt;# Limitar GC heaps (match com cores)&lt;/span&gt;
&lt;span class="nv"&gt;DOTNET_GCConserveMemory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5            &lt;span class="c"&gt;# 1-9: trade-off memória vs throughput&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Thread Suspension e Sinais
&lt;/h4&gt;

&lt;p&gt;O CoreCLR usa sinais POSIX (&lt;code&gt;SIGUSR1&lt;/code&gt;, &lt;code&gt;SIGUSR2&lt;/code&gt;) para suspender threads durante GC:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GC thread envia &lt;code&gt;SIGUSR2&lt;/code&gt; para todas as threads gerenciadas&lt;/li&gt;
&lt;li&gt;Signal handler em cada thread salva seu estado e sinaliza "safe point"&lt;/li&gt;
&lt;li&gt;GC executa (coleta, compacta)&lt;/li&gt;
&lt;li&gt;Threads são resumidas&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Esse mecanismo interage com o kernel scheduler — se uma thread está em &lt;code&gt;TASK_INTERRUPTIBLE&lt;/code&gt; (esperando I/O), o sinal a acorda imediatamente para que o GC possa prosseguir.&lt;/p&gt;

&lt;h3&gt;
  
  
  NUMA Awareness em Aplicações .NET
&lt;/h3&gt;

&lt;p&gt;Em servidores multi-socket (2+ CPUs físicas), a arquitetura &lt;strong&gt;NUMA&lt;/strong&gt; (Non-Uniform Memory Access) significa que acessar memória "local" (no mesmo nó) é significativamente mais rápido que memória "remota" (em outro nó):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Servidor dual-socket NUMA:

┌─────────────────────────┐    ┌─────────────────────────┐
│       NUMA Node 0       │    │       NUMA Node 1       │
│                         │    │                         │
│  CPU 0-7 (8 cores)      │    │  CPU 8-15 (8 cores)     │
│  RAM local: 64GB        │    │  RAM local: 64GB        │
│  Latência local: ~100ns │    │  Latência local: ~100ns │
│                         │    │                         │
└────────────┬────────────┘    └────────────┬────────────┘
             │                              │
             └────── QPI/UPI link ──────────┘
                   Latência remota: ~150-300ns
                   (1.5-3x mais lento!)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  .NET e NUMA
&lt;/h4&gt;

&lt;p&gt;O CoreCLR tem awareness básico de NUMA:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server GC&lt;/strong&gt; cria um GC heap por NUMA node (não por core)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ThreadPool&lt;/strong&gt; distribui threads entre nodes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alocações&lt;/strong&gt; são feitas preferencialmente na memória local ao core que está executando
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Verificando topologia NUMA em .NET&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Processor Count: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessorCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Em NUMA: retorna total de cores em todos os nodes&lt;/span&gt;

&lt;span class="c1"&gt;// Para workloads NUMA-sensitive, use CPU affinity:&lt;/span&gt;
&lt;span class="c1"&gt;// Exemplo: restringir processo a um único NUMA node&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Executando aplicação .NET em NUMA node específico&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;numactl &lt;span class="nt"&gt;--cpunodebind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;--membind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 dotnet MyApi.dll

&lt;span class="c"&gt;# Verificando distribuição de memória NUMA&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;numastat &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;pid&amp;gt;
Per-node process memory usage &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;in &lt;/span&gt;MBs&lt;span class="o"&gt;)&lt;/span&gt;
                 Node 0   Node 1    Total
                 &lt;span class="nt"&gt;------&lt;/span&gt;   &lt;span class="nt"&gt;------&lt;/span&gt;   &lt;span class="nt"&gt;------&lt;/span&gt;
Heap               512       48      560    ← idealmente tudo em Node 0
Stack               16        2       18
Private            128       12      140

&lt;span class="c"&gt;# Se há memória significativa no node "errado":&lt;/span&gt;
&lt;span class="c"&gt;# → alocação ocorreu em thread executando no outro node&lt;/span&gt;
&lt;span class="c"&gt;# → threadpool scheduling está causando acesso remoto&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Configuração NUMA para .NET em produção
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Opção 1: Processos separados por NUMA node&lt;/span&gt;
&lt;span class="c"&gt;# (Melhor isolamento, mais simples)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;numactl &lt;span class="nt"&gt;--cpunodebind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;--membind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 dotnet MyApi.dll &lt;span class="nt"&gt;--urls&lt;/span&gt; http://+:5000
&lt;span class="nv"&gt;$ &lt;/span&gt;numactl &lt;span class="nt"&gt;--cpunodebind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;--membind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 dotnet MyApi.dll &lt;span class="nt"&gt;--urls&lt;/span&gt; http://+:5001
&lt;span class="c"&gt;# Load balancer distribui entre as duas instâncias&lt;/span&gt;

&lt;span class="c"&gt;# Opção 2: Kubernetes com topology-aware scheduling&lt;/span&gt;
&lt;span class="c"&gt;# topology.kubernetes.io/zone anotações para NUMA-aware placement&lt;/span&gt;

&lt;span class="c"&gt;# Opção 3: Configuração de GC NUMA-aware&lt;/span&gt;
&lt;span class="nv"&gt;DOTNET_gcServer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="nv"&gt;DOTNET_GCHeapCount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8        &lt;span class="c"&gt;# heaps = cores por NUMA node&lt;/span&gt;
&lt;span class="nv"&gt;DOTNET_GCNoAffinitize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0     &lt;span class="c"&gt;# permitir GC affinitizar threads&lt;/span&gt;
&lt;span class="nv"&gt;DOTNET_GCHeapAffinitizeMask&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0xFF  &lt;span class="c"&gt;# cores 0-7 (Node 0)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Regra prática para .NET e NUMA&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Servidores single-socket (maioria na cloud): NUMA não é preocupação&lt;/li&gt;
&lt;li&gt;Servidores dual-socket (bare metal, databases): Configure &lt;code&gt;numactl&lt;/code&gt; ou use instâncias separadas por node&lt;/li&gt;
&lt;li&gt;Containers em Kubernetes: Use &lt;code&gt;topologySpreadConstraints&lt;/code&gt; e resource limits que se alinham com NUMA boundaries&lt;/li&gt;
&lt;li&gt;Monitore com &lt;code&gt;numastat&lt;/code&gt; e &lt;code&gt;perf stat -e node-load-misses&lt;/code&gt; — se acesso remoto &amp;gt; 10%, otimize&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Conexão com Desenvolvimento Backend: Golang
&lt;/h2&gt;

&lt;p&gt;O Go é único entre as linguagens backend mainstream por implementar um verdadeiro modelo &lt;strong&gt;M:N&lt;/strong&gt; de threading — goroutines (user-level threads) são multiplexadas sobre um número menor de kernel threads pelo runtime scheduler. Essa arquitetura permite criar milhões de unidades de concorrência com overhead mínimo, mas a interação com o kernel scheduler do Linux introduz nuances que todo desenvolvedor Go precisa compreender.&lt;/p&gt;

&lt;h3&gt;
  
  
  Goroutines vs Kernel Threads (M:N Threading Model)
&lt;/h3&gt;

&lt;p&gt;Uma goroutine &lt;strong&gt;não é&lt;/strong&gt; uma thread do sistema operacional. É uma unidade de execução gerenciada pelo Go runtime, com custo de criação e memória drasticamente menor:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Característica&lt;/th&gt;
&lt;th&gt;Goroutine&lt;/th&gt;
&lt;th&gt;Kernel Thread (OS Thread)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stack inicial&lt;/td&gt;
&lt;td&gt;~2KB (cresce dinamicamente até 1GB)&lt;/td&gt;
&lt;td&gt;~2-8MB (fixa, definida por &lt;code&gt;ulimit -s&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custo de criação&lt;/td&gt;
&lt;td&gt;~0.3μs&lt;/td&gt;
&lt;td&gt;~10-50μs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context switch&lt;/td&gt;
&lt;td&gt;~0.1-0.2μs (userspace)&lt;/td&gt;
&lt;td&gt;~1-3μs (kernel)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quantidade típica&lt;/td&gt;
&lt;td&gt;Milhares a milhões&lt;/td&gt;
&lt;td&gt;Centenas a baixos milhares&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Escalonamento&lt;/td&gt;
&lt;td&gt;Go runtime scheduler&lt;/td&gt;
&lt;td&gt;Kernel CFS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preempção&lt;/td&gt;
&lt;td&gt;Cooperativa + async (Go 1.14+)&lt;/td&gt;
&lt;td&gt;Preemptiva (timer interrupt)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Modelo M:N do Go:

                    Userspace (Go runtime)
┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│   G1  G2  G3  G4  G5  G6  G7  G8 ... G100000                    │
│   │   │   │   │   │   │   │   │                                 │
│   └─┬─┘   │   └─┬─┘   │   └─┬─┘                                 │
│     │     │     │     │     │      ← goroutines (user threads)  │
│     ▼     ▼     ▼     ▼     ▼                                   │
│   ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐                                 │
│   │P0 │ │P1 │ │P2 │ │P3 │ │P4 │   ← P (Logical Processors)      │
│   │LRQ│ │LRQ│ │LRQ│ │LRQ│ │LRQ│     cada P tem local run queue  │
│   └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘                                 │
│     │     │     │     │     │                                   │
│     ▼     ▼     ▼     ▼     ▼                                   │
│   ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐                                 │
│   │M0 │ │M1 │ │M2 │ │M3 │ │M4 │   ← M (OS Threads/Machines)     │
│   └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘     mapeiam para task_struct    │
├─────┼─────┼─────┼─────┼─────┼───────────────────────────────────┤
│     ▼     ▼     ▼     ▼     ▼         Kernel                    │
│   KT0   KT1   KT2   KT3   KT4        (kernel threads)           │
│   ┌─────────────────────────────┐                               │
│   │     CFS Scheduler           │                               │
│   └─────────────────────────────┘                               │
└─────────────────────────────────────────────────────────────────┘

Terminologia:
  G = Goroutine (unidade de execução leve)
  P = Processor (contexto lógico, contém run queue local)
  M = Machine (kernel thread real, escalonada pelo CFS)

Relação: muitos G → poucos P → poucos M → cores do hardware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A chave do modelo é: &lt;strong&gt;G (goroutines) &amp;gt;&amp;gt; P (processors) &amp;gt;= M (OS threads) ≈ cores&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  O Scheduler do Go Runtime e sua Relação com o Kernel
&lt;/h3&gt;

&lt;p&gt;O Go scheduler opera em userspace, executando dentro de cada M (OS thread). Ele toma decisões de escalonamento sem envolver o kernel — o que elimina o overhead de syscalls para context switches entre goroutines.&lt;/p&gt;

&lt;h4&gt;
  
  
  Componentes do scheduler (GMP model)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Anatomia de um P (Logical Processor):

P (Processor)
├── Local Run Queue (LRQ)
│   └── [G5] → [G12] → [G31] → ...    ← fila FIFO de goroutines prontas
├── Current G                            ← goroutine em execução
├── mcache                               ← cache de memória por-P (performance)
├── Timer heap                           ← goroutines dormindo (time.Sleep, etc.)
└── Runnext                              ← próxima G a executar (fast path)

Global Run Queue (GRQ):
└── [G99] → [G200] → [G345] → ...      ← overflow das LRQs, acessada com lock

Idle M list:
└── M5 → M6 → M7 → ...                 ← threads do kernel ociosas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Quando o Go scheduler roda (scheduling points)
&lt;/h4&gt;

&lt;p&gt;O scheduler é invocado (em userspace) em pontos específicos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;Pontos&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;escalonamento&lt;/span&gt; &lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="n"&gt;Go&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="m"&gt;1.&lt;/span&gt; &lt;span class="n"&gt;Chamada&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;função&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="n"&gt;prologue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;Verifica&lt;/span&gt; &lt;span class="n"&gt;se&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="n"&gt;precisa&lt;/span&gt; &lt;span class="n"&gt;crescer&lt;/span&gt;
   &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;Verifica&lt;/span&gt; &lt;span class="n"&gt;preemption&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Go&lt;/span&gt; &lt;span class="m"&gt;1.14&lt;/span&gt;&lt;span class="o"&gt;+:&lt;/span&gt; &lt;span class="n"&gt;sinal&lt;/span&gt; &lt;span class="n"&gt;SIGURG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="m"&gt;2.&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt; &lt;span class="n"&gt;operations&lt;/span&gt;
   &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt; &lt;span class="n"&gt;bloqueante&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;receive&lt;/span&gt; &lt;span class="n"&gt;bloqueante&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="m"&gt;3.&lt;/span&gt; &lt;span class="n"&gt;Blocking&lt;/span&gt; &lt;span class="n"&gt;syscalls&lt;/span&gt;
   &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;em&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt; &lt;span class="n"&gt;é&lt;/span&gt; &lt;span class="n"&gt;liberado&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="n"&gt;migra&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="n"&gt;outro&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt;

&lt;span class="m"&gt;4.&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Gosched&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;explícito&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raro&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt; &lt;span class="n"&gt;código&lt;/span&gt; &lt;span class="n"&gt;moderno&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="m"&gt;5.&lt;/span&gt; &lt;span class="n"&gt;Garbage&lt;/span&gt; &lt;span class="n"&gt;Collection&lt;/span&gt;
   &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;STW&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;phases&lt;/span&gt;
   &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;GC&lt;/span&gt; &lt;span class="n"&gt;assist&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;goroutine&lt;/span&gt; &lt;span class="n"&gt;ajuda&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;marking&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="m"&gt;6.&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;timer&lt;/span&gt; &lt;span class="n"&gt;expiration&lt;/span&gt;
   &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;Goroutine&lt;/span&gt; &lt;span class="n"&gt;vai&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="n"&gt;timer&lt;/span&gt; &lt;span class="n"&gt;heap&lt;/span&gt; &lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;

&lt;span class="m"&gt;7.&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="n"&gt;primitives&lt;/span&gt;
   &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quando&lt;/span&gt; &lt;span class="n"&gt;contended&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Interação com o kernel: blocking syscalls
&lt;/h4&gt;

&lt;p&gt;O aspecto mais importante da relação Go runtime ↔ kernel é o tratamento de &lt;strong&gt;syscalls bloqueantes&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cenário: goroutine G1 faz syscall bloqueante (ex: file read)

ANTES da syscall:
  P0 ←→ M0: executando G1
  P0.LRQ: [G2, G3, G4]   ← goroutines esperando

DURANTE a syscall:
  1. Go runtime detecta que G1 vai bloquear
  2. P0 se DESACOPLA de M0
  3. P0 se ACOPLA a M1 (um M idle, ou cria novo M)
  4. M1 começa a executar G2 da LRQ de P0
  5. M0 continua bloqueado no kernel com G1

  P0 ←→ M1: executando G2       ← P continua produtivo!
  M0: bloqueado em read() com G1 ← kernel thread bloqueada

APÓS a syscall retornar:
  1. M0 acorda com G1
  2. Tenta re-adquirir P0 (ou qualquer P idle)
  3. Se consegue: G1 volta a executar
  4. Se não: G1 vai para Global Run Queue
  5. M0 vai para idle list

Timeline:
M0: |─── G1 ───|── read() bloqueante ──|── G1 retoma ──|
M1:             |── G2 ──|── G3 ──|── G4 ──|
                ↑
         P migra para M1 (latência ~μs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse mecanismo é o que permite ao Go ter I/O "assíncrono" sem &lt;code&gt;async/await&lt;/code&gt; — do ponto de vista do programador, o código é síncrono e sequencial, mas o runtime garante que outras goroutines continuam executando.&lt;/p&gt;

&lt;h4&gt;
  
  
  Network poller (netpoller)
&lt;/h4&gt;

&lt;p&gt;Para I/O de rede, o Go usa um mecanismo diferente — o &lt;strong&gt;netpoller&lt;/strong&gt;, baseado em &lt;code&gt;epoll&lt;/code&gt; no Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;Network&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;não&lt;/span&gt; &lt;span class="n"&gt;bloqueia&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="m"&gt;1.&lt;/span&gt; &lt;span class="n"&gt;goroutine&lt;/span&gt; &lt;span class="n"&gt;chama&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="m"&gt;2.&lt;/span&gt; &lt;span class="n"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;setsockopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;O_NONBLOCK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;3.&lt;/span&gt; &lt;span class="n"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tenta&lt;/span&gt; &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;EAGAIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nada&lt;/span&gt; &lt;span class="n"&gt;disponível&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;4.&lt;/span&gt; &lt;span class="n"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;registra&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt; &lt;span class="n"&gt;epoll&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;parks&lt;/span&gt; &lt;span class="n"&gt;goroutine&lt;/span&gt;
&lt;span class="m"&gt;5.&lt;/span&gt; &lt;span class="n"&gt;Goroutine&lt;/span&gt; &lt;span class="n"&gt;sai&lt;/span&gt; &lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;não&lt;/span&gt; &lt;span class="n"&gt;ocupa&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;6.&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="n"&gt;executa&lt;/span&gt; &lt;span class="n"&gt;outras&lt;/span&gt; &lt;span class="n"&gt;goroutines&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="n"&gt;chegam&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="m"&gt;7.&lt;/span&gt; &lt;span class="n"&gt;Sysmon&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ou&lt;/span&gt; &lt;span class="n"&gt;outro&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;epoll_wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;detecta&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="n"&gt;ready&lt;/span&gt;
&lt;span class="m"&gt;8.&lt;/span&gt; &lt;span class="n"&gt;Goroutine&lt;/span&gt; &lt;span class="n"&gt;é&lt;/span&gt; &lt;span class="n"&gt;colocada&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;volta&lt;/span&gt; &lt;span class="n"&gt;na&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;
&lt;span class="m"&gt;9.&lt;/span&gt; &lt;span class="n"&gt;Goroutine&lt;/span&gt; &lt;span class="n"&gt;retoma&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="n"&gt;disponíveis&lt;/span&gt;

&lt;span class="n"&gt;Diferença&lt;/span&gt; &lt;span class="n"&gt;crucial&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BLOQUEIA&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kernel&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="n"&gt;fica&lt;/span&gt; &lt;span class="n"&gt;presa&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Network&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NÃO&lt;/span&gt; &lt;span class="n"&gt;bloqueia&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epoll&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;park&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;unpark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;Implicação&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;goroutines&lt;/span&gt; &lt;span class="n"&gt;fazendo&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt; &lt;span class="n"&gt;OS&lt;/span&gt; &lt;span class="n"&gt;threads&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;goroutines&lt;/span&gt; &lt;span class="n"&gt;fazendo&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;pode&lt;/span&gt; &lt;span class="n"&gt;criar&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;OS&lt;/span&gt; &lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GOMAXPROCS e CPU Affinity
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;GOMAXPROCS&lt;/code&gt; controla o número de P's (processors lógicos) — efetivamente o paralelismo máximo de execução de goroutines em Go code (não syscalls).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="n"&gt;sua&lt;/span&gt; &lt;span class="n"&gt;relação&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;hardware&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;Core&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G1&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G2&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G1&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G3&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G2&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="n"&gt;paralelismo&lt;/span&gt; &lt;span class="n"&gt;Go&lt;/span&gt;
  &lt;span class="n"&gt;Core&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idle&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="n"&gt;Go&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                  &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="n"&gt;pode&lt;/span&gt; &lt;span class="n"&gt;ter&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;em&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;Útil&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;debugging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eliminação&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;race&lt;/span&gt; &lt;span class="n"&gt;conditions&lt;/span&gt;

&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;em&lt;/span&gt; &lt;span class="n"&gt;máquina&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="n"&gt;cores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;Core&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G1&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G5&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G1&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;Core&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G2&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G6&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G2&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;              &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="n"&gt;paralelismo&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
  &lt;span class="n"&gt;Core&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G3&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G7&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G3&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;Core&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G4&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G8&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="n"&gt;G4&lt;/span&gt;&lt;span class="err"&gt;─&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;Default&lt;/span&gt; &lt;span class="n"&gt;desde&lt;/span&gt; &lt;span class="n"&gt;Go&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumCPU&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;em&lt;/span&gt; &lt;span class="n"&gt;máquina&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="n"&gt;cores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;Core&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P0&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P4&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P0&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P4&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;                  &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="n"&gt;oversubscription&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
  &lt;span class="n"&gt;Core&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P1&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P5&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P1&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P5&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;                  &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="n"&gt;switches&lt;/span&gt; &lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;
  &lt;span class="n"&gt;Core&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P2&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P6&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P2&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P6&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;                  &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="n"&gt;entre&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;dos&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
  &lt;span class="n"&gt;Core&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P3&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P7&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P3&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;P7&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;Geralmente&lt;/span&gt; &lt;span class="n"&gt;prejudicial&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="n"&gt;workloads&lt;/span&gt; &lt;span class="n"&gt;CPU&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;bound&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;Pode&lt;/span&gt; &lt;span class="n"&gt;ajudar&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="n"&gt;workloads&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="n"&gt;muitas&lt;/span&gt; &lt;span class="n"&gt;syscalls&lt;/span&gt; &lt;span class="n"&gt;bloqueantes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  GOMAXPROCS em containers
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Problema crítico&lt;/strong&gt;: Em containers com CPU limits, &lt;code&gt;runtime.NumCPU()&lt;/code&gt; retorna o número de cores do &lt;strong&gt;host&lt;/strong&gt;, não do container!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Em um container com cpu.max = "200000 100000" (2 cores):&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumCPU&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;      &lt;span class="c"&gt;// Pode imprimir 64! (cores do host)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c"&gt;// GOMAXPROCS = 64 por default!&lt;/span&gt;

&lt;span class="c"&gt;// Resultado: 64 P's competindo por 2 cores de CPU quota&lt;/span&gt;
&lt;span class="c"&gt;// → Excessive context switches no kernel&lt;/span&gt;
&lt;span class="c"&gt;// → Throttling pelo cgroup CPU controller&lt;/span&gt;
&lt;span class="c"&gt;// → Latência imprevisível&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solução&lt;/strong&gt;: Use &lt;code&gt;automaxprocs&lt;/code&gt; (library da Uber) ou configure manualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"go.uber.org/automaxprocs"&lt;/span&gt; &lt;span class="c"&gt;// Detecta cgroup limits automaticamente&lt;/span&gt;

&lt;span class="c"&gt;// Ou manualmente:&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;quota&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;getCGroupCPUQuota&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;quota&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quota&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Verificar se GOMAXPROCS está correto&lt;/span&gt;
&lt;span class="nv"&gt;$ GODEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;schedtrace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1000 ./myservice 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-3&lt;/span&gt;
SCHED 0ms: &lt;span class="nv"&gt;gomaxprocs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;idleprocs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;threads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="nv"&gt;idlethreads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
SCHED 1000ms: &lt;span class="nv"&gt;gomaxprocs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;idleprocs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;threads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 &lt;span class="nv"&gt;idlethreads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
SCHED 2000ms: &lt;span class="nv"&gt;gomaxprocs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;idleprocs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;threads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 &lt;span class="nv"&gt;idlethreads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1

&lt;span class="c"&gt;# gomaxprocs=2 ← deve corresponder ao CPU limit do container&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  CPU Affinity e Go
&lt;/h4&gt;

&lt;p&gt;O Go runtime não configura CPU affinity por default — os M's (OS threads) podem migrar entre cores livremente. Para workloads latency-sensitive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Pinning do processo Go a cores específicos&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;taskset &lt;span class="nt"&gt;-c&lt;/span&gt; 0-3 ./myservice

&lt;span class="c"&gt;# Ou via cgroups (Kubernetes):&lt;/span&gt;
&lt;span class="c"&gt;# resources.requests.cpu == resources.limits.cpu → cpuset pinning&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Dentro do Go, para pin goroutine a OS thread:&lt;/span&gt;
&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LockOSThread&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c"&gt;// Esta goroutine fica presa neste M&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UnlockOSThread&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;// Use cases:&lt;/span&gt;
&lt;span class="c"&gt;// - CGO com thread-local state&lt;/span&gt;
&lt;span class="c"&gt;// - OpenGL/GPU contexts&lt;/span&gt;
&lt;span class="c"&gt;// - Real-time goroutines que precisam de CPU dedicada&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Análise de Performance: Blocking Syscalls e Goroutines
&lt;/h3&gt;

&lt;p&gt;A principal armadilha de performance em Go é o excesso de &lt;strong&gt;OS threads criados por syscalls bloqueantes&lt;/strong&gt; — cada goroutine que bloqueia em file I/O, CGO, ou certain syscalls consome um M inteiro.&lt;/p&gt;

&lt;h4&gt;
  
  
  Diagnóstico: threads demais
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Monitorando OS threads do processo Go&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&amp;lt;pid&amp;gt;/status | &lt;span class="nb"&gt;grep &lt;/span&gt;Threads
Threads: 847    ← se muito maior que GOMAXPROCS, há goroutines em syscalls

&lt;span class="c"&gt;# Trace detalhado do scheduler&lt;/span&gt;
&lt;span class="nv"&gt;$ GODEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;schedtrace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1000,scheddetail&lt;span class="o"&gt;=&lt;/span&gt;1 ./myservice 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"^SCHED|threads"&lt;/span&gt;
SCHED 1000ms: &lt;span class="nv"&gt;gomaxprocs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="nv"&gt;idleprocs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;threads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;847 &lt;span class="nv"&gt;idlethreads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2
              &lt;span class="nv"&gt;runqueue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12 &lt;span class="o"&gt;[&lt;/span&gt;45 38 52 41]
&lt;span class="c"&gt;#                         ↑ LRQs dos P's (goroutines esperando)&lt;/span&gt;
&lt;span class="c"&gt;# threads=847: muitas goroutines bloqueadas em syscalls!&lt;/span&gt;
&lt;span class="c"&gt;# runqueue=12 + [45+38+52+41] = 188 goroutines ready mas sem P livre&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cenários problemáticos e soluções
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;Problema&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt; &lt;span class="n"&gt;massivo&lt;/span&gt;

&lt;span class="c"&gt;// ❌ Cada goroutine bloqueia um M em file read&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// bloqueia M!&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;// Com 10000 files: pode criar 10000 OS threads!&lt;/span&gt;

&lt;span class="c"&gt;// ✅ Limitar concorrência com semaphore&lt;/span&gt;
&lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// max 64 file I/O simultâneos&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="p"&gt;}()&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;Problema&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CGO&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="n"&gt;bloqueantes&lt;/span&gt;

&lt;span class="c"&gt;// CGO: TODA chamada C bloqueia o M&lt;/span&gt;
&lt;span class="c"&gt;// O runtime NÃO pode preemptar código C&lt;/span&gt;

&lt;span class="c"&gt;/*
#include &amp;lt;unistd.h&amp;gt;
void slow_c_function() {
    sleep(5);  // bloqueia M por 5 segundos!
}
*/&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt;

&lt;span class="c"&gt;// ✅ Limitar goroutines que chamam CGO&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cgoSem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;callCGO&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cgoSem&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;cgoSem&lt;/span&gt; &lt;span class="p"&gt;}()&lt;/span&gt;
    &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slow_c_function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;Problema&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DNS&lt;/span&gt; &lt;span class="n"&gt;resolution&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usa&lt;/span&gt; &lt;span class="n"&gt;CGO&lt;/span&gt; &lt;span class="n"&gt;por&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;Linux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// net.LookupHost usa CGO → bloqueia M&lt;/span&gt;
&lt;span class="c"&gt;// Sob carga alta, pode criar centenas de threads&lt;/span&gt;

&lt;span class="c"&gt;// ✅ Solução: usar pure Go resolver&lt;/span&gt;
&lt;span class="c"&gt;// export GODEBUG=netdns=go&lt;/span&gt;
&lt;span class="c"&gt;// ou no código:&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"net"&lt;/span&gt; &lt;span class="c"&gt;// com build tag: -tags netgo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Ferramentas de diagnóstico
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Runtime trace (visualização gráfica)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://localhost:6060/debug/pprof/trace?seconds&lt;span class="o"&gt;=&lt;/span&gt;5 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; trace.out
&lt;span class="nv"&gt;$ &lt;/span&gt;go tool trace trace.out
&lt;span class="c"&gt;# Mostra: goroutine scheduling, syscalls, network I/O, GC&lt;/span&gt;

&lt;span class="c"&gt;# 2. Goroutine profile&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://localhost:6060/debug/pprof/goroutine?debug&lt;span class="o"&gt;=&lt;/span&gt;2
&lt;span class="c"&gt;# Lista TODAS goroutines com stack traces&lt;/span&gt;
&lt;span class="c"&gt;# Procure por: "syscall" no stack = goroutine bloqueando M&lt;/span&gt;

&lt;span class="c"&gt;# 3. Thread create profile&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://localhost:6060/debug/pprof/threadcreate?debug&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="c"&gt;# Mostra onde threads foram criadas (indica syscalls bloqueantes)&lt;/span&gt;

&lt;span class="c"&gt;# 4. perf (kernel-level view)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;perf &lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; context-switches,cpu-migrations &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;pid&amp;gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;10
&lt;span class="c"&gt;# context-switches alto + muitos threads = problema de blocking syscalls&lt;/span&gt;

&lt;span class="c"&gt;# 5. Scheduler latency&lt;/span&gt;
&lt;span class="nv"&gt;$ GODEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;schedtrace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1000 ./myservice
&lt;span class="c"&gt;# Campos importantes:&lt;/span&gt;
&lt;span class="c"&gt;# - runqueue: goroutines na global queue (&amp;gt; 0 = P's saturados)&lt;/span&gt;
&lt;span class="c"&gt;# - [n n n n]: goroutines por P na LRQ (desbalanceado = work stealing falhou)&lt;/span&gt;
&lt;span class="c"&gt;# - idleprocs: P's ociosos (&amp;gt; 0 com runqueue &amp;gt; 0 = bug ou lock contention)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Exemplo Prático: Microserviços Go e Tuning de Concorrência
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Cenário: API Gateway em Go com latência degradada sob carga
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Ambiente:
- Kubernetes: 4 CPU limit, 4GB RAM
- Go 1.22, ~50k req/s
- Cada request faz: 2-3 chamadas HTTP a backends + 1 Redis lookup
- p50: 8ms, p95: 25ms, p99: 180ms &lt;span class="o"&gt;(!)&lt;/span&gt; ← degradação no p99

Observações iniciais:
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&amp;lt;pid&amp;gt;/status
Threads: 312      ← alto para &lt;span class="nv"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="o"&gt;(&lt;/span&gt;deveria ser ~10-20&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;$ GODEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;schedtrace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5000 ./gateway 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
SCHED 5000ms: &lt;span class="nv"&gt;gomaxprocs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="nv"&gt;idleprocs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;threads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;312 &lt;span class="nv"&gt;idlethreads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;280
              &lt;span class="nv"&gt;runqueue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="o"&gt;[&lt;/span&gt;2 1 3 0]

Análise:
- &lt;span class="nv"&gt;threads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;312 mas &lt;span class="nv"&gt;idlethreads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;280 → 32 threads ativas em algum momento
- 280 threads idle &lt;span class="o"&gt;=&lt;/span&gt; foram criadas para syscalls e não foram recicladas
- runqueue baixo &lt;span class="o"&gt;=&lt;/span&gt; não é falta de P&lt;span class="s1"&gt;'s
- O problema é CRIAÇÃO EXCESSIVA de threads por syscalls bloqueantes
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Diagnóstico profundo
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Goroutine dump&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl localhost:6060/debug/pprof/goroutine?debug&lt;span class="o"&gt;=&lt;/span&gt;2 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"syscall"&lt;/span&gt;
28    ← 28 goroutines bloqueadas em syscalls neste instante

&lt;span class="c"&gt;# Stack traces das goroutines em syscall:&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl localhost:6060/debug/pprof/goroutine?debug&lt;span class="o"&gt;=&lt;/span&gt;2 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-B5&lt;/span&gt; &lt;span class="s2"&gt;"syscall"&lt;/span&gt;
&lt;span class="c"&gt;# Revela: net/http.(*Transport).dialConn → net.(*Resolver).lookupHost → CGO!&lt;/span&gt;

&lt;span class="c"&gt;# perf para confirmar context switches&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;perf &lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;pid&amp;gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;10
    45,230  context-switches    ← ~4500/s, alto para 4 cores
     2,890  cpu-migrations      ← threads migrando entre cores
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Causa raiz&lt;/strong&gt;: DNS resolution via CGO criando threads excessivas + HTTP client sem connection pooling adequado.&lt;/p&gt;

&lt;h4&gt;
  
  
  Solução 1: Pure Go DNS resolver
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// main.go — forçar resolver Go puro&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"net"&lt;/span&gt; &lt;span class="c"&gt;// build com: go build -tags netgo&lt;/span&gt;

&lt;span class="c"&gt;// Ou via variável de ambiente:&lt;/span&gt;
&lt;span class="c"&gt;// GODEBUG=netdns=go ./gateway&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Solução 2: HTTP client com connection pooling otimizado
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ❌ Default: limites conservadores&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;  &lt;span class="c"&gt;// MaxIdleConnsPerHost = 2 (!)&lt;/span&gt;

&lt;span class="c"&gt;// ✅ Otimizado para alta concorrência&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;MaxIdleConns&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;MaxIdleConnsPerHost&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c"&gt;// match com concorrência esperada&lt;/span&gt;
        &lt;span class="n"&gt;MaxConnsPerHost&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c"&gt;// cap total de conexões por host&lt;/span&gt;
        &lt;span class="n"&gt;IdleConnTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="m"&gt;90&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="c"&gt;// Tuning TCP-level&lt;/span&gt;
        &lt;span class="n"&gt;DialContext&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dialer&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;KeepAlive&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DialContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="c"&gt;// Disable HTTP/2 se backends não suportam multiplexing&lt;/span&gt;
        &lt;span class="n"&gt;ForceAttemptHTTP2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Solução 3: GOMAXPROCS correto + automaxprocs
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"go.uber.org/automaxprocs"&lt;/span&gt;

&lt;span class="c"&gt;// Resultado: GOMAXPROCS=4 (correto para o container)&lt;/span&gt;
&lt;span class="c"&gt;// Sem automaxprocs em container com 4 CPU limit em host de 64 cores:&lt;/span&gt;
&lt;span class="c"&gt;// GOMAXPROCS=64 → 64 P's criando work que 4 cores não conseguem executar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Solução 4: Limitar concorrência de operações bloqueantes
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Semaphore para limitar file/disk I/O concurrent&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;diskSem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWeighted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GOMAXPROCS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;readConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;diskSem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Acquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;diskSem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Resultado após otimização
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Antes:                          Depois:
Threads: 312                    Threads: 18
Context switches: 4500/s        Context switches: 800/s
CPU migrations: 290/s           CPU migrations: 45/s
p50: 8ms                        p50: 6ms
p95: 25ms                       p95: 15ms
p99: 180ms                      p99: 35ms  ← 5x melhor!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Monitoramento contínuo em produção
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Expor métricas do runtime para Prometheus&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/prometheus/client_golang/prometheus"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Goroutines ativas&lt;/span&gt;
    &lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustRegister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewGaugeFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GaugeOpts&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"go_goroutines"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumGoroutine&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c"&gt;// OS threads&lt;/span&gt;
    &lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustRegister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewGaugeFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GaugeOpts&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"go_threads"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ThreadCreateProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Alertas recomendados (Prometheus):&lt;/span&gt;
&lt;span class="c"&gt;# - go_threads &amp;gt; GOMAXPROCS * 10 → muitas blocking syscalls&lt;/span&gt;
&lt;span class="c"&gt;# - go_goroutines &amp;gt; 100000 → possível goroutine leak&lt;/span&gt;
&lt;span class="c"&gt;# - rate(go_sched_latencies_seconds_sum[5m]) &amp;gt; 0.001 → scheduler saturado&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Regras práticas para Go em produção&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;GOMAXPROCS&lt;/code&gt; = CPU limit do container (use &lt;code&gt;automaxprocs&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;go_threads&lt;/code&gt; deve ser &amp;lt; &lt;code&gt;GOMAXPROCS * 5&lt;/code&gt; — mais indica blocking syscalls&lt;/li&gt;
&lt;li&gt;Use pure Go DNS resolver (&lt;code&gt;GODEBUG=netdns=go&lt;/code&gt; ou &lt;code&gt;-tags netgo&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;MaxIdleConnsPerHost&lt;/code&gt; no HTTP client (default 2 é muito baixo!)&lt;/li&gt;
&lt;li&gt;Limite concorrência de file I/O e CGO com semaphores&lt;/li&gt;
&lt;li&gt;Monitore com &lt;code&gt;GODEBUG=schedtrace&lt;/code&gt; em staging, pprof em produção&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Referências Bibliográficas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Livros
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Desenvolvimento-do-Kernel-Linux/dp/8573933410" rel="noopener noreferrer"&gt;Desenvolvimento do Kernel do Linux — Robert Love (David Cram, trad.)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Linux-Bible-Christopher-Negus/dp/1394317468/ref=sr_1_1?__mk_pt_BR=%C3%85M%C3%85%C5%BD%C3%95%C3%91&amp;amp;dib=eyJ2IjoiMSJ9.ep2zHSLrXTmnOmqryZZJPcwOnbqsPqlHDKyK_8FK75E7IdfT3OQ4iSeLNg4aDkEbas_KyjlckRv_HAqF0-rXbwY0A7IAJnyqEquSkUVLVco_qSolsvkdEK8LeRJ7GQcp8e8AIbQoxZMwHdkqzqy0WHcbLqaF3pcBaRdo4HBaO_m9ZJTLKY9TXza9uJCvonORaFc81XM-Gp76W7qwYVmuo33vr9HQHPeeyrrK2rw_dPY.CC-nHGj2vuWkT6U5wHlf4BLCa2H5hJDXH_Xg72Hue10&amp;amp;dib_tag=se&amp;amp;keywords=linux+a+biblia&amp;amp;qid=1780931929&amp;amp;s=books&amp;amp;sprefix=linux+a+bi%2Cstripbooks%2C1135&amp;amp;sr=1-1&amp;amp;ufe=app_do%3Aamzn1.fos.fcd6d665-32ba-4479-9f21-b774e276a678" rel="noopener noreferrer"&gt;Linux Bible — Christopher Negus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Sistemas-Operacionais-Modernos-Andrew-Tanenbaum/dp/8582606168/ref=sr_1_1?__mk_pt_BR=%C3%85M%C3%85%C5%BD%C3%95%C3%91&amp;amp;dib=eyJ2IjoiMSJ9.GT4sX07Q-JQVNuedOvqQ5ZO7y1vPyznY4qtp_jih_s6jnDsrFJut_q6oT6io7p-I4c2hke9cKBU-DXK1GrwEjyvNZQbXAMjxsM1C6oDQqUybKWMEHkoJo3VQvzLYVU4XXCGkjDiNVI_fYu7spu33HDSpcBcZ891_HBZu4218XEvpnWNCWv6D5pM2XF0qZnFJeNTYoTSbSf6aldeB0RoH1cQ62o63NXV8a8HNh9qdNJs.anlokyxWbWuareiNAhSAhsoxohIr4FfNT9TcagLW980&amp;amp;dib_tag=se&amp;amp;keywords=Sistemas+Operacionais&amp;amp;qid=1780931998&amp;amp;s=books&amp;amp;sr=1-1&amp;amp;ufe=app_do%3Aamzn1.fos.fcd6d665-32ba-4479-9f21-b774e276a678" rel="noopener noreferrer"&gt;Sistemas Operacionais Modernos — Andrew Tanenbaum&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.brendangregg.com/systems-performance.html" rel="noopener noreferrer"&gt;Systems Performance, 2nd Edition — Brendan Gregg&lt;/a&gt; &lt;em&gt;(fonte dos valores de benchmark de fork/clone/context switch)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Documentação Oficial
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/arch/x86/topology.html#threads" rel="noopener noreferrer"&gt;Linux Kernel Documentation — Threads Topology (x86)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.ibm.com/articles/l-linux-kernel/" rel="noopener noreferrer"&gt;Linux Kernel: An Introduction — IBM Developer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://elixir.bootlin.com/linux/latest/source/kernel/sched/core.c" rel="noopener noreferrer"&gt;Linux kernel source — context_switch() em kernel/sched/core.c&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://elixir.bootlin.com/linux/latest/source/kernel/sched/fair.c" rel="noopener noreferrer"&gt;Linux kernel source — CFS Scheduler (kernel/sched/fair.c)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.dev/ref/spec#Go_statements" rel="noopener noreferrer"&gt;The Go Programming Language Specification — Goroutines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.dev/src/runtime/HACKING.md" rel="noopener noreferrer"&gt;Go runtime scheduler design document (GMP model)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool" rel="noopener noreferrer"&gt;.NET ThreadPool documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/performance/performance-best-practices" rel="noopener noreferrer"&gt;ASP.NET Core performance best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/x86/pti.html" rel="noopener noreferrer"&gt;KPTI — Kernel Page-Table Isolation (Meltdown mitigation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lwn.net/Articles/741853/" rel="noopener noreferrer"&gt;PCID support in the Linux kernel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Ferramentas de Observabilidade
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://perf.wiki.kernel.org/index.php/Main_Page" rel="noopener noreferrer"&gt;&lt;code&gt;perf&lt;/code&gt;&lt;/a&gt; — profiler e ferramenta de performance do kernel Linux &lt;em&gt;(usado para medir TLB misses, cache misses e context switches)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://man7.org/linux/man-pages/man1/pidstat.1.html" rel="noopener noreferrer"&gt;&lt;code&gt;pidstat&lt;/code&gt;&lt;/a&gt; — estatísticas de processos e threads (parte do pacote &lt;code&gt;sysstat&lt;/code&gt;) &lt;em&gt;(usado para monitorar voluntary/involuntary context switches)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-counters" rel="noopener noreferrer"&gt;&lt;code&gt;dotnet-counters&lt;/code&gt;&lt;/a&gt; — ferramenta de monitoramento de runtime .NET &lt;em&gt;(ThreadPool Thread Count, Queue Length, etc.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-trace" rel="noopener noreferrer"&gt;&lt;code&gt;dotnet-trace&lt;/code&gt;&lt;/a&gt; — coleta de traces do runtime .NET&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pkg.go.dev/net/http/pprof" rel="noopener noreferrer"&gt;Go pprof&lt;/a&gt; — profiler de CPU, memória e goroutines do Go &lt;em&gt;(goroutine dump, threadcreate profile)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pkg.go.dev/cmd/trace" rel="noopener noreferrer"&gt;&lt;code&gt;go tool trace&lt;/code&gt;&lt;/a&gt; — visualizador gráfico de traces do Go runtime&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pkg.go.dev/runtime#hdr-Environment_Variables" rel="noopener noreferrer"&gt;&lt;code&gt;GODEBUG=schedtrace&lt;/code&gt;&lt;/a&gt; — variável de ambiente para debug do scheduler Go&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://man7.org/linux/man-pages/man1/taskset.1.html" rel="noopener noreferrer"&gt;&lt;code&gt;taskset&lt;/code&gt;&lt;/a&gt; — configuração de CPU affinity para processos&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://man7.org/linux/man-pages/man8/numactl.8.html" rel="noopener noreferrer"&gt;&lt;code&gt;numactl&lt;/code&gt;&lt;/a&gt; — controle de política de memória e CPU NUMA&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://man7.org/linux/man-pages/man8/numastat.8.html" rel="noopener noreferrer"&gt;&lt;code&gt;numastat&lt;/code&gt;&lt;/a&gt; — estatísticas de alocação de memória por NUMA node&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bibliotecas e Pacotes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/uber-go/automaxprocs" rel="noopener noreferrer"&gt;&lt;code&gt;go.uber.org/automaxprocs&lt;/code&gt;&lt;/a&gt; — detecta automaticamente CPU limits de cgroups e ajusta &lt;code&gt;GOMAXPROCS&lt;/code&gt; &lt;em&gt;(Uber)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pkg.go.dev/golang.org/x/sync/semaphore" rel="noopener noreferrer"&gt;&lt;code&gt;golang.org/x/sync/semaphore&lt;/code&gt;&lt;/a&gt; — semáforo com peso para controle de concorrência em Go&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/prometheus/client_golang" rel="noopener noreferrer"&gt;&lt;code&gt;github.com/prometheus/client_golang&lt;/code&gt;&lt;/a&gt; — cliente Prometheus para Go &lt;em&gt;(exposição de métricas de runtime)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>backend</category>
      <category>dotnet</category>
      <category>linux</category>
      <category>performance</category>
    </item>
    <item>
      <title>Kernel Linux para Desenvolvedores Backend - Processos &amp; Threads Parte III</title>
      <dc:creator>Alex Volnei Galante</dc:creator>
      <pubDate>Tue, 09 Jun 2026 14:34:39 +0000</pubDate>
      <link>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-processos-threads-parte-iii-1187</link>
      <guid>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-processos-threads-parte-iii-1187</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Este artigo é a continuação da &lt;strong&gt;Parte II&lt;/strong&gt;, onde abordamos processos, seu ciclo de vida, syscalls e como os runtimes de Python, Go e .NET os utilizam. Se você ainda não leu, recomendo começar por lá:&lt;br&gt;
&lt;a href="https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-processos-threads-parte-ii-54fj"&gt;Kernel Linux para Desenvolvedores Backend — Processos &amp;amp; Threads Parte II&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Sumário
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Threads: Fundamentos

&lt;ul&gt;
&lt;li&gt;Modelo Clássico de Thread&lt;/li&gt;
&lt;li&gt;Motivação para Threads&lt;/li&gt;
&lt;li&gt;1. Paralelismo real em múltiplos cores&lt;/li&gt;
&lt;li&gt;2. Economia de recursos comparado a processos&lt;/li&gt;
&lt;li&gt;3. Responsividade e overlapping de I/O&lt;/li&gt;
&lt;li&gt;Threads em Espaço de Usuário vs Kernel&lt;/li&gt;
&lt;li&gt;User-Level Threads (ULT) ou Green Threads&lt;/li&gt;
&lt;li&gt;Kernel-Level Threads (KLT)&lt;/li&gt;
&lt;li&gt;Modelos Híbridos (M:N)&lt;/li&gt;
&lt;li&gt;Implementação de Pop-up Threads&lt;/li&gt;
&lt;li&gt;Thread Pools: Conceito e Benefícios&lt;/li&gt;
&lt;li&gt;Comparação: Quando Usar Threads vs Processos&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Context Switching: Teoria

&lt;ul&gt;
&lt;li&gt;O que é Salvo: Anatomia de um Context Switch&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Threads: Fundamentos
&lt;/h2&gt;

&lt;p&gt;Processos são unidades monolíticas de execução. Porém, aplicações modernas — especialmente servidores backend — raramente operam com um único fluxo de execução. &lt;strong&gt;Threads&lt;/strong&gt; permitem que múltiplos fluxos de execução coexistam dentro de um mesmo processo, compartilhando o espaço de endereçamento e recursos, mas mantendo contextos de execução independentes.&lt;/p&gt;

&lt;p&gt;Gosto de pensar que threads são linhas de isolamento de processos, porém a nível de kernel uma thread e um processo são a mesma coisa. &lt;strong&gt;A grande diferença para você, desenvolvedor backend, é entender a diferença de thread no nível de kernel e thread no nível de usuário&lt;/strong&gt;, mas isso a gente vai falar mais pra frente.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.kernel.org/arch/x86/topology.html#threads" rel="noopener noreferrer"&gt;Documentação Oficial da Kernel - Threads Topology&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Modelo Clássico de Thread
&lt;/h3&gt;

&lt;p&gt;Uma thread (ou &lt;strong&gt;lightweight process&lt;/strong&gt;) é a menor unidade de execução escalonável pelo sistema operacional. Enquanto um processo define um &lt;strong&gt;espaço de endereçamento&lt;/strong&gt; e um conjunto de &lt;strong&gt;recursos&lt;/strong&gt;, uma thread define um &lt;strong&gt;fluxo de controle&lt;/strong&gt; dentro desse espaço.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Processo (espaço de endereçamento compartilhado)
┌─────────────────────────────────────────────────────────────┐
│  Code (text segment)     │  Data (global variables)         │
├──────────────────────────┴──────────────────────────────────┤
│  Heap (dynamic allocation)                                  │
├─────────────────────────────────────────────────────────────┤
│  Open files, sockets, signals, credentials, cwd             │
├─────────┬─────────┬─────────┬───────────────────────────────┤
│ Thread 1│ Thread 2│ Thread 3│  ← Cada thread possui:        │
│┌───────┐│┌───────┐│┌───────┐│    - Stack própria            │
││ Stack │││ Stack │││ Stack ││    - Program counter          │
││ PC    │││ PC    │││ PC    ││    - Registradores            │
││ Regs  │││ Regs  │││ Regs  ││    - Estado (running, etc)    │
│└───────┘│└───────┘│└───────┘│    - Thread-local storage     │
└─────────┴─────────┴─────────┴───────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;O que threads compartilham&lt;/strong&gt; (pertence ao processo):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Espaço de endereçamento (code, data, heap)&lt;/li&gt;
&lt;li&gt;File descriptors abertos&lt;/li&gt;
&lt;li&gt;Sinais e handlers de sinais&lt;/li&gt;
&lt;li&gt;Working directory e root directory&lt;/li&gt;
&lt;li&gt;User ID e Group ID&lt;/li&gt;
&lt;li&gt;Memory mappings (&lt;code&gt;mmap&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;O que cada thread possui exclusivamente&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stack (cada thread tem sua própria pilha de execução)&lt;/li&gt;
&lt;li&gt;Program counter (aponta para a instrução sendo executada)&lt;/li&gt;
&lt;li&gt;Registradores da CPU (salvos/restaurados no context switch)&lt;/li&gt;
&lt;li&gt;Estado de escalonamento (running, blocked, ready)&lt;/li&gt;
&lt;li&gt;Thread-local storage (TLS) — variáveis privadas por thread&lt;/li&gt;
&lt;li&gt;Signal mask (quais sinais estão bloqueados)&lt;/li&gt;
&lt;li&gt;errno (em sistemas POSIX)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essa separação é fundamental: o compartilhamento do espaço de endereçamento permite comunicação eficiente entre threads (basta ler/escrever em memória compartilhada), mas introduz problemas de &lt;strong&gt;sincronização&lt;/strong&gt; e &lt;strong&gt;race conditions&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Motivação para Threads
&lt;/h3&gt;

&lt;p&gt;Por que não usar simplesmente múltiplos processos? Threads oferecem três vantagens fundamentais:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Paralelismo real em múltiplos cores
&lt;/h4&gt;

&lt;p&gt;Em um servidor com 8 cores, um processo single-threaded utiliza no máximo 12.5% da capacidade de CPU. Threads permitem distribuir trabalho entre todos os cores disponíveis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Servidor 8 cores — Processando 8 requests simultâneos:

Processo single-threaded:
Core 0: |████████████████████████████████████████████████| (100% — saturado)
Core 1: |                                                | (idle)
Core 2: |                                                | (idle)
...
Core 7: |                                                | (idle)
Throughput: 1x (serializado)

Processo multi-threaded (8 threads):
Core 0: |██████| req 1
Core 1: |██████| req 2
Core 2: |██████| req 3
...
Core 7: |██████| req 8
Throughput: ~8x (paralelo)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Economia de recursos comparado a processos
&lt;/h4&gt;

&lt;p&gt;Criar uma thread é significativamente mais barato que criar um processo:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operação&lt;/th&gt;
&lt;th&gt;Custo típico&lt;/th&gt;
&lt;th&gt;Motivo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;fork()&lt;/code&gt; (processo)&lt;/td&gt;
&lt;td&gt;~100-500μs&lt;/td&gt;
&lt;td&gt;Copia page tables, duplica estruturas do kernel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;clone()&lt;/code&gt; (thread)&lt;/td&gt;
&lt;td&gt;~10-50μs&lt;/td&gt;
&lt;td&gt;Compartilha espaço de endereçamento, aloca apenas stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context switch entre processos&lt;/td&gt;
&lt;td&gt;~3-5μs&lt;/td&gt;
&lt;td&gt;Flush de TLB, troca de page tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context switch entre threads (mesmo processo)&lt;/td&gt;
&lt;td&gt;~1-2μs&lt;/td&gt;
&lt;td&gt;Sem flush de TLB (mesmo espaço de endereçamento)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Fonte: &lt;a href="https://www.brendangregg.com/systems-performance.html" rel="noopener noreferrer"&gt;Systems Performance, 2nd Edition — Brendan Gregg&lt;/a&gt;; valores de referência medidos com &lt;a href="http://www.bitmover.com/lmbench/" rel="noopener noreferrer"&gt;lmbench&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A economia é especialmente relevante em servidores que precisam tratar milhares de conexões simultâneas — criar um processo por conexão (modelo Apache pre-fork) é ordens de magnitude mais caro que criar uma thread por conexão.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Responsividade e overlapping de I/O
&lt;/h4&gt;

&lt;p&gt;Em aplicações que combinam I/O e computação, threads permitem sobrepor atividades:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sem threads (serializado):
|── read DB ──|── process ──|── read DB ──|── process ──|── respond ──|
0             50            80           130           160            180ms

Com threads (overlapping):
Thread 1: |── read DB ──|── process ──|── respond ──|
Thread 2:     |── read DB ──|── process ──|
              ↑ I/O concurrent
0             50            80           100ms  ← 44% mais rápido
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Implicação para backend&lt;/strong&gt;: Um servidor web que faz múltiplas queries ao banco de dados para compor uma resposta pode disparar todas as queries em paralelo usando threads, ao invés de executá-las sequencialmente. Frameworks como ASP.NET Core fazem isso nativamente com &lt;code&gt;async/await&lt;/code&gt; e o thread pool.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Threads em Espaço de Usuário vs Kernel
&lt;/h3&gt;

&lt;p&gt;A implementação de threads pode ocorrer em diferentes camadas do sistema, cada uma com trade-offs distintos.&lt;/p&gt;

&lt;h4&gt;
  
  
  User-Level Threads (ULT) ou Green Threads
&lt;/h4&gt;

&lt;p&gt;Threads implementadas inteiramente em espaço de usuário, por uma biblioteca de runtime — sem envolvimento do kernel. O kernel enxerga apenas um único processo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────┐
│         Espaço de Usuário            │
│  ┌──────────────────────────────┐    │
│  │   Thread Library (runtime)   │    │
│  │  ┌─────┐ ┌─────┐ ┌─────┐     │    │
│  │  │ ULT │ │ ULT │ │ ULT │     │    │  ← 3 threads visíveis ao runtime
│  │  │  1  │ │  2  │ │  3  │     │    │
│  │  └─────┘ └─────┘ └─────┘     │    │
│  │        Thread Scheduler      │    │  ← escalonamento em userspace
│  └──────────────────────────────┘    │
├──────────────────────────────────────┤
│              Kernel                  │
│  ┌──────────────────────────────┐    │
│  │ 1 kernel thread (1 processo) │    │  ← kernel vê apenas 1 fluxo
│  └──────────────────────────────┘    │
└──────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Vantagens&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context switch ultra-rápido&lt;/strong&gt;: Troca de thread não envolve trap para o kernel (~100ns vs ~1-2μs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portabilidade&lt;/strong&gt;: Funciona em qualquer OS, independente de suporte a threads no kernel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customização&lt;/strong&gt;: O algoritmo de escalonamento pode ser otimizado para a aplicação específica&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalabilidade&lt;/strong&gt;: Pode criar milhões de threads (são apenas structs em memória)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limitações críticas&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blocking I/O bloqueia todo o processo&lt;/strong&gt;: Se uma ULT faz uma syscall bloqueante (read, accept), &lt;em&gt;todas&lt;/em&gt; as threads do processo param — o kernel não sabe que existem outras threads prontas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sem paralelismo real&lt;/strong&gt;: Como o kernel vê apenas um processo, todas as ULTs executam no mesmo core — impossível utilizar múltiplos cores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page faults bloqueiam tudo&lt;/strong&gt;: Um page fault em qualquer thread suspende todo o processo
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problema de blocking I/O com ULTs:

ULT 1: |████|── read() ──────────────────|████|
ULT 2: |░░░░|░░░░░░░░░░░░░░░░░░░░░░░░░░░|████|  ← bloqueada esperando ULT 1!
ULT 3: |░░░░|░░░░░░░░░░░░░░░░░░░░░░░░░░░|████|  ← idem

O kernel vê: |████|── BLOCKED ──────────────|████|
             (todo o processo está bloqueado)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Exemplos históricos&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Green threads do Java 1.0, GNU Pth, Solaris LWPs iniciais.&lt;/li&gt;
&lt;li&gt;Goroutines do Go são user-level threads, mas o runtime gerencia a multiplexação em OS threads para contornar as limitações de ULTs tradicionais.&lt;/li&gt;
&lt;li&gt;Python (antes do GIL) tinha uma implementação de green threads chamada &lt;code&gt;greenlet&lt;/code&gt;, mas o GIL tornou isso inviável para paralelismo real.&lt;/li&gt;
&lt;li&gt;.NET tinha uma implementação de user-level threads chamada "fibers", mas foi descontinuada em favor do modelo 1:1 com kernel threads.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Kernel-Level Threads (KLT)
&lt;/h4&gt;

&lt;p&gt;Threads gerenciadas diretamente pelo kernel. Cada thread é uma entidade escalonável independente.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────┐
│         Espaço de Usuário            │
│  ┌─────┐    ┌─────┐    ┌─────┐       │
│  │ Thr │    │ Thr │    │ Thr │       │  ← 3 threads visíveis ao programa
│  │  1  │    │  2  │    │  3  │       │
│  └──┬──┘    └──┬──┘    └──┬──┘       │
├─────┼──────────┼──────────┼──────────┤
│     ▼          ▼          ▼   Kernel │
│  ┌─────┐    ┌─────┐    ┌─────┐       │
│  │ KLT │    │ KLT │    │ KLT │       │  ← 3 kernel threads (task_structs)
│  │  1  │    │  2  │    │  3  │       │
│  └─────┘    └─────┘    └─────┘       │
│         Kernel Scheduler             │  ← escalonamento pelo kernel
└──────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Vantagens&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Paralelismo real&lt;/strong&gt;: Threads podem executar simultaneamente em diferentes cores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I/O não bloqueia outras threads&lt;/strong&gt;: Se thread 1 bloqueia em I/O, threads 2 e 3 continuam executando&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalonamento justo&lt;/strong&gt;: O kernel aplica as mesmas políticas (CFS, etc.) a todas as threads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Desvantagens&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overhead de criação&lt;/strong&gt;: Cada thread requer alocação de &lt;code&gt;task_struct&lt;/code&gt;, stack de kernel (~8-16KB), e entrada na tabela de processos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context switch mais caro&lt;/strong&gt;: Requer transição user→kernel→user&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalabilidade limitada&lt;/strong&gt;: Criar milhares de threads é viável, mas milhões não — cada uma consome memória de kernel e sobrecarrega o escalonador&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sincronização via syscalls&lt;/strong&gt;: Operações como mutex lock/unlock requerem traps para o kernel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Modelo 1:1&lt;/strong&gt; — No Linux moderno (NPTL - Native POSIX Thread Library), cada thread POSIX mapeia diretamente para uma kernel thread. Este é o modelo usado por:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python (cada thread Python = 1 kernel thread)&lt;/li&gt;
&lt;li&gt;.NET (cada thread gerenciada = 1 kernel thread)&lt;/li&gt;
&lt;li&gt;Java (desde Java 1.3+)&lt;/li&gt;
&lt;li&gt;Go (cada goroutine é multiplexada em OS threads, mas o modelo é efetivamente 1:1 para threads do kernel)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Verificando threads de um processo&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /proc/1350/task/
1350  1351  1352  1353    ← 4 threads &lt;span class="o"&gt;(&lt;/span&gt;4 task_structs no kernel&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/1350/status | &lt;span class="nb"&gt;grep &lt;/span&gt;Threads
Threads: 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Modelos Híbridos (M:N)
&lt;/h4&gt;

&lt;p&gt;O modelo M:N combina M user-level threads mapeadas em N kernel threads (onde M &amp;gt;&amp;gt; N). Busca obter o melhor dos dois mundos: escalabilidade de ULTs com paralelismo de KLTs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────────────────────────────────────────────┐
│              Espaço de Usuário                    │
│  ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐  │
│  │UT 1 │ │UT 2 │ │UT 3 │ │UT 4 │ │UT 5 │ │UT 6 │  │ ← M user threads
│  └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘  │
│     │    ╲  │   ╱   │       │   ╲   │   ╱   │     │
│     ▼     ╲ ▼  ╱    ▼       ▼    ╲  ▼  ╱    ▼     │ ← multiplexação
│  ┌─────────────┐  ┌───────┐  ┌─────────────┐      │
│  │ User Sched  │  │ U.S.  │  │ User Sched  │      │
├──┴──────┬──────┴──┴───┬───┴──┴──────┬──────┴──────┤
│         ▼             ▼             ▼      Kernel │
│      ┌─────┐       ┌─────┐       ┌─────┐          │
│      │KLT 1│       │KLT 2│       │KLT 3│          │ ← N kernel threads
│      └─────┘       └─────┘       └─────┘          │
│              Kernel Scheduler                     │
└───────────────────────────────────────────────────┘

M = 6 user threads, N = 3 kernel threads → modelo 6:3 (ou 2:1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Vantagens do M:N&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pode criar milhões de user threads sem sobrecarregar o kernel&lt;/li&gt;
&lt;li&gt;Paralelismo real (N kernel threads em N cores)&lt;/li&gt;
&lt;li&gt;Context switch rápido entre user threads no mesmo kernel thread&lt;/li&gt;
&lt;li&gt;Blocking syscalls podem ser mascaradas (a runtime move outras user threads para KLTs livres)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Desafios&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complexidade de implementação significativa&lt;/li&gt;
&lt;li&gt;Coordenação entre user scheduler e kernel scheduler&lt;/li&gt;
&lt;li&gt;Debugging mais difícil (stack traces podem ser confusos)&lt;/li&gt;
&lt;li&gt;Sincronização entre user threads e kernel primitives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;O exemplo mais bem-sucedido de M:N na prática&lt;/strong&gt;: o &lt;strong&gt;Go runtime&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Go runtime (modelo M:N):

G (Goroutines):  G1  G2  G3  G4  G5  G6 ... G100000  ← milhões possíveis
                  │   │   │   │   │   │
                  └───┴───┼───┴───┴───┘
                          │
M (OS Threads):     M1    M2    M3    M4         ← GOMAXPROCS kernel threads
                    │     │     │     │
P (Processors):     P1    P2    P3    P4         ← logical processors

- G: goroutine (~2KB stack inicial, cresce dinamicamente)
- M: kernel thread (task_struct no Linux)
- P: contexto de processamento (run queue local)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Conexão com linguagens backend&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt;: Modelo 1:1 (cada thread = kernel thread), mas o GIL impede paralelismo real para código Python puro&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET&lt;/strong&gt;: Modelo 1:1 para threads, mas &lt;code&gt;Task&lt;/code&gt;/&lt;code&gt;async-await&lt;/code&gt; implementa um scheduler cooperativo em userspace sobre o thread pool&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go&lt;/strong&gt;: Modelo M:N verdadeiro — goroutines são user-level threads multiplexadas em OS threads pelo Go scheduler&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Implementação de Pop-up Threads
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pop-up threads&lt;/strong&gt; são um padrão onde threads são criadas dinamicamente em resposta a eventos (tipicamente mensagens de rede chegando). Ao invés de manter um pool de threads bloqueadas em &lt;code&gt;accept()&lt;/code&gt;/&lt;code&gt;recv()&lt;/code&gt;, uma nova thread é "disparada" (pop-up) para tratar cada mensagem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Modelo tradicional (thread pool blocking):
Thread 1: |── accept() ──────|── handle ──|── accept() ──────|
Thread 2: |── accept() ──────────────────────|── handle ──|
Thread 3: |── accept() ──────────────────────────────────────|  ← idle, desperdiçando stack

Modelo pop-up:
                    msg arrives
                         │
                         ▼
Dispatcher: |── wait ──|── spawn ──|── wait ──|── spawn ──|
                              │                      │
Pop-up T1:                    |── handle ──|         │
Pop-up T2:                                           |── handle ──|
                              ↑                      ↑
                     thread criada sob demanda (sem estado prévio)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Vantagens&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Thread começa "fresca" — sem estado anterior para salvar/restaurar&lt;/li&gt;
&lt;li&gt;Criação é mais rápida que acordar uma thread bloqueada (em implementações otimizadas)&lt;/li&gt;
&lt;li&gt;Sem overhead de threads ociosas consumindo stack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Desvantagens&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custo de criação pode ser alto se threads são kernel-level&lt;/li&gt;
&lt;li&gt;Sem limite inerente — pode criar threads demais sob carga alta (thundering herd)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Na prática, o conceito de pop-up threads inspirou modelos como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goroutines em Go&lt;/strong&gt;: extremamente baratas de criar (~2KB), usadas como pop-up threads para cada request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task.Run em .NET&lt;/strong&gt;: cria uma task (executada por uma thread do pool) para cada operação&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event-driven + thread pool&lt;/strong&gt;: Node.js, asyncio — o event loop despacha trabalho CPU-bound para threads do pool&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Thread Pools: Conceito e Benefícios
&lt;/h3&gt;

&lt;p&gt;Um &lt;strong&gt;thread pool&lt;/strong&gt; é um conjunto pré-alocado de threads que aguardam trabalho em uma fila. Ao invés de criar e destruir threads para cada tarefa, as threads são reutilizadas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Thread Pool:
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   Work Queue:  [Task A] → [Task B] → [Task C] → ...         │
│                    │                                        │
│                    ▼                                        │
│   ┌────────┐  ┌────────┐  ┌────────┐  ┌────────┐            │
│   │Thread 1│  │Thread 2│  │Thread 3│  │Thread 4│            │
│   │ (busy) │  │ (busy) │  │(waitin)│  │(waitin)│            │
│   └────────┘  └────────┘  └────────┘  └────────┘            │
│                                                             │
└─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefícios&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Amortização do custo de criação&lt;/strong&gt;: Threads são criadas uma vez e reutilizadas milhares de vezes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controle de recursos&lt;/strong&gt;: Limita o número máximo de threads, prevenindo esgotamento de memória&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redução de latência&lt;/strong&gt;: Thread já existe quando o trabalho chega — não há atraso de criação&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backpressure natural&lt;/strong&gt;: Quando o pool está saturado, novas tarefas aguardam na fila, fornecendo um mecanismo natural de controle de carga&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Dimensionamento do thread pool&lt;/strong&gt; — uma das decisões mais impactantes para performance de backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Para workloads I/O-bound:
  Threads ≈ N_cores × (1 + Wait_time / Service_time)

  Exemplo: 8 cores, ratio wait/service = 9 (90% I/O)
  Threads ≈ 8 × (1 + 9) = 80 threads

Para workloads CPU-bound:
  Threads ≈ N_cores (ou N_cores + 1)

  Exemplo: 8 cores, computação pura
  Threads ≈ 8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Exemplos em linguagens backend&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python (Gunicorn)&lt;/strong&gt;: Workers (processos) com threads — &lt;code&gt;workers = 2*cores + 1&lt;/code&gt;, &lt;code&gt;threads = 2-4&lt;/code&gt; por worker&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET (ThreadPool)&lt;/strong&gt;: Auto-tuning com hill climbing algorithm — começa com &lt;code&gt;Environment.ProcessorCount&lt;/code&gt; threads e ajusta dinamicamente&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go&lt;/strong&gt;: Não usa thread pool explícito — o runtime gerencia OS threads dinamicamente (geralmente &lt;code&gt;GOMAXPROCS&lt;/code&gt; = número de cores)
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Monitorando thread pool em produção&lt;/span&gt;

&lt;span class="c"&gt;# .NET: ThreadPool stats&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;dotnet-counters monitor &lt;span class="nt"&gt;--process-id&lt;/span&gt; 950 System.Runtime
    ThreadPool Thread Count:    24
    ThreadPool Queue Length:     0
    ThreadPool Completed Items: 1,234,567

&lt;span class="c"&gt;# Python: verificando threads de workers Gunicorn&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;ps &lt;span class="nt"&gt;-eLf&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;gunicorn | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
48    &lt;span class="c"&gt;# 16 workers × 3 threads cada&lt;/span&gt;

&lt;span class="c"&gt;# Go: goroutines vs OS threads&lt;/span&gt;
&lt;span class="c"&gt;# (via pprof ou GODEBUG=schedtrace=1000)&lt;/span&gt;
SCHED 1000ms: &lt;span class="nv"&gt;gomaxprocs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8 &lt;span class="nv"&gt;idleprocs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;threads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10 &lt;span class="nv"&gt;idlethreads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
              &lt;span class="nv"&gt;runqueue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="o"&gt;[&lt;/span&gt;2 0 1 0 0 3 0 0]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Comparação: Quando Usar Threads vs Processos
&lt;/h3&gt;

&lt;p&gt;A escolha entre threads e processos é uma decisão arquitetural fundamental para aplicações backend:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Critério&lt;/th&gt;
&lt;th&gt;Threads&lt;/th&gt;
&lt;th&gt;Processos&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Isolamento&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fraco — crash em uma thread pode corromper todo o processo&lt;/td&gt;
&lt;td&gt;Forte — crash isolado, outros processos continuam&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Comunicação&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rápida — memória compartilhada direta&lt;/td&gt;
&lt;td&gt;Lenta — IPC (pipes, sockets, shared memory explícita)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Overhead de criação&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Baixo (~10-50μs)&lt;/td&gt;
&lt;td&gt;Alto (~100-500μs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Context switch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rápido (mesmo address space)&lt;/td&gt;
&lt;td&gt;Lento (TLB flush, troca de page tables)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Escalabilidade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limitada pela memória de stack (milhares)&lt;/td&gt;
&lt;td&gt;Limitada pela tabela de processos (milhares)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Debugging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Difícil (race conditions, deadlocks)&lt;/td&gt;
&lt;td&gt;Mais simples (estados isolados)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mesmas permissões, uma vulnerabilidade compromete tudo&lt;/td&gt;
&lt;td&gt;Isolamento de permissões possível&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quando usar PROCESSOS:
├── Isolamento é crítico (ex: processando dados de múltiplos tenants)
├── O código pode crashar (ex: extensões C/C++ instáveis)
├── Precisa de security boundaries (ex: sandbox por request)
└── Linguagem tem GIL (Python) e precisa de paralelismo CPU

Quando usar THREADS:
├── Comunicação frequente entre unidades de trabalho
├── Baixa latência de criação é importante
├── Workload é I/O-bound (threads bloqueiam em I/O independentemente)
└── Memória compartilhada simplifica a arquitetura
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Decisões práticas por linguagem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt;: Use processos (multiprocessing/Gunicorn workers) para CPU-bound; threads para I/O-bound (apesar do GIL, threads liberam o GIL durante I/O); asyncio para alta concorrência I/O&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET&lt;/strong&gt;: Use threads/Tasks para tudo (sem GIL); processos apenas para isolamento extremo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go&lt;/strong&gt;: Use goroutines para tudo — o runtime gerencia a complexidade; processos separados apenas para isolamento de serviços (microserviços)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Context Switching: Teoria
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;context switch&lt;/strong&gt; (troca de contexto) é o mecanismo pelo qual o kernel salva o estado de um processo/thread em execução e restaura o estado de outro, efetivamente transferindo a CPU de uma unidade de execução para outra. Embora invisível para a aplicação, o context switch é uma operação que ocorre milhares de vezes por segundo em um servidor backend — e seu custo acumulado pode ser significativo.&lt;/p&gt;

&lt;h3&gt;
  
  
  O que é Salvo: Anatomia de um Context Switch
&lt;/h3&gt;

&lt;p&gt;Quando o kernel decide trocar o processo/thread em execução, ele precisa preservar todo o estado necessário para que o processo interrompido possa ser retomado exatamente de onde parou, como se nada tivesse acontecido.&lt;/p&gt;

&lt;h4&gt;
  
  
  Estado salvo por hardware (automático na troca de privilégio)
&lt;/h4&gt;

&lt;p&gt;Na arquitetura x86-64, quando ocorre uma interrupção ou trap que causa transição para kernel mode, o processador automaticamente salva na kernel stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stack do kernel após interrupção (x86-64):
┌─────────────────────┐  ← topo da kernel stack
│ SS (user stack seg) │
│ RSP (user stack ptr)│
│ RFLAGS              │  ← flags de status (carry, zero, overflow, interrupt enable)
│ CS (code segment)   │
│ RIP (program count) │  ← instrução onde o processo foi interrompido
└─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Estado salvo pelo kernel (software)
&lt;/h4&gt;

&lt;p&gt;O kernel salva explicitamente o restante do contexto na &lt;code&gt;task_struct&lt;/code&gt; (ou estrutura associada como &lt;code&gt;thread_struct&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Contexto salvo pelo kernel:
┌─────────────────────────────────────────────────────────┐
│ Registradores de Propósito Geral                        │
│   RAX, RBX, RCX, RDX, RSI, RDI, RBP                     │
│   R8, R9, R10, R11, R12, R13, R14, R15                  │
├─────────────────────────────────────────────────────────┤
│ Program Counter (RIP) e Stack Pointer (RSP)             │
├─────────────────────────────────────────────────────────┤
│ Registradores de Segmento (FS, GS — usados para TLS)    │
├─────────────────────────────────────────────────────────┤
│ Estado da FPU/SSE/AVX                                   │
│   Registradores XMM0-XMM15 (128 bits cada)              │
│   Registradores YMM0-YMM15 (256 bits — AVX)             │
│   Registradores ZMM0-ZMM31 (512 bits — AVX-512)         │
│   MXCSR (controle SSE)                                  │
│   x87 FPU state (legacy)                                │
├─────────────────────────────────────────────────────────┤
│ Estado de Debug (DR0-DR7) — se em uso                   │
├─────────────────────────────────────────────────────────┤
│ Informações de Escalonamento                            │
│   vruntime, prioridade efetiva, timeslice restante      │
├─────────────────────────────────────────────────────────┤
│ Kernel stack pointer                                    │
└─────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O estado FPU/SIMD é particularmente volumoso — com AVX-512, pode ser mais de 2KB por contexto. O Linux usa &lt;strong&gt;lazy FPU saving&lt;/strong&gt; (ou, em kernels modernos, &lt;strong&gt;eager FPU saving&lt;/strong&gt; com XSAVE/XRSTOR) para otimizar esse custo.&lt;/p&gt;

&lt;h4&gt;
  
  
  O que NÃO é salvo (compartilhado entre threads do mesmo processo)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Espaço de endereçamento (page tables) — por isso context switch entre threads é mais barato&lt;/li&gt;
&lt;li&gt;File descriptors&lt;/li&gt;
&lt;li&gt;Sinais e handlers&lt;/li&gt;
&lt;li&gt;Credenciais (UID/GID)&lt;/li&gt;
&lt;li&gt;Working directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-processos-threads-parte-iv-41j"&gt;&lt;strong&gt;Continua na parte IV&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conteudo parcialmente gerado com auxilio de IA generativa (eu organizei o conteudo e ela me ajudou com lero lero, novos tempos kkkk)&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referências Bibliográficas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Livros
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Desenvolvimento-do-Kernel-Linux/dp/8573933410" rel="noopener noreferrer"&gt;Desenvolvimento do Kernel do Linux — Robert Love (David Cram, trad.)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Linux-Bible-Christopher-Negus/dp/1394317468/ref=sr_1_1?__mk_pt_BR=%C3%85M%C3%85%C5%BD%C3%95%C3%91&amp;amp;dib=eyJ2IjoiMSJ9.ep2zHSLrXTmnOmqryZZJPcwOnbqsPqlHDKyK_8FK75E7IdfT3OQ4iSeLNg4aDkEbas_KyjlckRv_HAqF0-rXbwY0A7IAJnyqEquSkUVLVco_qSolsvkdEK8LeRJ7GQcp8e8AIbQoxZMwHdkqzqy0WHcbLqaF3pcBaRdo4HBaO_m9ZJTLKY9TXza9uJCvonORaFc81XM-Gp76W7qwYVmuo33vr9HQHPeeyrrK2rw_dPY.CC-nHGj2vuWkT6U5wHlf4BLCa2H5hJDXH_Xg72Hue10&amp;amp;dib_tag=se&amp;amp;keywords=linux+a+biblia&amp;amp;qid=1780931929&amp;amp;s=books&amp;amp;sprefix=linux+a+bi%2Cstripbooks%2C1135&amp;amp;sr=1-1&amp;amp;ufe=app_do%3Aamzn1.fos.fcd6d665-32ba-4479-9f21-b774e276a678" rel="noopener noreferrer"&gt;Linux Bible — Christopher Negus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Sistemas-Operacionais-Modernos-Andrew-Tanenbaum/dp/8582606168/ref=sr_1_1?__mk_pt_BR=%C3%85M%C3%85%C5%BD%C3%95%C3%91&amp;amp;dib=eyJ2IjoiMSJ9.GT4sX07Q-JQVNuedOvqQ5ZO7y1vPyznY4qtp_jih_s6jnDsrFJut_q6oT6io7p-I4c2hke9cKBU-DXK1GrwEjyvNZQbXAMjxsM1C6oDQqUybKWMEHkoJo3VQvzLYVU4XXCGkjDiNVI_fYu7spu33HDSpcBcZ891_HBZu4218XEvpnWNCWv6D5pM2XF0qZnFJeNTYoTSbSf6aldeB0RoH1cQ62o63NXV8a8HNh9qdNJs.anlokyxWbWuareiNAhSAhsoxohIr4FfNT9TcagLW980&amp;amp;dib_tag=se&amp;amp;keywords=Sistemas+Operacionais&amp;amp;qid=1780931998&amp;amp;s=books&amp;amp;sr=1-1&amp;amp;ufe=app_do%3Aamzn1.fos.fcd6d665-32ba-4479-9f21-b774e276a678" rel="noopener noreferrer"&gt;Sistemas Operacionais Modernos — Andrew Tanenbaum&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.brendangregg.com/systems-performance.html" rel="noopener noreferrer"&gt;Systems Performance, 2nd Edition — Brendan Gregg&lt;/a&gt; &lt;em&gt;(fonte dos valores de benchmark de fork/clone/context switch)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Documentação Oficial
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/arch/x86/topology.html#threads" rel="noopener noreferrer"&gt;Linux Kernel Documentation — Threads Topology (x86)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.ibm.com/articles/l-linux-kernel/" rel="noopener noreferrer"&gt;Linux Kernel: An Introduction — IBM Developer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Ferramentas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://www.bitmover.com/lmbench/" rel="noopener noreferrer"&gt;lmbench — benchmark de latências de OS&lt;/a&gt; &lt;em&gt;(utilizado para medir custos de fork, clone e context switch)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>kernel</category>
      <category>programming</category>
    </item>
    <item>
      <title>Kernel Linux para Desenvolvedores Backend - Processos &amp; Threads Parte II</title>
      <dc:creator>Alex Volnei Galante</dc:creator>
      <pubDate>Mon, 08 Jun 2026 18:39:01 +0000</pubDate>
      <link>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-processos-threads-parte-ii-54fj</link>
      <guid>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-processos-threads-parte-ii-54fj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Este artigo é a continuação da &lt;strong&gt;Parte I&lt;/strong&gt;, onde abordamos processos, seu ciclo de vida, syscalls e como os runtimes de Python, Go e .NET os utilizam. Se você ainda não leu, recomendo começar por lá:&lt;br&gt;
&lt;a href="https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-processos-threads-parte-i-1hlp"&gt;Kernel Linux para Desenvolvedores Backend — Processos &amp;amp; Threads Parte I&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Sumário da Parte II
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Escalonamento de Processos

&lt;ul&gt;
&lt;li&gt;Categorias de Algoritmos de Escalonamento&lt;/li&gt;
&lt;li&gt;
Algoritmos para Batch Systems

&lt;ul&gt;
&lt;li&gt;First-Come, First-Served (FCFS)&lt;/li&gt;
&lt;li&gt;Shortest Job First (SJF)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Algoritmos para Interactive Systems

&lt;ul&gt;
&lt;li&gt;Round-Robin (RR)&lt;/li&gt;
&lt;li&gt;Priority Scheduling&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Algoritmos para Real-Time Systems&lt;/li&gt;
&lt;li&gt;Escalonamento Preemptivo vs Não-Preemptivo&lt;/li&gt;
&lt;li&gt;Problema da Inversão de Prioridade&lt;/li&gt;
&lt;li&gt;Starvation e Aging&lt;/li&gt;
&lt;li&gt;Como o Linux Implementa Escalonamento: Visão Geral&lt;/li&gt;
&lt;li&gt;Prioridades e Nice Values&lt;/li&gt;
&lt;li&gt;Métricas de Escalonamento na Prática&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Referências Bibliográficas&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Escalonamento de Processos
&lt;/h2&gt;

&lt;p&gt;A peça fundamental que realiza toda a máquina de estados com processos no kernel é o escalonador. Vamos começar com uma base teórica sobre os algoritmos de escalonamento, suas categorias e como o Linux implementa isso na prática.&lt;/p&gt;

&lt;h3&gt;
  
  
  Categorias de Algoritmos de Escalonamento
&lt;/h3&gt;

&lt;p&gt;Os algoritmos de escalonamento são projetados para diferentes tipos de sistemas, cada um com prioridades distintas.&lt;/p&gt;

&lt;h4&gt;
  
  
  Algoritmos para Batch Systems
&lt;/h4&gt;

&lt;p&gt;Sistemas batch priorizam throughput e turnaround time. Não há usuário interativo esperando resposta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First-Come, First-Served (FCFS)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Detalhe completo no link &lt;a href="https://www.geeksforgeeks.org/dsa/first-come-first-serve-cpu-scheduling-non-preemptive/" rel="noopener noreferrer"&gt;https://www.geeksforgeeks.org/dsa/first-come-first-serve-cpu-scheduling-non-preemptive/&lt;/a&gt; — é o algoritmo mais simples, mas pode levar a tempos de espera muito altos para processos curtos (efeito comboio).&lt;/p&gt;

&lt;p&gt;Executa os processos na ordem de chegada. O processo que chega primeiro é executado até terminar, depois o próximo, e assim por diante.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Fila de chegada: P1(24ms) → P2(3ms) → P3(3ms)

  Execução FCFS:
  |────────── P1 (24ms) ──────────|─ P2 (3ms) ─|─ P3 (3ms) ─|
  0                               24            27            30

  Waiting time médio: (0 + 24 + 27) / 3 = 17ms

  Se a ordem fosse P2, P3, P1:
  |─ P2 ─|─ P3 ─|────────── P1 (24ms) ──────────|
  0       3       6                               30

  Waiting time médio: (0 + 3 + 6) / 3 = 3ms  ← 5.7x melhor!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;strong&gt;efeito comboio&lt;/strong&gt; (convoy effect) é o problema clássico do FCFS: um processo CPU-bound longo bloqueia todos os demais. Isso é análogo a ter uma query SQL pesada bloqueando o único worker disponível.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shortest Job First (SJF)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Detalhe completo no link &lt;a href="https://translate.google.com/translate?u=https://www.geeksforgeeks.org/operating-systems/shortest-job-first-or-sjf-cpu-scheduling/&amp;amp;hl=pt&amp;amp;sl=en&amp;amp;tl=pt&amp;amp;client=srp" rel="noopener noreferrer"&gt;https://translate.google.com/translate?u=https://www.geeksforgeeks.org/operating-systems/shortest-job-first-or-sjf-cpu-scheduling/&amp;amp;hl=pt&amp;amp;sl=en&amp;amp;tl=pt&amp;amp;client=srp&lt;/a&gt; —&lt;/p&gt;

&lt;p&gt;Executa primeiro o processo com menor tempo estimado de CPU. É provadamente ótimo para minimizar o waiting time médio — mas requer conhecimento prévio do tempo de execução, o que raramente é possível.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Processos: P1(6ms), P2(8ms), P3(7ms), P4(3ms)

  FCFS:  |─ P1(6) ─|── P2(8) ──|─ P3(7) ─|─P4(3)─|
        Waiting médio: (0+6+14+21)/4 = 10.25ms

  SJF:   |P4(3)|─ P1(6) ─|─ P3(7) ─|── P2(8) ──|
        Waiting médio: (0+3+9+16)/4 = 7ms  ← ótimo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Na prática, SJF inspira heurísticas usadas em load balancers e connection schedulers: redirecionar requisições para o worker que deve terminar mais rápido (least-connections, por exemplo).&lt;/p&gt;

&lt;h4&gt;
  
  
  Algoritmos para Interactive Systems
&lt;/h4&gt;

&lt;p&gt;Sistemas interativos — onde se encaixam a maioria das aplicações backend — priorizam response time e fairness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Round-Robin (RR)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Detalhe completo no link &lt;a href="https://www.geeksforgeeks.org/operating-systems/round-robin-scheduling-in-operating-system/" rel="noopener noreferrer"&gt;https://www.geeksforgeeks.org/operating-systems/round-robin-scheduling-in-operating-system/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cada processo recebe um &lt;strong&gt;quantum&lt;/strong&gt; (timeslice) fixo de CPU. Ao esgotar o quantum, é preemptado e colocado no final da fila.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quantum = 4ms
Processos: P1(24ms), P2(3ms), P3(3ms)

|─P1(4)─|P2(3)|P3(3)|─P1(4)─|─P1(4)─|─P1(4)─|─P1(4)─|P1(4)|
0        4     7    10      14      18      22      26    30

P2 termina em t=7  (vs t=27 no FCFS)
P3 termina em t=10 (vs t=30 no FCFS)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O Round-Robin é a base conceitual sobre a qual o CFS do Linux foi construído — embora o CFS use uma abordagem muito mais sofisticada baseada em virtual runtime.&lt;/p&gt;

&lt;p&gt;A escolha do &lt;strong&gt;quantum&lt;/strong&gt; é crítica:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Muito pequeno&lt;/strong&gt; (&amp;lt; 1ms): overhead de context switch domina — a CPU gasta mais tempo trocando de processo do que executando&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Muito grande&lt;/strong&gt; (&amp;gt; 100ms): degenera para FCFS — processos interativos sofrem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regra empírica&lt;/strong&gt;: 80% dos CPU bursts devem ser menores que o quantum&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Priority Scheduling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Detalhe completo no link &lt;a href="https://www.geeksforgeeks.org/operating-systems/priority-scheduling-in-operating-system/" rel="noopener noreferrer"&gt;https://www.geeksforgeeks.org/operating-systems/priority-scheduling-in-operating-system/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cada processo recebe uma prioridade. O processo de maior prioridade executa primeiro.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prioridades (menor número = maior prioridade):

Prioridade 1: ├── Kernel threads (interrupts, softirqs)
Prioridade 2: ├── Processos real-time (SCHED_FIFO, SCHED_RR)
              │   └── Exemplo: audio processing, controle industrial
Prioridade 3: ├── Processos normais com nice negativo
              │   └── Exemplo: nginx worker com nice -5
Prioridade 4: ├── Processos normais (nice 0)
              │   └── Exemplo: sua API Python/Go/.NET
Prioridade 5: └── Processos de baixa prioridade (nice positivo)
                  └── Exemplo: backup, log rotation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O problema fundamental do priority scheduling é o &lt;strong&gt;starvation&lt;/strong&gt;: processos de baixa prioridade podem nunca executar se processos de alta prioridade estão sempre prontos.&lt;/p&gt;

&lt;h4&gt;
  
  
  Algoritmos para Real-Time Systems
&lt;/h4&gt;

&lt;p&gt;Sistemas real-time precisam de garantias temporais — deadlines que devem ser cumpridos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate Monotonic Scheduling (RMS)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Atribui prioridade fixa inversamente proporcional ao período da tarefa. Tarefas com períodos menores (mais frequentes) recebem prioridade mais alta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Earliest Deadline First (EDF)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prioridade dinâmica: o processo com deadline mais próximo executa primeiro. Teoricamente ótimo — pode atingir 100% de utilização de CPU.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Conexão com o kernel&lt;/strong&gt;: O Linux suporta escalonamento real-time via &lt;code&gt;SCHED_FIFO&lt;/code&gt;, &lt;code&gt;SCHED_RR&lt;/code&gt; e, a partir do kernel 3.14, &lt;code&gt;SCHED_DEADLINE&lt;/code&gt; (baseado em EDF). Embora a maioria das aplicações backend não precise de real-time, entender essas classes é importante para diagnosticar problemas quando um processo real-time inadvertidamente monopoliza CPU.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Escalonamento Preemptivo vs Não-Preemptivo
&lt;/h3&gt;

&lt;p&gt;A distinção entre escalonamento &lt;strong&gt;preemptivo&lt;/strong&gt; e &lt;strong&gt;não-preemptivo&lt;/strong&gt; é fundamental para entender o comportamento do Linux:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Não-preemptivo (cooperativo)&lt;/strong&gt;: O processo mantém a CPU até voluntariamente liberá-la (terminar, bloquear em I/O, ou ceder via &lt;code&gt;yield()&lt;/code&gt;). Simples, mas um processo mal-comportado pode monopolizar a CPU.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Preemptivo&lt;/strong&gt;: O kernel pode forçar a remoção de um processo da CPU a qualquer momento (tipicamente quando seu timeslice expira ou um processo de maior prioridade fica pronto). Mais complexo, mas garante responsividade.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Não-preemptivo:
P1 (CPU-bound, buggy): |████████████████████████████████████████|
P2 (sua API):          |░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░|  ← nunca executa!

Preemptivo (quantum = 10ms):
P1: |████|    |████|    |████|    |████|    |████|
P2:      |████|    |████|    |████|    |████|
          ↑ kernel preempta P1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O Linux é &lt;strong&gt;totalmente preemptivo&lt;/strong&gt; no userspace — o kernel pode preemptar qualquer processo em modo usuário a qualquer momento. O kernel em si tem diferentes níveis de preempção configuráveis:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuração&lt;/th&gt;
&lt;th&gt;Comportamento&lt;/th&gt;
&lt;th&gt;Uso típico&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PREEMPT_NONE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Kernel não-preemptivo&lt;/td&gt;
&lt;td&gt;Servidores de throughput máximo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PREEMPT_VOLUNTARY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Preempção em pontos explícitos&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Default na maioria das distros server&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PREEMPT_FULL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Kernel totalmente preemptivo&lt;/td&gt;
&lt;td&gt;Desktop, baixa latência&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PREEMPT_RT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Real-time, preempção determinística&lt;/td&gt;
&lt;td&gt;Sistemas embarcados, áudio profissional&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Implicação prática&lt;/strong&gt;: Distros server como Ubuntu Server e RHEL usam &lt;code&gt;PREEMPT_VOLUNTARY&lt;/code&gt; por default. Se sua aplicação precisa de latência ultra-baixa (ex: trading), pode ser benéfico usar um kernel com &lt;code&gt;PREEMPT_FULL&lt;/code&gt; ou &lt;code&gt;PREEMPT_RT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implicação prática&lt;/strong&gt;: Imagens docker que utilizam o kernel do host herdam a configuração de preempção do host. Portanto, mesmo dentro de um container, o comportamento de escalonamento é ditado pelo kernel do host.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Problema da Inversão de Prioridade
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;inversão de prioridade&lt;/strong&gt; ocorre quando um processo de alta prioridade é indiretamente bloqueado por um de baixa prioridade, violando a política de escalonamento.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cenário clássico (Mars Pathfinder, 1997):

Prioridade Alta (H):   Task meteorológica (deadline crítico)
Prioridade Média (M):  Task de comunicação (longa)
Prioridade Baixa (L):  Task de coleta de dados

Sequência do problema:
1. L adquire mutex M₁
2. L é preemptado por H
3. H tenta adquirir M₁ → bloqueado (L detém M₁)
4. M fica pronto e executa (maior prioridade que L)
5. M executa por tempo arbitrário
6. H continua bloqueado — inversão de prioridade!

Timeline:
L:  |██|      |░░░░░░░░░░░░░░░░|██|──unlock──|
M:  |  |      |████████████████|  |           |
H:  |  |██|→blocked            |  |           |██████|
         ↑                                     ↑
    tenta lock                            finalmente executa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Soluções&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Priority Inheritance&lt;/strong&gt;: Quando H bloqueia em um lock detido por L, L temporariamente "herda" a prioridade de H, impedindo que M execute no meio. O kernel Linux implementa isso para &lt;code&gt;rt_mutex&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Priority Ceiling&lt;/strong&gt;: O mutex recebe a prioridade do processo de maior prioridade que pode usá-lo. Qualquer processo que adquire o mutex tem sua prioridade elevada ao ceiling.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Implicação prática&lt;/strong&gt;: Em Go, a inversão de prioridade pode ocorrer entre goroutines quando uma goroutine de alta prioridade (tratando request HTTP) bloqueia em um &lt;code&gt;sync.Mutex&lt;/code&gt; detido por uma goroutine de baixa prioridade (fazendo log assíncrono), enquanto goroutines de prioridade média consomem os threads do runtime. O scheduler do Go não implementa priority inheritance — é responsabilidade do desenvolvedor minimizar a contenção de locks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Starvation e Aging
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Starvation&lt;/strong&gt; ocorre quando um processo nunca recebe CPU porque processos de maior prioridade estão sempre prontos. Em sistemas com priority scheduling puro, processos de baixa prioridade podem ser indefinidamente postergados.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starvation:
Tempo →  0    10    20    30    40    50    60    70    80
Prio 1:  |████|████|████|████|████|████|████|████|████|
Prio 2:  |░░░░|░░░░|░░░░|░░░░|░░░░|░░░░|░░░░|░░░░|░░░░|  ← nunca executa!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Aging&lt;/strong&gt; é a solução clássica: a prioridade de um processo aumenta gradualmente quanto mais tempo ele espera na ready queue. Eventualmente, mesmo o processo de menor prioridade terá prioridade suficiente para executar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Aging:
Tempo →  0    10    20    30    40    50    60
Prio P2: 10   11    12    13    14    15    16  ← agora compete com Prio 1!
                                          |████| P2 finalmente executa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O CFS do Linux implementa uma forma sofisticada de aging através do &lt;strong&gt;virtual runtime&lt;/strong&gt;: processos que receberam menos CPU têm vruntime menor e são naturalmente favorecidos pelo escalonador. Isso torna starvation virtualmente impossível no CFS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Como o Linux Implementa Escalonamento: Visão Geral
&lt;/h3&gt;

&lt;p&gt;O escalonador do Linux organiza os algoritmos em &lt;strong&gt;scheduling classes&lt;/strong&gt;, cada uma implementando uma política diferente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hierarquia de Scheduling Classes (maior → menor prioridade):

    ┌─────────────────────────────────────────────┐
    │  stop_sched_class                           │ ← Migration threads (interno)
    ├─────────────────────────────────────────────┤
    │  dl_sched_class (SCHED_DEADLINE)            │ ← EDF: deadline-based
    ├─────────────────────────────────────────────┤
    │  rt_sched_class (SCHED_FIFO,SCHED_RR)       │ ← Real-time: prioridade fixa
    ├─────────────────────────────────────────────┤
    │  fair_sched_class (SCHED_NORMAL,SCHED_BATCH)│ ← CFS: a maioria dos processos
    ├─────────────────────────────────────────────┤
    │  idle_sched_class (SCHED_IDLE)              │ ← Executa apenas quando nada mais
    └─────────────────────────────────────────────┘

O kernel percorre as classes de cima para baixo.
Se uma classe de maior prioridade tem um processo pronto, ele executa.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para a vasta maioria das aplicações backend, os processos rodam na classe &lt;code&gt;fair_sched_class&lt;/code&gt; com política &lt;code&gt;SCHED_NORMAL&lt;/code&gt;. É aqui que o &lt;strong&gt;CFS (Completely Fair Scheduler)&lt;/strong&gt; — e seu sucessor &lt;strong&gt;EEVDF&lt;/strong&gt; no kernel 6.6+ — opera.&lt;/p&gt;

&lt;h4&gt;
  
  
  Prioridades e Nice Values
&lt;/h4&gt;

&lt;p&gt;O Linux mapeia o conceito de prioridade em dois espaços numéricos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nice values (userspace):     -20 ────────── 0 ────────── +19
                              ↑ maior prio   normal      ↑ menor prio

Static priority (kernel):     100 ─────────120─────────── 139
                              ↑ nice -20   nice 0        ↑ nice +19

Real-time priorities:         0 ──────────────────────── 99
                              ↑ menor prio rt            ↑ maior prio rt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Executando um processo com prioridade alterada&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;nice&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt; python3 app.py          &lt;span class="c"&gt;# maior prioridade (precisa de root para nice &amp;lt; 0)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;nice&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 10 python3 batch_job.py    &lt;span class="c"&gt;# menor prioridade&lt;/span&gt;

&lt;span class="c"&gt;# Alterando prioridade de processo em execução&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;renice &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 1350               &lt;span class="c"&gt;# aumenta prioridade do PID 1350&lt;/span&gt;

&lt;span class="c"&gt;# Verificando nice value&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;ps &lt;span class="nt"&gt;-eo&lt;/span&gt; pid,ni,comm | &lt;span class="nb"&gt;grep &lt;/span&gt;python
1350  &lt;span class="nt"&gt;-5&lt;/span&gt; python3
1400  10 python3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Dica prática&lt;/strong&gt;: Em um servidor que roda tanto APIs quanto batch jobs, use &lt;code&gt;nice&lt;/code&gt; para dar menor prioridade aos batch jobs. Isso garante que suas APIs mantêm boa responsividade mesmo durante processamento pesado em background.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Métricas de Escalonamento na Prática
&lt;/h3&gt;

&lt;p&gt;Para avaliar como o escalonamento afeta sua aplicação, monitore estas métricas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Context switches do sistema (total)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;vmstat 1
procs &lt;span class="nt"&gt;-----------memory----------&lt;/span&gt; &lt;span class="nt"&gt;---swap--&lt;/span&gt; &lt;span class="nt"&gt;-----io----&lt;/span&gt; &lt;span class="nt"&gt;-system--&lt;/span&gt; &lt;span class="nt"&gt;------cpu-----&lt;/span&gt;
 r  b   swpd   free   buff  cache   si   so    bi    bo   &lt;span class="k"&gt;in   &lt;/span&gt;cs us sy &lt;span class="nb"&gt;id &lt;/span&gt;wa st
 3  0      0 245612  45632 1234567    0    0     5    12  256 4521 15  3 80  2  0
                                                           ↑    ↑
                                                     interrupts  context switches

&lt;span class="c"&gt;# Context switches por processo&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&amp;lt;pid&amp;gt;/status | &lt;span class="nb"&gt;grep &lt;/span&gt;ctxt
voluntary_ctxt_switches:    15230
nonvoluntary_ctxt_switches: 892

&lt;span class="c"&gt;# Run queue length (processos aguardando CPU)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/loadavg
2.15 1.80 1.45 3/412 28503
↑    ↑    ↑    ↑
1m   5m   15m  running/total

&lt;span class="c"&gt;# Latência de escalonamento com perf&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;perf sched latency
&lt;span class="nt"&gt;-------------------------------------------------&lt;/span&gt;
  Task                  |   Runtime ms  | Switches | Avg delay ms |
&lt;span class="nt"&gt;-------------------------------------------------&lt;/span&gt;
  python3:1350          |    1052.340   |    15230 |    0.045     |
  dotnet:950            |     876.230   |     8920 |    0.032     |
  nginx:601             |     234.120   |    42310 |    0.012     |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A coluna &lt;strong&gt;Avg delay ms&lt;/strong&gt; no &lt;code&gt;perf sched latency&lt;/code&gt; mostra quanto tempo, em média, o processo esperou na ready queue antes de ser escalonado. Valores altos indicam contenção de CPU.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Visualizando scheduling events em tempo real&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;perf sched record &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;10    &lt;span class="c"&gt;# grava 10 segundos&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;perf sched map                   &lt;span class="c"&gt;# mapa visual de scheduling&lt;/span&gt;

&lt;span class="c"&gt;# Exemplo de saída:&lt;/span&gt;
&lt;span class="c"&gt;#           *A0          . .  .  .  .  .  .    846.275762 secs A0 =&amp;gt; python3:1350&lt;/span&gt;
&lt;span class="c"&gt;#            A0          *B0 .  .  .  .  .     846.275800 secs B0 =&amp;gt; nginx:601&lt;/span&gt;
&lt;span class="c"&gt;#            A0           B0 *C0 .  .  .  .    846.275845 secs C0 =&amp;gt; postgres:1500&lt;/span&gt;
&lt;span class="c"&gt;#           *A0           B0  C0 .  .  .  .    846.275900 secs&lt;/span&gt;
&lt;span class="c"&gt;#            A0          *B0  C0 .  .  .  .    846.275950 secs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;a href="https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-processos-threads-parte-iii-1187"&gt;&lt;strong&gt;Continua na parte III&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conteudo parcialmente gerado com auxilio de IA generativa (eu organizei o conteudo e ela me ajudou com lero lero, novos tempos kkkk)&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencias Bibliográficas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Desenvolvimento-do-Kernel-Linux/dp/8573933410" rel="noopener noreferrer"&gt;Desenvolvimento Do Kernel Do Linux - David Cram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Linux-Bible-Christopher-Negus/dp/1394317468/ref=sr_1_1?__mk_pt_BR=%C3%85M%C3%85%C5%BD%C3%95%C3%91&amp;amp;dib=eyJ2IjoiMSJ9.ep2zHSLrXTmnOmqryZZJPcwOnbqsPqlHDKyK_8FK75E7IdfT3OQ4iSeLNg4aDkEbas_KyjlckRv_HAqF0-rXbwY0A7IAJnyqEquSkUVLVco_qSolsvkdEK8LeRJ7GQcp8e8AIbQoxZMwHdkqzqy0WHcbLqaF3pcBaRdo4HBaO_m9ZJTLKY9TXza9uJCvonORaFc81XM-Gp76W7qwYVmuo33vr9HQHPeeyrrK2rw_dPY.CC-nHGj2vuWkT6U5wHlf4BLCa2H5hJDXH_Xg72Hue10&amp;amp;dib_tag=se&amp;amp;keywords=linux+a+biblia&amp;amp;qid=1780931929&amp;amp;s=books&amp;amp;sprefix=linux+a+bi%2Cstripbooks%2C1135&amp;amp;sr=1-1&amp;amp;ufe=app_do%3Aamzn1.fos.fcd6d665-32ba-4479-9f21-b774e276a678" rel="noopener noreferrer"&gt;Linux Bible - Chsristopher Negus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.ibm.com/articles/l-linux-kernel/" rel="noopener noreferrer"&gt;Linux Kernel: An Introduction&lt;/a&gt; - IBM Developer&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Sistemas-Operacionais-Modernos-Andrew-Tanenbaum/dp/8582606168/ref=sr_1_1?__mk_pt_BR=%C3%85M%C3%85%C5%BD%C3%95%C3%91&amp;amp;dib=eyJ2IjoiMSJ9.GT4sX07Q-JQVNuedOvqQ5ZO7y1vPyznY4qtp_jih_s6jnDsrFJut_q6oT6io7p-I4c2hke9cKBU-DXK1GrwEjyvNZQbXAMjxsM1C6oDQqUybKWMEHkoJo3VQvzLYVU4XXCGkjDiNVI_fYu7spu33HDSpcBcZ891_HBZu4218XEvpnWNCWv6D5pM2XF0qZnFJeNTYoTSbSf6aldeB0RoH1cQ62o63NXV8a8HNh9qdNJs.anlokyxWbWuareiNAhSAhsoxohIr4FfNT9TcagLW980&amp;amp;dib_tag=se&amp;amp;keywords=Sistemas+Operacionais&amp;amp;qid=1780931998&amp;amp;s=books&amp;amp;sr=1-1&amp;amp;ufe=app_do%3Aamzn1.fos.fcd6d665-32ba-4479-9f21-b774e276a678" rel="noopener noreferrer"&gt;Sistemas Operacionais Modernos - Tanenbaum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>algorithms</category>
      <category>backend</category>
      <category>computerscience</category>
      <category>linux</category>
    </item>
    <item>
      <title>Kernel Linux para Desenvolvedores Backend - Processos &amp; Threads Parte I</title>
      <dc:creator>Alex Volnei Galante</dc:creator>
      <pubDate>Mon, 08 Jun 2026 18:09:13 +0000</pubDate>
      <link>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-processos-threads-parte-i-1hlp</link>
      <guid>https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-processos-threads-parte-i-1hlp</guid>
      <description>&lt;h2&gt;
  
  
  Sumário da Parte I
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introdução ao Kernel Linux&lt;/li&gt;
&lt;li&gt;Estrutura do Kernel&lt;/li&gt;
&lt;li&gt;Gerenciamento de Processos&lt;/li&gt;
&lt;li&gt;Sua aplicação e o Kernel&lt;/li&gt;
&lt;li&gt;Referências Bibliográficas&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Introdução ao Kernel Linux
&lt;/h2&gt;

&lt;p&gt;A história começa em meados de 1991 quando um nerd que vivia em um país congelado chamado Linus Torvalds decidiu criar um sistema operacional baseado no Unix, mas que fosse gratuito e de código aberto. Ele começou a escrever o código do kernel do Linux em seu computador pessoal, e em pouco tempo, o projeto ganhou a atenção de outros desenvolvedores ao redor do mundo.&lt;/p&gt;

&lt;p&gt;A história completa do Linux e sua união com o GNU pode ser encontrada no livro "&lt;a href="https://www.amazon.com.br/Just-Fun-Story-Accidental-Revolutionary/dp/0066620732" rel="noopener noreferrer"&gt;Just for Fun: The Story of an Accidental Revolutionary&lt;/a&gt;" de Linus Torvalds, é uma leitura muito interessante para quem quer entender a história do Linux e como ele se tornou o que é hoje.&lt;/p&gt;




&lt;h2&gt;
  
  
  Estrutura do Kernel
&lt;/h2&gt;

&lt;p&gt;O kernel possui uma estrutura super complexa, mas podemos dividi-lo em 3 blocos fundamentais:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User Space&lt;/strong&gt;: onde os aplicativos e processos rodam, é a parte com a qual os desenvolvedores backend têm mais contato.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kernel Space&lt;/strong&gt;: onde o kernel do Linux roda, é a parte que gerencia os recursos de hardware e fornece uma interface para os aplicativos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System Calls&lt;/strong&gt;: é a interface entre o user space e o kernel space, é onde os aplicativos fazem chamadas para o kernel para acessar recursos de hardware ou realizar operações privilegiadas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ob637gwdfx3sa10peie.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ob637gwdfx3sa10peie.jpg" alt="Estrutura Kernel Linux" width="511" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Subsistemas do Kernel
&lt;/h3&gt;

&lt;p&gt;O kernel do Linux é composto por vários subsistemas, cada um responsável por uma parte específica do sistema operacional. Alguns dos subsistemas mais importantes incluem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gerenciamento de Processos&lt;/strong&gt;: responsável por criar, gerenciar e finalizar processos no sistema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gerenciamento de Memória&lt;/strong&gt;: responsável por alocar e liberar memória para os processos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gerenciamento de Arquivos&lt;/strong&gt;: responsável por gerenciar o sistema de arquivos e fornecer uma interface para os aplicativos acessarem arquivos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gerenciamento de Dispositivos&lt;/strong&gt;: responsável por gerenciar os dispositivos de hardware e fornecer uma interface para os aplicativos acessarem esses dispositivos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gerenciamento de Rede&lt;/strong&gt;: responsável por gerenciar as conexões de rede e fornecer uma interface para os aplicativos se comunicarem pela rede.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nessa primeira parte, nosso objetivo é desvendar o subsistema de gerenciamento de processos, entender como ele funciona e como ele pode impactar o desenvolvimento backend.&lt;br&gt;
Vamos primeiramente entender o que são processos e threads, e como o kernel do Linux gerencia esses recursos.&lt;/p&gt;


&lt;h2&gt;
  
  
  Processos
&lt;/h2&gt;

&lt;p&gt;Um processo é a abstração mais fundamental que um sistema operacional oferece para a execução de programas. Em termos simples, um processo é &lt;strong&gt;um programa em execução&lt;/strong&gt; — mas essa definição esconde uma complexidade considerável.&lt;/p&gt;

&lt;p&gt;Quando você executa uma aplicação backend — seja um servidor Flask, uma API ASP.NET Core ou um microserviço em Go — o kernel Linux cria um processo que encapsula tudo o que é necessário para aquela execução:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Espaço de endereçamento&lt;/strong&gt;: uma região de memória virtual exclusiva contendo o código (text), dados globais (data/BSS), heap e stack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registradores da CPU&lt;/strong&gt;: o program counter (PC/RIP), o stack pointer (SP/RSP), registradores de propósito geral e registradores de status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recursos do sistema&lt;/strong&gt;: file descriptors abertos, sinais pendentes, informações de credenciais, working directory, mapeamentos de memória&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cada processo opera sob a ilusão de que possui a máquina inteira para si (assim como um S.O virtualizado acredita que controla o hardware completo rsrsrsr). Essa ilusão é construída pelo kernel através de duas abstrações principais: &lt;strong&gt;virtualização de CPU&lt;/strong&gt; (escalonamento) e &lt;strong&gt;virtualização de memória&lt;/strong&gt; (memória virtual).&lt;/p&gt;
&lt;h3&gt;
  
  
  Multiprogramação e Pseudoparalelismo
&lt;/h3&gt;

&lt;p&gt;Em um sistema com uma única CPU, apenas um processo pode executar instruções em um dado instante. No entanto, o kernel alterna entre processos tão rapidamente que, para um observador humano, parece que todos executam simultaneamente. Esse fenômeno é chamado de &lt;strong&gt;pseudoparalelismo&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;multiprogramação&lt;/strong&gt; é a técnica que permite manter múltiplos processos em memória ao mesmo tempo, alternando a CPU entre eles. O objetivo é maximizar a utilização da CPU: quando um processo bloqueia aguardando I/O (uma query ao banco de dados, uma leitura de disco, uma resposta de rede), outro processo pode utilizar a CPU.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tempo →
CPU:  |--P1--|--P2--|--P1--|--P3--|--P2--|--P1--|

P1:   ██████░░░░░░██████░░░░░░░░░░░░░░░░██████
P2:   ░░░░░░██████░░░░░░░░░░░░░░░░██████░░░░░░
P3:   ░░░░░░░░░░░░░░░░░░██████░░░░░░░░░░░░░░░░

██ = executando    ░░ = aguardando/pronto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para aplicações backend, esse modelo tem implicações diretas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Servidores web multi-processo&lt;/strong&gt; (Gunicorn com workers pre-fork, por exemplo) dependem do kernel para distribuir tempo de CPU entre os workers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microserviços em containers&lt;/strong&gt; competem por CPU com outros containers no mesmo host&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;latência de resposta&lt;/strong&gt; da sua API é diretamente afetada pela capacidade do kernel de escalonar seu processo de forma eficiente.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Hierarquia de Processos
&lt;/h3&gt;

&lt;p&gt;No Linux, processos formam uma &lt;strong&gt;árvore hierárquica&lt;/strong&gt;. Todo processo (exceto o &lt;code&gt;init&lt;/code&gt;/&lt;code&gt;systemd&lt;/code&gt;, PID 1) possui um processo pai que o criou. Essa relação é estabelecida pela system call &lt;code&gt;fork()&lt;/code&gt; (ou, mais modernamente, &lt;code&gt;clone()&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;systemd (PID 1)
├── sshd (PID 512)
│   └── bash (PID 1200)
│       └── python app.py (PID 1350)
│           ├── worker-1 (PID 1351)
│           ├── worker-2 (PID 1352)
│           └── worker-3 (PID 1353)
├── dockerd (PID 800)
│   └── containerd-shim (PID 900)
│       └── dotnet MyApi.dll (PID 950)
└── nginx (PID 600)
    ├── nginx worker (PID 601)
    └── nginx worker (PID 602)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa hierarquia não é meramente organizacional — ela tem consequências práticas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sinais&lt;/strong&gt;: quando um processo pai termina, sinais são enviados aos filhos (se você não sabe o que são sinais, fique tranquilo, vamos falar disso em breve...)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Processos zumbis&lt;/strong&gt;: quando um filho termina mas o pai não coleta seu exit status via &lt;code&gt;wait()&lt;/code&gt;/&lt;code&gt;waitpid()&lt;/code&gt;, o processo permanece como zombie, consumindo uma entrada na tabela de processos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Processos órfãos&lt;/strong&gt;: filhos cujo pai terminou são "adotados" pelo &lt;code&gt;init&lt;/code&gt;/&lt;code&gt;systemd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grupos de processos e sessões&lt;/strong&gt;: permitem gerenciar conjuntos de processos relacionados (fundamental para job control em shells e para containers)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]&lt;br&gt;
&lt;strong&gt;Implicação prática&lt;/strong&gt;: Se sua aplicação Python com Gunicorn cria workers via &lt;code&gt;fork()&lt;/code&gt;, cada worker é um processo filho. Se o master process morrer inesperadamente sem cleanup adequado, você pode acabar com workers orphans consumindo recursos.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Estados de Processo
&lt;/h3&gt;

&lt;p&gt;Entender o ciclo de vida de um processo vai permitir que você, desenvolvedor backend, perceba por que a performance de sua aplicação pode ser afetada por fatores que estão fora do seu código — como a carga do sistema, a quantidade de processos concorrentes, o comportamento de I/O, etc.&lt;/p&gt;

&lt;p&gt;Um processo no Linux transita entre estados bem definidos ao longo de sua vida. A compreensão desses estados é essencial para diagnosticar problemas de performance.&lt;/p&gt;

&lt;h4&gt;
  
  
  Os cinco estados fundamentais (modelo teórico)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌─────────────────────────┐
                    │                         │
                    ▼                         │
┌─────┐  admit  ┌───────┐  dispatch ┌─────────┐  exit  ┌────────────┐
│ New │────────►│ Ready │──────────►│ Running │──────► │ Terminated │
└─────┘         └───────┘           └─────────┘        └────────────┘
                    ▲                     │
                    │    I/O or event     │
                    │    completion       │
                    │                     │ I/O or event
                    │                     │ wait
                    │    ┌─────────┐      │
                    └─── │ Blocked │ ◄────┘
                         └─────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;New (Criado)&lt;/strong&gt;: o processo está sendo criado pelo kernel. A &lt;code&gt;task_struct&lt;/code&gt; está sendo alocada e inicializada.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ready (Pronto)&lt;/strong&gt;: o processo está em memória, pronto para executar, aguardando que o escalonador lhe atribua a CPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Running (Executando)&lt;/strong&gt;: o processo está efetivamente utilizando a CPU, executando instruções.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocked (Bloqueado)&lt;/strong&gt;: o processo está aguardando algum evento externo — I/O de disco, resposta de rede, lock de mutex, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminated (Terminado)&lt;/strong&gt;: o processo finalizou sua execução, mas sua entrada na tabela de processos ainda existe até que o pai colete o exit status.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Estados no kernel Linux
&lt;/h4&gt;

&lt;p&gt;O kernel Linux implementa esses estados conceituais com granularidade adicional, definidos no campo &lt;code&gt;state&lt;/code&gt; da &lt;code&gt;task_struct&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Estado do Kernel&lt;/th&gt;
&lt;th&gt;Valor&lt;/th&gt;
&lt;th&gt;Significado&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TASK_RUNNING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Processo executando ou na fila de prontos (ready queue)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TASK_INTERRUPTIBLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Bloqueado, mas pode ser acordado por sinais&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TASK_UNINTERRUPTIBLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bloqueado em I/O crítico, não responde a sinais&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__TASK_STOPPED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Parado por sinal (SIGSTOP, SIGTSTP)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__TASK_TRACED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Sendo rastreado por debugger (ptrace)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EXIT_ZOMBIE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Terminado, aguardando &lt;code&gt;wait()&lt;/code&gt; do pai&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EXIT_DEAD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;Estado final antes da remoção&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TASK_IDLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Idle (kernel 4.21+), similar a UNINTERRUPTIBLE mas não conta como load&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A distinção entre &lt;code&gt;TASK_INTERRUPTIBLE&lt;/code&gt; e &lt;code&gt;TASK_UNINTERRUPTIBLE&lt;/code&gt; é particularmente importante:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Processos em &lt;code&gt;TASK_UNINTERRUPTIBLE&lt;/code&gt; (estado &lt;strong&gt;D&lt;/strong&gt; no &lt;code&gt;ps&lt;/code&gt;/&lt;code&gt;top&lt;/code&gt;) &lt;strong&gt;contam para o load average&lt;/strong&gt; do sistema. Se sua aplicação tem muitos processos nesse estado, geralmente indica problemas de I/O — disco lento, NFS travado, ou storage com latência alta.&lt;/li&gt;
&lt;li&gt;Processos em &lt;code&gt;TASK_INTERRUPTIBLE&lt;/code&gt; (estado &lt;strong&gt;S&lt;/strong&gt;) são o caso normal de processos aguardando I/O — um servidor web esperando conexões, por exemplo.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Visualizando estados de processos&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;ps aux | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
USER       PID %CPU %MEM    VSZ   RSS TTY STAT START   TIME COMMAND
root         1  0.0  0.1 169536 13312 ?   Ss   May01   0:12 /sbin/init
root         2  0.0  0.0      0     0 ?   S    May01   0:00 &lt;span class="o"&gt;[&lt;/span&gt;kthreadd]
www-data  1200  2.3  1.5 285432 61440 ?   Sl   09:00   1:45 gunicorn: worker
postgres  1500  0.1  0.8 215000 32768 ?   Ss   May01   0:55 postgres: writer

&lt;span class="c"&gt;# STAT column: S=sleeping(interruptible), D=disk sleep(uninterruptible),&lt;/span&gt;
&lt;span class="c"&gt;#              R=running, T=stopped, Z=zombie, l=multi-threaded, s=session leader&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
&lt;strong&gt;Dica de diagnóstico&lt;/strong&gt;: Se o load average do seu servidor está alto, mas a utilização de CPU é baixa, procure processos no estado &lt;strong&gt;D&lt;/strong&gt; (&lt;code&gt;TASK_UNINTERRUPTIBLE&lt;/code&gt;). Isso indica gargalo de I/O, não de CPU.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Como um processo é criado?
&lt;/h3&gt;

&lt;p&gt;A criação de um processo no Linux é realizada através da system call &lt;code&gt;fork()&lt;/code&gt; ou, mais modernamente, &lt;code&gt;clone()&lt;/code&gt;. O processo pai chama &lt;code&gt;fork()&lt;/code&gt;, que cria um novo processo filho duplicando o contexto do pai — incluindo código, dados, heap e stack. O filho recebe um novo PID e é colocado na fila de prontos para execução. O processo filho pode então chamar &lt;code&gt;execve()&lt;/code&gt; para substituir sua própria imagem por um novo programa, ou pai e filho podem simplesmente continuar executando o mesmo código.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;sys/types.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;sys/wait.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pid_t&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// syscall: duplica o processo atual&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// fork() retorna -1 em caso de erro&lt;/span&gt;
        &lt;span class="n"&gt;perror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fork falhou"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Estamos no processo filho (fork() retorna 0 para o filho)&lt;/span&gt;
        &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[filho] PID=%d, pai PID=%d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;getppid&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="c1"&gt;// execve() substitui a imagem do processo pelo programa especificado.&lt;/span&gt;
        &lt;span class="c1"&gt;// A partir daqui, o filho passa a executar /bin/echo.&lt;/span&gt;
        &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"/bin/echo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"[filho] execve: processo substituído com sucesso"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="n"&gt;execve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/bin/echo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Só chega aqui se execve() falhar&lt;/span&gt;
        &lt;span class="n"&gt;perror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"execve falhou"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Estamos no processo pai (fork() retorna o PID do filho para o pai)&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[pai] PID=%d, filho PID=%d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// wait() bloqueia o pai até o filho terminar, evitando processo zumbi&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;waitpid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[pai] filho encerrou com status %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WEXITSTATUS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;EXIT_SUCCESS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A mesma criação pode ser feita com &lt;code&gt;clone()&lt;/code&gt;, que é a syscall de baixo nível usada internamente pelo próprio &lt;code&gt;fork()&lt;/code&gt; — com a diferença de que &lt;code&gt;clone()&lt;/code&gt; permite controlar exatamente o que será compartilhado entre pai e filho, viabilizando a criação de &lt;strong&gt;threads&lt;/strong&gt; (onde memória, file descriptors e outros recursos são compartilhados):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define _GNU_SOURCE
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;sched.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;sys/types.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;sys/wait.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cp"&gt;#define STACK_SIZE (1024 * 1024) // 1 MB de stack para o filho
&lt;/span&gt;
&lt;span class="c1"&gt;// Função que será executada pelo processo/thread filho&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;filho_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[filho] PID=%d, pai PID=%d, arg='%s'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;getppid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Aloca stack para o filho (clone() exige que o chamador forneça a stack)&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;STACK_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;perror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"malloc falhou"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// clone() recebe um ponteiro para o TOPO da stack (cresce para baixo)&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;stack_top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;STACK_SIZE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Flags controlam o que será compartilhado entre pai e filho.&lt;/span&gt;
    &lt;span class="c1"&gt;// SIGCHLD: sinaliza o pai quando o filho terminar (necessário para waitpid).&lt;/span&gt;
    &lt;span class="c1"&gt;// Sem flags de compartilhamento → comportamento idêntico ao fork().&lt;/span&gt;
    &lt;span class="n"&gt;pid_t&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filho_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stack_top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SIGCHLD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dados do pai"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;perror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"clone falhou"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;EXIT_FAILURE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[pai] PID=%d, filho PID=%d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;waitpid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[pai] filho encerrou com status %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WEXITSTATUS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;EXIT_SUCCESS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Flags do &lt;code&gt;clone()&lt;/code&gt; e o que cada uma controla
&lt;/h3&gt;

&lt;p&gt;A principal diferença entre &lt;code&gt;fork()&lt;/code&gt; e &lt;code&gt;clone()&lt;/code&gt; está nas &lt;strong&gt;flags&lt;/strong&gt; que &lt;code&gt;clone()&lt;/code&gt; aceita. Elas definem precisamente quais recursos serão &lt;strong&gt;compartilhados&lt;/strong&gt; (e não copiados) entre o processo pai e o filho:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Efeito&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_VM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compartilha o &lt;strong&gt;espaço de memória virtual&lt;/strong&gt; — pai e filho enxergam as mesmas páginas. Sem essa flag, o kernel aplica Copy-on-Write (CoW).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_FS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compartilha o &lt;strong&gt;contexto de sistema de arquivos&lt;/strong&gt; (working directory, root, umask).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_FILES&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compartilha a &lt;strong&gt;tabela de file descriptors&lt;/strong&gt; — um &lt;code&gt;close()&lt;/code&gt; no pai fecha para o filho também.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_SIGHAND&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compartilha os &lt;strong&gt;handlers de sinais&lt;/strong&gt;. Obrigatório junto com &lt;code&gt;CLONE_VM&lt;/code&gt; para threads POSIX.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_THREAD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Coloca o filho no mesmo &lt;strong&gt;thread group&lt;/strong&gt; do pai (mesmo &lt;code&gt;tgid&lt;/code&gt;). Necessário para que &lt;code&gt;getpid()&lt;/code&gt; retorne o mesmo valor em todas as threads.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_NEWPID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cria um &lt;strong&gt;novo namespace de PIDs&lt;/strong&gt; — a base dos containers (o filho vira PID 1 dentro do namespace).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_NEWNET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cria um &lt;strong&gt;novo namespace de rede&lt;/strong&gt; — interfaces, rotas e portas isoladas.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLONE_NEWNS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cria um &lt;strong&gt;novo namespace de mount&lt;/strong&gt; — sistema de arquivos isolado.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SIGCHLD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sinal enviado ao pai quando o filho terminar (necessário para &lt;code&gt;waitpid()&lt;/code&gt; funcionar).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
&lt;strong&gt;Threads vs Processos no Linux&lt;/strong&gt;: ao contrário de outros sistemas operacionais, o Linux não tem um conceito de "thread" separado no kernel. Uma thread POSIX é simplesmente um &lt;code&gt;clone()&lt;/code&gt; com &lt;code&gt;CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD&lt;/code&gt;. A distinção entre processo e thread é feita pelas flags passadas ao &lt;code&gt;clone()&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Principais syscalls do ciclo de criação de processos
&lt;/h3&gt;

&lt;p&gt;As syscalls abaixo formam o núcleo do gerenciamento de processos no Linux. Toda linguagem, framework ou runtime que cria processos ou threads passa por alguma combinação delas:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Syscall&lt;/th&gt;
&lt;th&gt;Número (x86-64)&lt;/th&gt;
&lt;th&gt;Descrição&lt;/th&gt;
&lt;th&gt;Quando é usada&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fork()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;Duplica o processo atual. Filho herda uma cópia do espaço de endereçamento do pai via Copy-on-Write. Retorna 0 para o filho e o PID do filho para o pai.&lt;/td&gt;
&lt;td&gt;Criação de processos filhos (Gunicorn workers, subprocessos de shell)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clone()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;td&gt;Versão parametrizável de &lt;code&gt;fork()&lt;/code&gt;. Flags definem o que é compartilhado (memória, FDs, handlers de sinal). Base para criação de threads POSIX e namespaces de containers.&lt;/td&gt;
&lt;td&gt;Threads (&lt;code&gt;pthread_create&lt;/code&gt;), containers (Docker, runc), runtimes de linguagens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;execve()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;Substitui a imagem do processo atual por um novo programa. O PID é mantido, mas código, dados, heap e stack são trocados.&lt;/td&gt;
&lt;td&gt;Inicialização de qualquer programa: &lt;code&gt;python app.py&lt;/code&gt;, &lt;code&gt;./api&lt;/code&gt;, &lt;code&gt;dotnet MyApi.dll&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;waitpid()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;61&lt;/td&gt;
&lt;td&gt;Bloqueia o processo pai até que um filho específico termine, coletando seu exit status. Evita processos zumbi.&lt;/td&gt;
&lt;td&gt;Qualquer pai que cria filhos com &lt;code&gt;fork()&lt;/code&gt; ou &lt;code&gt;clone()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;exit_group()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;231&lt;/td&gt;
&lt;td&gt;Encerra o processo e todas as suas threads, liberando recursos. É chamada quando &lt;code&gt;main()&lt;/code&gt; retorna ou quando &lt;code&gt;exit()&lt;/code&gt; é invocado.&lt;/td&gt;
&lt;td&gt;Término normal de qualquer processo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getpid()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;Retorna o PID do processo corrente. Para threads do mesmo grupo, retorna o PID do grupo (TGID).&lt;/td&gt;
&lt;td&gt;Diagnóstico, logging, sistemas de lock baseados em PID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getppid()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;110&lt;/td&gt;
&lt;td&gt;Retorna o PID do processo pai. Útil para detectar se o pai morreu (retorna 1 se adotado pelo &lt;code&gt;init&lt;/code&gt;).&lt;/td&gt;
&lt;td&gt;Verificação de "pai vivo" em daemons e supervisores de processo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kill()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;Envia um sinal a um processo ou grupo de processos. Apesar do nome, é usada para qualquer sinal — não apenas para encerramento.&lt;/td&gt;
&lt;td&gt;Envio de &lt;code&gt;SIGTERM&lt;/code&gt;, &lt;code&gt;SIGKILL&lt;/code&gt;, &lt;code&gt;SIGHUP&lt;/code&gt; a workers e daemons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prctl()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;157&lt;/td&gt;
&lt;td&gt;Controla comportamentos específicos do processo: nome (&lt;code&gt;PR_SET_NAME&lt;/code&gt;), comportamento ao morte do pai (&lt;code&gt;PR_SET_PDEATHSIG&lt;/code&gt;), capacidades, etc.&lt;/td&gt;
&lt;td&gt;Nomeação de threads para diagnóstico, hardening de segurança&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;setrlimit()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;160&lt;/td&gt;
&lt;td&gt;Define limites de recursos do processo: número de FDs abertos, tamanho máximo de stack, uso de CPU, memória, etc.&lt;/td&gt;
&lt;td&gt;Configuração de ulimits em servidores, containers (cgroups v1 usa isso indiretamente)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
&lt;strong&gt;Como observar essas syscalls em sua aplicação&lt;/strong&gt;: a ferramenta &lt;code&gt;strace&lt;/code&gt; intercepta e exibe todas as syscalls feitas por um processo em tempo real. Para ver o ciclo de criação completo de um processo Python, por exemplo: &lt;code&gt;strace -e trace=fork,clone,execve,waitpid python3 -c "import os; os.fork()"&lt;/code&gt;. O número da syscall (coluna "Número") corresponde ao valor em &lt;code&gt;rax&lt;/code&gt; no momento da instrução &lt;code&gt;syscall&lt;/code&gt; em x86-64.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Como sua aplicação é iniciada pelo kernel
&lt;/h3&gt;

&lt;p&gt;Quando você digita &lt;code&gt;python app.py&lt;/code&gt;, &lt;code&gt;./minha-api&lt;/code&gt; ou &lt;code&gt;dotnet MyApi.dll&lt;/code&gt; no terminal, uma sequência bem definida de eventos acontece antes de qualquer linha do seu código ser executada. Entender esse fluxo ajuda a compreender por que configurações de ambiente, limites de recursos e permissões afetam sua aplicação desde o primeiro instante.&lt;/p&gt;

&lt;p&gt;O fluxo geral é sempre o mesmo, independente da linguagem: o shell (ou outro processo pai) chama &lt;code&gt;fork()&lt;/code&gt; para se duplicar e, em seguida, o filho chama &lt;code&gt;execve()&lt;/code&gt; para substituir sua imagem pelo executável da sua aplicação. O kernel então carrega o binário, configura o espaço de endereçamento e transfere o controle para o ponto de entrada do programa.&lt;/p&gt;

&lt;h4&gt;
  
  
  Python (&lt;code&gt;python app.py&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Ao executar um script Python, o kernel carrega o binário do interpretador (&lt;code&gt;/usr/bin/python3&lt;/code&gt;) via &lt;code&gt;execve()&lt;/code&gt;. O interpretador é um executável ELF nativo — é &lt;em&gt;ele&lt;/em&gt; que vira o processo, não o seu script. A partir daí:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;O dynamic linker (&lt;code&gt;ld.so&lt;/code&gt;) carrega as bibliotecas compartilhadas do CPython (como &lt;code&gt;libpython3.x.so&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;O CPython inicializa seu runtime: configura o GIL, o gerenciador de memória (&lt;code&gt;pymalloc&lt;/code&gt;) e o sistema de módulos&lt;/li&gt;
&lt;li&gt;O interpretador abre e compila &lt;code&gt;app.py&lt;/code&gt; para bytecode (&lt;code&gt;.pyc&lt;/code&gt;) em memória&lt;/li&gt;
&lt;li&gt;A execução do bytecode começa — somente aqui seu código roda&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Todo esse bootstrap acontece antes de a primeira linha do seu &lt;code&gt;app.py&lt;/code&gt; ser lida. É por isso que um &lt;code&gt;import&lt;/code&gt; pesado no topo do módulo eleva o tempo de inicialização do processo.&lt;/p&gt;

&lt;h4&gt;
  
  
  Go (&lt;code&gt;./minha-api&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Diferente de Python, um binário Go é &lt;strong&gt;compilado estaticamente&lt;/strong&gt; por padrão — não depende de um interpretador. O kernel carrega o ELF diretamente via &lt;code&gt;execve()&lt;/code&gt; e:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;O dynamic linker tem pouco ou nenhum trabalho (binário estático)&lt;/li&gt;
&lt;li&gt;O &lt;strong&gt;runtime Go&lt;/strong&gt; é inicializado: o scheduler M:N é configurado, as threads do SO (M) são criadas via &lt;code&gt;clone()&lt;/code&gt; com &lt;code&gt;CLONE_VM | CLONE_THREAD&lt;/code&gt;, e as estruturas de goroutines (G) são preparadas&lt;/li&gt;
&lt;li&gt;A goroutine principal (&lt;code&gt;main goroutine&lt;/code&gt;) é criada e agendada&lt;/li&gt;
&lt;li&gt;A função &lt;code&gt;main()&lt;/code&gt; do seu pacote &lt;code&gt;main&lt;/code&gt; é chamada&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O número de threads do SO criadas nesse bootstrap é controlado por &lt;code&gt;GOMAXPROCS&lt;/code&gt; (padrão: número de CPUs lógicas disponíveis, respeitando cgroups em containers). Por isso binários Go iniciam tão rapidamente e já nascem prontos para paralelismo real.&lt;/p&gt;

&lt;h4&gt;
  
  
  .NET (&lt;code&gt;dotnet MyApi.dll&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;O comando &lt;code&gt;dotnet&lt;/code&gt; é o &lt;strong&gt;host do CLR&lt;/strong&gt; — um executável nativo que o kernel carrega via &lt;code&gt;execve()&lt;/code&gt;. A DLL com seu código é passada como argumento. O processo de inicialização:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;O host carrega o CoreCLR (&lt;code&gt;libcoreclr.so&lt;/code&gt;) via dynamic linker&lt;/li&gt;
&lt;li&gt;O CLR inicializa o JIT compiler, o Garbage Collector e o ThreadPool&lt;/li&gt;
&lt;li&gt;O ThreadPool cria um conjunto inicial de threads do SO via &lt;code&gt;clone()&lt;/code&gt; (com &lt;code&gt;CLONE_VM | CLONE_THREAD&lt;/code&gt;) prontas para executar work items&lt;/li&gt;
&lt;li&gt;O assembly &lt;code&gt;MyApi.dll&lt;/code&gt; é carregado, o método &lt;code&gt;Main&lt;/code&gt; é localizado, o JIT compila o IL para código nativo e a execução começa&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O GC do .NET configura suas gerações de memória e barreiras de escrita durante essa inicialização — o que explica por que o .NET tem um footprint de memória inicial maior do que Go, mas amortiza esse custo ao longo do tempo de vida do processo com otimizações de JIT (tiered compilation).&lt;/p&gt;

&lt;h4&gt;
  
  
  Resumo comparativo
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Python&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;.NET&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;O que o kernel carrega&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;python3&lt;/code&gt; (interpretador)&lt;/td&gt;
&lt;td&gt;binário ELF nativo&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;dotnet&lt;/code&gt; (host CLR)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seu código chega ao CPU via&lt;/td&gt;
&lt;td&gt;interpretação de bytecode&lt;/td&gt;
&lt;td&gt;compilação AOT&lt;/td&gt;
&lt;td&gt;JIT (tiered compilation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Threads do SO no startup&lt;/td&gt;
&lt;td&gt;1 (+ GIL)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GOMAXPROCS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pool inicial do ThreadPool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paralelismo de CPU real&lt;/td&gt;
&lt;td&gt;apenas com &lt;code&gt;multiprocessing&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;goroutines em N threads&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Task&lt;/code&gt;/&lt;code&gt;Thread&lt;/code&gt; em N threads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tempo de startup típico&lt;/td&gt;
&lt;td&gt;lento (inicialização do runtime + imports)&lt;/td&gt;
&lt;td&gt;muito rápido (binário estático)&lt;/td&gt;
&lt;td&gt;moderado (JIT warmup)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Process Control Block (PCB): &lt;code&gt;task_struct&lt;/code&gt; no Linux
&lt;/h3&gt;

&lt;p&gt;O &lt;strong&gt;Process Control Block&lt;/strong&gt; é a estrutura de dados que o kernel mantém para cada processo, contendo todas as informações necessárias para gerenciá-lo. No Linux, essa estrutura é a &lt;code&gt;task_struct&lt;/code&gt;, definida em &lt;code&gt;include/linux/sched.h&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;task_struct&lt;/code&gt; é uma das maiores estruturas do kernel — com mais de 600 campos em kernels modernos — e inclui:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;task_struct
├── Identificação
│   ├── pid          → PID do processo
│   ├── tgid         → Thread Group ID (PID visível em userspace)
│   ├── comm[16]     → Nome do processo (até 16 caracteres)
│   └── cred         → Credenciais (UID, GID, capabilities)
│
├── Estado e Escalonamento
│   ├── state        → Estado atual (RUNNING, INTERRUPTIBLE, etc.)
│   ├── prio         → Prioridade efetiva
│   ├── static_prio  → Prioridade estática (nice value mapeada)
│   ├── normal_prio  → Prioridade normal calculada
│   ├── policy       → Política de escalonamento (SCHED_NORMAL, etc.)
│   ├── se           → Scheduling entity (para CFS)
│   └── cpus_allowed → Máscara de CPUs permitidas (affinity)
│
├── Memória
│   ├── mm           → Descritor de memória (espaço de endereçamento)
│   └── active_mm    → mm ativo (mesmo para kernel threads)
│
├── Hierarquia
│   ├── parent       → Ponteiro para processo pai
│   ├── children     → Lista de processos filhos
│   └── sibling      → Lista de processos irmãos
│
├── Sistema de Arquivos
│   ├── fs           → Informações de filesystem (root dir, cwd)
│   └── files        → Tabela de file descriptors abertos
│
├── Sinais
│   ├── signal       → Estrutura de sinais compartilhada
│   ├── sighand      → Handlers de sinais
│   └── pending      → Sinais pendentes
│
├── Namespaces e cgroups
│   ├── nsproxy      → Referências aos namespaces
│   └── cgroups      → Associação com control groups
│
└── Contabilidade
    ├── utime        → Tempo em modo usuário
    ├── stime        → Tempo em modo kernel
    └── start_time   → Timestamp de criação
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alguns aspectos dessa estrutura são particularmente relevantes para desenvolvedores backend:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;pid&lt;/code&gt; vs &lt;code&gt;tgid&lt;/code&gt;&lt;/strong&gt;: No kernel, cada thread tem seu próprio &lt;code&gt;pid&lt;/code&gt;. Porém, o que o userspace enxerga como PID é na verdade o &lt;code&gt;tgid&lt;/code&gt; (Thread Group ID). Todas as threads de um processo compartilham o mesmo &lt;code&gt;tgid&lt;/code&gt;. Quando você executa &lt;code&gt;os.getpid()&lt;/code&gt; em Python ou &lt;code&gt;Process.GetCurrentProcess().Id&lt;/code&gt; em .NET, está obtendo o &lt;code&gt;tgid&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;mm&lt;/code&gt; (memory descriptor)&lt;/strong&gt;: Processos que compartilham o mesmo &lt;code&gt;mm&lt;/code&gt; compartilham o mesmo espaço de endereçamento — é isso que define threads vs processos. Quando &lt;code&gt;clone()&lt;/code&gt; é chamado com &lt;code&gt;CLONE_VM&lt;/code&gt;, o novo processo/thread compartilha o &lt;code&gt;mm&lt;/code&gt; do pai.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;files&lt;/code&gt; (file descriptor table)&lt;/strong&gt;: Cada processo tem sua própria tabela de file descriptors. Isso significa que o file descriptor 5 no processo A pode apontar para um arquivo completamente diferente do fd 5 no processo B. Threads, por outro lado, compartilham essa tabela quando criadas com &lt;code&gt;CLONE_FILES&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;nsproxy&lt;/code&gt; e &lt;code&gt;cgroups&lt;/code&gt;&lt;/strong&gt;: Essas são as bases da containerização. Quando sua aplicação roda em Docker/Kubernetes, cada container possui seus próprios namespaces (PID, network, mount, etc.) e está associado a cgroups específicos que limitam CPU, memória e I/O.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ciclo de Vida de um Processo
&lt;/h3&gt;

&lt;p&gt;A criação e destruição de processos no Linux segue um fluxo bem definido:&lt;/p&gt;

&lt;h4&gt;
  
  
  Criação: &lt;code&gt;fork()&lt;/code&gt; e &lt;code&gt;clone()&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Processo Pai                    Kernel                         Processo Filho
     │                            │                                 │
     │── fork()/clone() ─────────►│                                 │
     │                            │── aloca task_struct             │
     │                            │── copia/compartilha recursos    │
     │                            │── configura espaço de endereço  │
     │                            │   (COW - Copy-on-Write)         │
     │                            │── insere na run queue           │
     │                            │                                 │
     │◄── retorna PID do filho ───│── retorna 0 ───────────────────►│
     │                            │                                 │
     │   (continua execução)      │            (continua execução)  │
     │                            │                                 │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O mecanismo de &lt;strong&gt;Copy-on-Write (COW)&lt;/strong&gt; é uma otimização crucial: ao invés de copiar todo o espaço de endereçamento do pai para o filho (operação cara), o kernel marca as páginas de memória como somente leitura e compartilha-as. Apenas quando um dos processos tenta &lt;strong&gt;escrever&lt;/strong&gt; em uma página, o kernel cria uma cópia privada daquela página específica.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;br&gt;
&lt;strong&gt;Implicação prática para Python&lt;/strong&gt;: Servidores como Gunicorn no modo pre-fork criam workers via &lt;code&gt;fork()&lt;/code&gt;. Graças ao COW, os workers inicialmente compartilham a memória do master process (incluindo o código Python carregado, módulos importados, etc.). Porém, o reference counting do CPython modifica os objetos em memória (incrementando/decrementando contadores), o que aciona o COW e gradualmente duplica as páginas. Isso pode resultar em consumo de memória significativamente maior do que o esperado.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Execução: &lt;code&gt;exec()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Frequentemente, após um &lt;code&gt;fork()&lt;/code&gt;, o processo filho substitui sua imagem por um novo programa via &lt;code&gt;exec()&lt;/code&gt;. Isso:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Descarta o espaço de endereçamento atual&lt;/li&gt;
&lt;li&gt;Carrega o novo binário&lt;/li&gt;
&lt;li&gt;Inicializa novos segmentos de text, data, BSS, heap e stack&lt;/li&gt;
&lt;li&gt;Preserva o PID, file descriptors (exceto os marcados com &lt;code&gt;FD_CLOEXEC&lt;/code&gt;), e credenciais&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Terminação: &lt;code&gt;exit()&lt;/code&gt; e &lt;code&gt;wait()&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Quando um processo termina:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Libera a maioria dos seus recursos (memória, file descriptors, etc.)&lt;/li&gt;
&lt;li&gt;Entra no estado &lt;code&gt;EXIT_ZOMBIE&lt;/code&gt; — mantendo apenas a &lt;code&gt;task_struct&lt;/code&gt; com o exit status&lt;/li&gt;
&lt;li&gt;Envia &lt;code&gt;SIGCHLD&lt;/code&gt; ao processo pai&lt;/li&gt;
&lt;li&gt;O pai coleta o exit status via &lt;code&gt;wait()&lt;/code&gt;/&lt;code&gt;waitpid()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;O kernel remove a &lt;code&gt;task_struct&lt;/code&gt; — o processo deixa de existir
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Detectando processos zombie&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;ps aux | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$8 ~ /Z/ {print}'&lt;/span&gt;

&lt;span class="c"&gt;# Ou com contagem&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;ps aux | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$8 ~ /Z/'&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]&lt;br&gt;
&lt;strong&gt;Implicação prática&lt;/strong&gt;: Se sua aplicação cria processos filhos (via &lt;code&gt;subprocess&lt;/code&gt; em Python, &lt;code&gt;Process.Start&lt;/code&gt; em .NET, ou &lt;code&gt;os/exec&lt;/code&gt; em Go) e não faz &lt;code&gt;wait()&lt;/code&gt; adequadamente, você acumulará zombies. Em escala, isso pode esgotar a tabela de processos do sistema (&lt;code&gt;kernel.pid_max&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Visualizando Processos na Prática
&lt;/h3&gt;

&lt;p&gt;Para entender o estado dos processos em um sistema de produção, o kernel expõe informações detalhadas via &lt;code&gt;/proc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Informações básicas do processo&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&amp;lt;pid&amp;gt;/status
Name:   python3
State:  S &lt;span class="o"&gt;(&lt;/span&gt;sleeping&lt;span class="o"&gt;)&lt;/span&gt;
Tgid:   1350
Pid:    1350
PPid:   1200
Threads: 4
VmPeak: 285432 kB
VmRSS:  61440 kB
voluntary_ctxt_switches:    15230
nonvoluntary_ctxt_switches: 892

&lt;span class="c"&gt;# Mapeamento de memória&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&amp;lt;pid&amp;gt;/maps | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
00400000-00452000 r-xp 00000000 08:01 131074  /usr/bin/python3
00652000-00653000 r--p 00052000 08:01 131074  /usr/bin/python3
00653000-00654000 rw-p 00053000 08:01 131074  /usr/bin/python3
7f8a00000000-7f8a00021000 rw-p 00000000 00:00 0
7f8a04000000-7f8a04001000 rw-p 00000000 00:00 0

&lt;span class="c"&gt;# Informações de escalonamento&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&amp;lt;pid&amp;gt;/sched
python3 &lt;span class="o"&gt;(&lt;/span&gt;1350, &lt;span class="c"&gt;#threads: 4)&lt;/span&gt;
&lt;span class="nt"&gt;---&lt;/span&gt;
se.exec_start                      : 1234567890.123456
se.vruntime                        : 987654.321098
se.sum_exec_runtime                : 105678.000000
nr_switches                        : 16122
nr_voluntary_switches              : 15230
nr_involuntary_switches            : 892
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O campo &lt;code&gt;voluntary_ctxt_switches&lt;/code&gt; vs &lt;code&gt;nonvoluntary_ctxt_switches&lt;/code&gt; é revelador:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Voluntary&lt;/strong&gt;: o processo cedeu a CPU voluntariamente (geralmente por I/O). Alto para servidores I/O-bound — normal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Involuntary&lt;/strong&gt;: o kernel forçou a preempção (o processo esgotou seu timeslice). Alto para processos CPU-bound — pode indicar contenção de CPU.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Teoria de Escalonamento
&lt;/h2&gt;

&lt;p&gt;O escalonador (scheduler) é o componente do kernel que responde a uma pergunta aparentemente simples: &lt;strong&gt;qual processo deve executar agora?&lt;/strong&gt; A resposta, no entanto, envolve trade-offs complexos que impactam diretamente a latência das suas APIs, o throughput dos seus workers e a responsividade dos seus serviços.&lt;/p&gt;

&lt;h3&gt;
  
  
  Por que escalonamento importa para backend?
&lt;/h3&gt;

&lt;p&gt;Considere um servidor com 8 cores rodando:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;16 workers Gunicorn servindo uma API REST&lt;/li&gt;
&lt;li&gt;4 instâncias de Celery processando tarefas em background&lt;/li&gt;
&lt;li&gt;1 processo Redis&lt;/li&gt;
&lt;li&gt;1 processo PostgreSQL com múltiplas conexões&lt;/li&gt;
&lt;li&gt;Dezenas de processos auxiliares do sistema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;São potencialmente centenas de threads competindo por 8 cores. O escalonador precisa decidir, milhares de vezes por segundo, qual thread executa em qual core. Decisões ruins resultam em latência alta, tail latency imprevisível e throughput degradado.&lt;/p&gt;

&lt;h3&gt;
  
  
  Objetivos do Escalonamento
&lt;/h3&gt;

&lt;p&gt;Todo algoritmo de escalonamento busca otimizar um conjunto de métricas que, frequentemente, são conflitantes entre si:&lt;/p&gt;

&lt;h4&gt;
  
  
  Métricas fundamentais
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Métrica&lt;/th&gt;
&lt;th&gt;Definição&lt;/th&gt;
&lt;th&gt;Relevância para Backend&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fairness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Distribuição justa de CPU entre processos&lt;/td&gt;
&lt;td&gt;Evita que um worker monopolize CPU enquanto outros ficam parados&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Efficiency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manter a CPU ocupada (minimizar idle time)&lt;/td&gt;
&lt;td&gt;Maximizar utilização dos cores pagos na cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Turnaround time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tempo total desde submissão até conclusão&lt;/td&gt;
&lt;td&gt;Tempo total para processar um batch job ou ETL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Waiting time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tempo que o processo passa na ready queue&lt;/td&gt;
&lt;td&gt;Contribui diretamente para a latência da sua API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Response time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tempo até a primeira resposta&lt;/td&gt;
&lt;td&gt;Crítico para APIs interativas — o usuário percebe esse delay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throughput&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Processos completados por unidade de tempo&lt;/td&gt;
&lt;td&gt;Requests/segundo que seu servidor consegue atender&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  O conflito fundamental
&lt;/h4&gt;

&lt;p&gt;Essas métricas frequentemente se opõem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Throughput vs Response time&lt;/strong&gt;: Maximizar throughput favorece processos CPU-bound com timeslices longos (menos overhead de context switch). Minimizar response time favorece timeslices curtos e preempção frequente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fairness vs Efficiency&lt;/strong&gt;: Garantir fairness perfeita exige context switches frequentes, que desperdiçam ciclos de CPU com overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch vs Interactive&lt;/strong&gt;: Jobs de processamento em lote (ETL, relatórios) se beneficiam de execução contínua. Serviços interativos (APIs) precisam de resposta rápida.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trade-off: Timeslice Size

Timeslice curto (1ms)              Timeslice longo (100ms)
├─ + Melhor response time          ├─ + Maior throughput
├─ + Mais justo                    ├─ + Menos overhead de context switch
├─ - Muito overhead de switching   ├─ - Response time pior
└─ - Menor throughput              └─ - Menos justo (monopolização)

           Sistemas interativos ◄──────────► Batch systems
           (APIs, web servers)                (ETL, ML training)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Implicação prática&lt;/strong&gt;: Quando você configura o número de workers do Gunicorn ou o tamanho do thread pool do ASP.NET Core, está indiretamente influenciando como o escalonador distribui CPU entre suas threads. Mais workers do que cores disponíveis significa mais competição e mais context switches.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://dev.to/lexgalante/kernel-linux-para-desenvolvedores-backend-processos-threads-parte-ii-54fj"&gt;&lt;strong&gt;Continua na parte II&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Conteudo parcialmente gerado com auxilio de IA generatica (me ajudou organizar tudo isso kkkk)&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencias Bibliográficas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Desenvolvimento-do-Kernel-Linux/dp/8573933410" rel="noopener noreferrer"&gt;Desenvolvimento Do Kernel Do Linux - David Cram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Linux-Bible-Christopher-Negus/dp/1394317468/ref=sr_1_1?__mk_pt_BR=%C3%85M%C3%85%C5%BD%C3%95%C3%91&amp;amp;dib=eyJ2IjoiMSJ9.ep2zHSLrXTmnOmqryZZJPcwOnbqsPqlHDKyK_8FK75E7IdfT3OQ4iSeLNg4aDkEbas_KyjlckRv_HAqF0-rXbwY0A7IAJnyqEquSkUVLVco_qSolsvkdEK8LeRJ7GQcp8e8AIbQoxZMwHdkqzqy0WHcbLqaF3pcBaRdo4HBaO_m9ZJTLKY9TXza9uJCvonORaFc81XM-Gp76W7qwYVmuo33vr9HQHPeeyrrK2rw_dPY.CC-nHGj2vuWkT6U5wHlf4BLCa2H5hJDXH_Xg72Hue10&amp;amp;dib_tag=se&amp;amp;keywords=linux+a+biblia&amp;amp;qid=1780931929&amp;amp;s=books&amp;amp;sprefix=linux+a+bi%2Cstripbooks%2C1135&amp;amp;sr=1-1&amp;amp;ufe=app_do%3Aamzn1.fos.fcd6d665-32ba-4479-9f21-b774e276a678" rel="noopener noreferrer"&gt;Linux Bible - Chsristopher Negus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.ibm.com/articles/l-linux-kernel/" rel="noopener noreferrer"&gt;Linux Kernel: An Introduction&lt;/a&gt; - IBM Developer&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.br/Sistemas-Operacionais-Modernos-Andrew-Tanenbaum/dp/8582606168/ref=sr_1_1?__mk_pt_BR=%C3%85M%C3%85%C5%BD%C3%95%C3%91&amp;amp;dib=eyJ2IjoiMSJ9.GT4sX07Q-JQVNuedOvqQ5ZO7y1vPyznY4qtp_jih_s6jnDsrFJut_q6oT6io7p-I4c2hke9cKBU-DXK1GrwEjyvNZQbXAMjxsM1C6oDQqUybKWMEHkoJo3VQvzLYVU4XXCGkjDiNVI_fYu7spu33HDSpcBcZ891_HBZu4218XEvpnWNCWv6D5pM2XF0qZnFJeNTYoTSbSf6aldeB0RoH1cQ62o63NXV8a8HNh9qdNJs.anlokyxWbWuareiNAhSAhsoxohIr4FfNT9TcagLW980&amp;amp;dib_tag=se&amp;amp;keywords=Sistemas+Operacionais&amp;amp;qid=1780931998&amp;amp;s=books&amp;amp;sr=1-1&amp;amp;ufe=app_do%3Aamzn1.fos.fcd6d665-32ba-4479-9f21-b774e276a678" rel="noopener noreferrer"&gt;Sistemas Operacionais Modernos - Tanenbaum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>backend</category>
      <category>kernel</category>
      <category>linux</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
