<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://seu-usuario.github.io/ariablog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://seu-usuario.github.io/ariablog/" rel="alternate" type="text/html" /><updated>2026-08-02T23:24:25+00:00</updated><id>https://seu-usuario.github.io/ariablog/feed.xml</id><title type="html">Aria Diniz</title><subtitle>Falo sobre Computação no geral, com foco em low-level. Não posto com frequência, mas tento trazer algo interessante quando o faço.</subtitle><entry><title type="html">Scheduler: o software mais importante do seu kernel</title><link href="https://seu-usuario.github.io/ariablog/so/kernel/scheduler/lowlevel/2026/08/02/kernel-scheduler.html" rel="alternate" type="text/html" title="Scheduler: o software mais importante do seu kernel" /><published>2026-08-02T22:50:00+00:00</published><updated>2026-08-02T22:50:00+00:00</updated><id>https://seu-usuario.github.io/ariablog/so/kernel/scheduler/lowlevel/2026/08/02/kernel-scheduler</id><content type="html" xml:base="https://seu-usuario.github.io/ariablog/so/kernel/scheduler/lowlevel/2026/08/02/kernel-scheduler.html"><![CDATA[<p>Antes de começarmos, como é a primeira vez que falo sobre Kernel aqui, é importante explicar o que é um Kernel. O kernel é, em poucas palavras, o software que realiza a comunicação entre o hardware e o sistema operacional (SO), e é ele quem nos fornece uma camada de abstração para a comunicação com o hardware. Imagine o seguinte: você escreveu um programa simples na sua linguagem de programação favorita, e a única coisa que este programa faz é imprimir na tela “Hello World!”. Na maior parte das linguagens de programação isso gastaria algumas poucas linhas de código, o que para nós seres humanos, tem uma carga cognitiva minúscula. Mas o que está acontecendo por baixo dos panos é bem mais complexo do que isso, e é justamente o Kernel quem abstrai toda essa complexidade. Para você imprimir uma simples frase na tela, várias coisas precisam acontecer. Além da etapa de compilação que já falamos um pouco no último artigo, quando seu programa é executado, é criado um processo para ele. Através desse processo, ele entra em uma fila para ser executado (exatamente aqui que entra o scheduler, ele é o responsável por gerenciar o uso de CPU pelos processos abertos). Quando ele é escolhido para a execução, o endereço de memória da primeira instrução do seu programa é gravado no Program Counter do seu processador, a CPU executa a instrução neste endereço de memória e segue executando as instruções do seu programa até chegar na instrução para imprimir a mensagem na tela. Considerando que no seu programa você fez algo como <code class="language-plaintext highlighter-rouge">printf("Hello World!")</code>, o texto da frase ficou gravado no binário do programa na seção .rodata. Então para imprimir a CPU lê estes dados,  envia para o barramento de dados para ser lido pela sua GPU, escreve isso em um framebuffer alocado na VRAM, o monitor lê esse framebuffer e finalmente desenha isso na tela.</p>

<p>Se isso pareceu um pouco mais complicado, é porque ainda sim eu abstrai do texto as particularidades do hardware. Como vimos no último artigo, todo hardware tem uma <code class="language-plaintext highlighter-rouge">datasheet</code>, que é um documento técnico que descreve os circuitos internos, com informações de cada componente como tensão elétrica, indutância, capacitância, resistência elétrica etc. Além disso, a datasheet nos informa também quais os terminais do hardware recebem e enviam quais tipos de informação. E pensando em kernel e firmwares no geral, o mais importante, nos informa <strong>quais endereços de memória devem ser usados para cada ação.</strong> Não adianta a CPU pegar os dados da frase a ser imprimida na tela, jogar no barramento de dados e seguir a vida. Essa informação deve ser escrita para um endereço específico para que essa ação aconteça corretamente. E isso vale para qualquer hardware e qualquer ação, não só o nosso monitor hipotético. Um “simples” <code class="language-plaintext highlighter-rouge">printf()</code> parece bem mais complexo agora, certo?</p>

<p>Quando falamos que o kernel é o software que faz a ponte entre o hardware e o sistema operacional, muitas vezes a complexidade do que ele faz fica muito implícita, como o que podemos ver nesse exemplo que eu trouxe, e ainda sim nem mesmo arranhamos a superfície desse tópico, que seriam necessários alguns livros inteiros para abordá-lo com profundidade. De qualquer forma, fiz uma introdução bem longa sobre o que é um kernel, e já podemos agora discutir o tema principal, o <strong>Scheduler</strong>.</p>

<p>Como disse, o kernel é responsável pela comunicação entre hardware e SO, e uma das partes mais importantes dele (a mais importante na minha opinião, por motivos que ficarão evidentes ao longo desse artigo), é o scheduler. Mas bem, o que é o scheduler? Para responder essa pergunta vou ilustrar um problema, assim como fiz nos parágrafos anteriores. Como disse antes, toda vez que você executa um software no seu computador, o kernel atribui a este software um process ID (PID). Todos os softwares rodando no espaço de usuário, ou ring 3 (entrarei mais em detalhes sobre arquitetura de rings em artigos futuros), possuem seu próprio PID. Isso independe se estes softwares foram executados por você, automaticamente ao inicializar a máquina ou executados por um terceiro software. E isso é necessário por uma razão simples: uma CPU  antigamente conseguia executar uma única instrução de cada vez, apenas uma por ciclo, e embora CPUs modernas possuam recursos Simultaneous Multi-Threading como o hyper Threading dos processadores Intel, um computador mesmo que em stand-by possui centenas de processos ativos. Quando pensamos nisso, é natural nos perguntarmos algo como: então como é possível ter, no meu computador, diversos softwares sendo executados ao mesmo tempo? É exatamente aqui que o scheduler entra: o scheduler é, em poucas palavras, o software responsável por administrar o uso de CPU por cada processo, gerenciando com latência de microsegundos o uso do tempo de CPU por centenas de processos simultaneamente, garantindo que todos os processos ativos consigam utilizar a CPU de acordo com a prioridade e minimizando o impacto da troca de contextos na performance. Conforme discutido por Arpaci-Dusseau &amp; Arpaci-Dusseau<sup id="fnref:os3" role="doc-noteref"><a href="#fn:os3" class="footnote" rel="footnote">1</a></sup> nos capítulos sobre escalonamento, o custo de um context switch envolve o salvamento e restauração do estado da CPU, incluindo registradores, program counter e TLB, podendo impactar significativamente a performance em sistemas com alta carga.</p>

<p>Ao compreendermos a função do scheduler conseguimos de imediato compreender como um computador consegue executar vários softwares “ao mesmo tempo”. A verdade é que ele está executando apenas um (ou alguns poucos, considerando tecnologias de SMT que citadas anteriormente), porém como o scheduler é capaz de alternar o uso de CPU entre milhares de processos em uma fração de segundos, temos a sensação de que são executados simultaneamente.</p>

<p>Agora que essa questão foi esclarecida, é natural que novas perguntas surjam. A mais importante é: mas <strong>como</strong> o scheduler organiza os processos e decide quem deve e quem não deve utilizar a CPU em um determinado momento?</p>

<p><em>Nota: As primeiras versões do kernel Linux e do Unix utilizavam algoritmos diferentes dos que são utilizados hoje. O scheduler do Kernel linux passou a utilizar o algoritmo conhecido como Completely Fair Scheduler em 2007, que embora tenha uma complexidade temporal de O(log n) de busca de processos na árvore red-black ele consegue dividir proporcionalmente o tempo de uso de CPU entre todos os processos, diferente do algoritmo anterior que, apesar de ser O(1) na busca de tarefas, utilizava uma janela de tempo baseada em prioridades, priorizando tarefas CPU-bound em detrimento das interativas, o que causava problemas de performance. Como o objetivo deste artigo não é discutir a história do scheduler e sim o que ele é e como funciona, focarei apenas no algoritmo utilizado nas versões modernas do kernel.</em><sup id="fnref:cfs" role="doc-noteref"><a href="#fn:cfs" class="footnote" rel="footnote">2</a></sup></p>

<p>Quando tomamos como exemplo o kernel Linux, o algoritmo utilizado atualmente pelo scheduler é conhecido como Completely Fair Scheduler (CFS). Esse algoritmo foca em distribuir o mais igualmente possível o tempo de uso de CPU entre todos os processos, e para isso ele organiza todos os processos em uma árvore Red-Black (Ou rubro negra, como a literatura brasileira prefere usar) baseando-se no Virtual Runtime (vruntime) de cada processo. O vruntime é, de forma simples, a quantidade de ciclos de CPU que um processo utilizou. Nesta árvore, os processos com <strong>menos</strong> vruntime são posicionados mais a esquerda, de maneira com que a árvore seja organizada com os processos que utilizaram menos vruntime para os que utilizaram mais, de maneira crescente, da esquerda para a direita. Quando o scheduler irá delegar o uso de CPU para um processo ele procura na árvore pelo processo com <strong>menos</strong> vruntime, ou seja, o mais a esquerda, e delega o uso de CPU para este processo. Na hora de trocar novamente de contexto ele interrompe este processo e salva seu estado, atualiza o vruntime e rebalanceia a árvore (como o vruntime deste processo agora é maior, ele será registrado na árvore mais a direita do que estava inicialmente) e então o processo é repetido. Um detalhe importante: o vruntime não é a medida de <strong>apenas</strong> uso de CPU, ele é uma medida ponderada que se baseia na prioridade dos processos. Em sistemas Unix e Unix-like (como o Linux) geralmente as prioridades (ou nice level) variam de -20 a 20, onde -20 é de prioridade crítica e 20 é o menos prioritário.</p>

<p align="center"><small>Figura 1: Representação de uma Red-Black tree com processos gerenciados pelo scheduler. Imagem retirada do portal da IBM. Fonte: https://developer.ibm.com/tutorials/l-completely-fair-scheduler/</small></p>
<p><img src="/ariablog/assets/images/cfs.gif" alt="cfsimg" style="display: block; margin: 0 auto;" /></p>

<p>Como uma cientista, o que mais me encanta na vida é que quando respondo uma pergunta, geralmente várias outras surgem. A primeira vez que estudei sobre o scheduler e entendi como ele gerenciava os processos, a primeira coisa que me veio a cabeça foi: mas como o algoritmo identifica o que está pronto para consumir CPU? Vamos tomar como exemplo um socket TCP. Imagina que você escreveu um programa que cria um socket TCP escutando na porta 3000, aguarda uma conexão e quando é realizada uma conexão ele escreve uma mensagem para o client, fecha o file descriptor da conexão e encerra a execução. Enquanto o seu software está aguardando uma conexão ele não está consumindo CPU. Na verdade ele não está fazendo nada, e delegar uso de CPU para ele nem mesmo faria sentido. Para lidar com estes casos, o scheduler possui uma tabela de status para os processos. Embora os nomes dos status sejam diferentes em cada kernel a funcionalidade é bem semelhante, então discutiremos aqui a lista de status do scheduler do kernel Linux:</p>

<table>
  <tbody>
    <tr>
      <td>TASK_RUNNING</td>
      <td>O processo está na runqueue (árvore do CFS) e apto a ser escalonado.</td>
    </tr>
    <tr>
      <td>TASK_INTERRUPTIBLE</td>
      <td>Processo bloqueado (ex: aguardando I/O, socket, read() no disco). Ele é removido da runqueue e colocado em uma wait queue específica. Só acorda com um sinal ou quando o recurso fica disponível.</td>
    </tr>
    <tr>
      <td>TASK_UNINTERRUPTIBLE (D-state)</td>
      <td>A versão “à prova de sinais” do bloqueio. Geralmente usada quando o processo espera I/O de disco diretamente. Um processo nesse estado não pode ser morto nem mesmo com um sinal da tabela de vetores de interrupção, como por exemplo um SIGKILL.</td>
    </tr>
    <tr>
      <td>TASK_STOPPED / TASK_TRACED</td>
      <td>Pausado (ex: pelo debugger gdb).</td>
    </tr>
    <tr>
      <td>EXIT_ZOMBIE</td>
      <td>O processo já terminou, mas o pai ainda não coletou o status (wait()).</td>
    </tr>
  </tbody>
</table>

<p align="center"><small>Tabela 1: Status possíveis de um processo no scheduler.</small></p>

<p>Então no nosso exemplo do socket, enquanto o software estivesse aguardando uma conexão ele ficaria com o status <code class="language-plaintext highlighter-rouge">TASK_INTERRUPTIBLE</code>, e portanto fora da runqueue do scheduler. A parte mais interessante é que, enquanto na wait queue, este software <strong>não</strong> acumularia vruntime. Então ao receber uma conexão e voltar para <code class="language-plaintext highlighter-rouge">TASK_RUNNING</code>, como seu vruntime ficou congelado, ele estaria bem a esquerda na árvore, e portanto teria uma prioridade maior para uso de CPU.</p>

<p>Obrigada por lerem ate aqui! Neste artigo foi introduzido o CFS, mas ele é apenas uma das classes do scheduler, a <code class="language-plaintext highlighter-rouge">SCHED_NORMAL</code>. Existem outras classes como <code class="language-plaintext highlighter-rouge">SCHED_FIFO</code> e <code class="language-plaintext highlighter-rouge">SCHED_IDLE</code>, que serão abordadas em artigos futuros.</p>

<h1 id="bibliografia">Bibliografia</h1>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:os3" role="doc-endnote">
      <p>ARPACI-DUSSEAU, Remzi H.; ARPACI-DUSSEAU, Andrea C. Operating Systems: three easy pieces. [S.l.]: Arpaci-Dusseau Books, 2018. 676 p. ISBN 9781985086593. <a href="#fnref:os3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:cfs" role="doc-endnote">
      <p>Linux: The Completely Fair Scheduler. KernelTrap. 2007-04-19. Disponível em: https://web.archive.org/web/20070416040156/http://kerneltrap.org/. Acesso em: 2 aug 2026. <a href="#fnref:cfs" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="so" /><category term="kernel" /><category term="scheduler" /><category term="lowlevel" /><summary type="html"><![CDATA[Antes de começarmos, como é a primeira vez que falo sobre Kernel aqui, é importante explicar o que é um Kernel. O kernel é, em poucas palavras, o software que realiza a comunicação entre o hardware e o sistema operacional (SO), e é ele quem nos fornece uma camada de abstração para a comunicação com o hardware. Imagine o seguinte: você escreveu um programa simples na sua linguagem de programação favorita, e a única coisa que este programa faz é imprimir na tela “Hello World!”. Na maior parte das linguagens de programação isso gastaria algumas poucas linhas de código, o que para nós seres humanos, tem uma carga cognitiva minúscula. Mas o que está acontecendo por baixo dos panos é bem mais complexo do que isso, e é justamente o Kernel quem abstrai toda essa complexidade. Para você imprimir uma simples frase na tela, várias coisas precisam acontecer. Além da etapa de compilação que já falamos um pouco no último artigo, quando seu programa é executado, é criado um processo para ele. Através desse processo, ele entra em uma fila para ser executado (exatamente aqui que entra o scheduler, ele é o responsável por gerenciar o uso de CPU pelos processos abertos). Quando ele é escolhido para a execução, o endereço de memória da primeira instrução do seu programa é gravado no Program Counter do seu processador, a CPU executa a instrução neste endereço de memória e segue executando as instruções do seu programa até chegar na instrução para imprimir a mensagem na tela. Considerando que no seu programa você fez algo como printf("Hello World!"), o texto da frase ficou gravado no binário do programa na seção .rodata. Então para imprimir a CPU lê estes dados, envia para o barramento de dados para ser lido pela sua GPU, escreve isso em um framebuffer alocado na VRAM, o monitor lê esse framebuffer e finalmente desenha isso na tela.]]></summary></entry><entry><title type="html">Compiladores: o que é um linker e como funciona</title><link href="https://seu-usuario.github.io/ariablog/compiladores/linker/baremetal/2026/07/07/compiladores-o-que-e-linker.html" rel="alternate" type="text/html" title="Compiladores: o que é um linker e como funciona" /><published>2026-07-07T19:00:00+00:00</published><updated>2026-07-07T19:00:00+00:00</updated><id>https://seu-usuario.github.io/ariablog/compiladores/linker/baremetal/2026/07/07/compiladores-o-que-e-linker</id><content type="html" xml:base="https://seu-usuario.github.io/ariablog/compiladores/linker/baremetal/2026/07/07/compiladores-o-que-e-linker.html"><![CDATA[<p>Nunca escrevi um blog antes, e para o primeiro post do meu primeiro blog, estou meio perdida em como começar, mas vamos lá. Estou escrevendo este artigo na noite de uma terça feira, enquanto escuto a discografia do Pink Floyd pela <strong>INTEGER_OVERFLOW</strong> vez. Para quem não me conhece, sou a Aria, costumo postar no LinkedIn várias coisas sobre computação e matemática, com bastante foco em desenvolvimento de software low-level. Há muito tempo prometo começar com um blog, e finalmente estou cumprindo essa promessa. E como assunto do primeiro post, quero falar algo que sempre toquei no assunto mas nunca me aprofundei, graças ao limite de caracteres do LinkedIn que não me permite: <strong>Linkers</strong>.</p>

<p>Quem me acompanha já me viu falando sobre compiladores diversas vezes, um exemplo é <a href="https://www.linkedin.com/feed/update/urn:li:activity:7425672799689756673">neste post aqui</a> em que faço algumas considerações sobre o compilador de C escrito em Rust da Anthropic criado puramente com agentes. Futuramente quero cobrir todas as etapas do processo de compilação aqui no blog, uma em cada post dedicado, mas hoje quero dedicar minha escrita ao Linker. Para relembrar, o processo de compilação de código em linguagem alto nível para linguagem de máquina contece em quatro etapas<sup id="fnref:dragonbook" role="doc-noteref"><a href="#fn:dragonbook" class="footnote" rel="footnote">1</a></sup>:</p>

<ol>
  <li>Preprocessamento: Aqui são removidos comentários, feitas substituições de strings etc</li>
  <li>Tradução: O código fonte é convertido em instruções de CPU (Várias otimizações ocorrem aqui)</li>
  <li>Assembly: Tradução das instruções de CPU para binário</li>
  <li>Linker: Organização do executável final</li>
</ol>

<p>Sim, eu sei que a etapa de Link é a última, mas quero começar o blog com ela por duas razões. A primeira é que, de todas elas, é a que menos falei até hoje, mesmo sendo a mais interessante de todas (pra mim!). A segunda é que, quando você lê que o linker organiza o binário, dá a entender que ele apenas troca coisas de lugar ou algo do tipo, sendo que o linker é, na verdade, o responsável por definir, por exemplo, o endereço de memória onde seu software será executado, os limites da stack etc<sup id="fnref:dragonbook:1" role="doc-noteref"><a href="#fn:dragonbook" class="footnote" rel="footnote">1</a></sup>.</p>

<p>Pois é, imagino que pra quem nunca tinha ouvido falar antes, agora deve ter uma noção melhor da importância do linker.</p>

<p>O linker é a etapa da compilação responsável por pegar todos os compilados intermediários (no caso de C os arquivos .o ou .a), e transformar em um único executável. Além disso ele também resolve toda a symbol table do seu software e as dependências. Se você tem declaradas as variáveis <code class="language-plaintext highlighter-rouge">foo</code> e <code class="language-plaintext highlighter-rouge">bar</code>, é ele quem decide o endereço final absoluto de memória onde essas variáveis ficarão localizadas. Se você importou bibliotecas externas, o linker é o responsável por alocar também a memória dos recursos utilizados por elas, endereço de memória das funções, tudo.</p>

<p>Normalmente, quando estamos escrevendo software que vai rodar em cima de um sistema operacional, é extremamente raro precisarmos escrever o script do linker, independente de estarmos trabalhando em C, Rust, Go etc. O compilador já faz esse trabalho por nós (e muito melhor do que uma pessoa conseguiria). Então, para este post aqui, resolvi ir um pouco abaixo do low-level tradicional que costumo postar e ir direto para o bare-metal. Escolhi um STM32, mais especificamente o STM32F103C8T6 também conhecido como blue-pill, pra criar esse exemplo. Escolhi ele porque é a placa que costumo utilizar nos meus projetos pessoais, e portanto a que tenho disponível aqui. Essa placa possui um processador ARM Cortex M3, e vai servir muito bem para mostrar como funciona o linker. Como estarei criando um driver simples do zero, utilizando apenas a datasheet da placa para mapear a memória na mão e manipular manualmente os registradores, o script do linker é bem mais curto e simples do que um script gerado por compilador, que está referenciando várias funções da stdlib, syscalls para o Kernel etc.</p>

<p>Não deixarei aqui neste post tudo que fiz para compilar a mão e gravar o código na memória Flash da placa, mas quem tiver interesse me sinalize que posso pensar em fazer um post dedicado a isso no futuro. Existem diversas IDEs que são super simples e prontas para programar essas placas, quase um plug-and-play, mas não é isso que vou fazer aqui. Pode não ser muito prático, mas criar seu próprio código manipulando o hardware, sem IDEs nem nada, utilizando apenas a datasheet fornecida pelo fabricante é uma das coisas mais recompensadoras na computação! Para quem tiver interesse, aqui está a <a href="https://www.google.com/url?sa=t&amp;source=web&amp;rct=j&amp;opi=89978449&amp;url=https://www.st.com/resource/en/datasheet/stm32f103c8.pdf&amp;ved=2ahUKEwjqwKDLzcGVAxVUGbkGHSyGChsQFnoECA8QAQ&amp;usg=AOvVaw0rd6I_7fuhTLdZOoycvGV5">datasheet</a> desta placa específica, que utilizei para criar o código C e o script do Linker.</p>

<p>Retomando o tema do linker, imaginem o seguinte. Acabamos de colocar as mãos nessa plaquinha bonitinha e queremos testar. Qual a coisa mais simples que podemos fazer? Essa placa possui um LED embutido, que a datasheet nos informa ser o pino número 13. Podemos então, para fins de teste, tentar fazer este pino piscar.</p>

<p>Em tese é muito simples, mas como estamos fazendo tudo do zero, sem IDEs, libs externas nem nada, temos um problema. O STM32 não roda em um OS, então precisaremos criar uma Interrupt Vector Table (IVT) na mão. A IVT é uma tabela de ponteiros que mapeia sinais do hardware a funções de software. Um exemplo é quando você aperta <code class="language-plaintext highlighter-rouge">Ctrl + C</code> no seu terminal. Na IVT do seu Kernel está registrado que quando esse sinal do hardware for detectado, um determinado endereço de memória deve ser chamado. Nesse endereço está a função que interrompe a execução do seu programa.</p>

<p>Deixarei aqui um exemplo da IVT que codei para este nosso driver. Como não é o foco deste post não entrarei em detalhes, mas como disse antes, se for do interesse de vocês, posso fazer um post apenas sobre bare-metal no futuro:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">main</span><span class="p">();</span>
<span class="kt">void</span> <span class="nf">Reset_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">HardFault_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="n">__attribute__</span><span class="p">((</span><span class="n">weak</span><span class="p">));</span>

<span class="n">__attribute__</span><span class="p">((</span><span class="n">section</span><span class="p">(</span><span class="s">".isr_vector"</span><span class="p">)))</span>
<span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="k">const</span> <span class="n">g_pfnVectors</span><span class="p">[])(</span><span class="kt">void</span><span class="p">)</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">(</span><span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="p">)(</span><span class="kt">void</span><span class="p">))</span><span class="mh">0x20005000</span><span class="p">,</span>
    <span class="n">Reset_Handler</span><span class="p">,</span>
    <span class="n">HardFault_Handler</span><span class="p">,</span>
<span class="p">};</span>

<span class="kt">void</span> <span class="nf">Reset_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">extern</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">_sdata</span><span class="p">,</span> <span class="n">_edata</span><span class="p">,</span> <span class="n">_la_data</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="o">*</span><span class="n">pSrc</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">_la_data</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="o">*</span><span class="n">pDest</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">_sdata</span><span class="p">;</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">pDest</span> <span class="o">&lt;</span> <span class="o">&amp;</span><span class="n">_edata</span><span class="p">)</span> <span class="o">*</span><span class="n">pDest</span><span class="o">++</span> <span class="o">=</span> <span class="o">*</span><span class="n">pSrc</span><span class="o">++</span><span class="p">;</span>

    <span class="k">extern</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">_sbss</span><span class="p">,</span> <span class="n">_ebss</span><span class="p">;</span>
    <span class="n">pDest</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">_sbss</span><span class="p">;</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">pDest</span> <span class="o">&lt;</span> <span class="o">&amp;</span><span class="n">_ebss</span><span class="p">)</span> <span class="o">*</span><span class="n">pDest</span><span class="o">++</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="n">main</span><span class="p">();</span>
    <span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">HardFault_Handler</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span> <span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span> <span class="p">}</span>
</code></pre></div></div>

<p>E aqui deixarei o código para piscar o LED:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define PERIPH_BASE     0x40000000
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE  (PERIPH_BASE + 0x20000)
</span>
<span class="cp">#define RCC_BASE        (AHBPERIPH_BASE + 0x1000)
#define GPIOC_BASE      (APB2PERIPH_BASE + 0x1000)
</span>
<span class="cp">#define RCC_APB2ENR     (*((volatile unsigned int*)(RCC_BASE + 0x18)))
#define GPIOC_CRH       (*((volatile unsigned int*)(GPIOC_BASE + 0x04)))
#define GPIOC_ODR       (*((volatile unsigned int*)(GPIOC_BASE + 0x0C)))
</span>
<span class="kt">void</span> <span class="nf">delay</span><span class="p">(</span><span class="k">volatile</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">count</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">while</span><span class="p">(</span><span class="n">count</span><span class="o">--</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">RCC_APB2ENR</span> <span class="o">|=</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">4</span><span class="p">);</span>

    <span class="n">GPIOC_CRH</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="p">(</span><span class="mh">0xF</span> <span class="o">&lt;&lt;</span> <span class="mi">20</span><span class="p">);</span>
    <span class="n">GPIOC_CRH</span> <span class="o">|=</span> <span class="p">(</span><span class="mh">0x3</span> <span class="o">&lt;&lt;</span> <span class="mi">20</span><span class="p">);</span>

    <span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">GPIOC_ODR</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">13</span><span class="p">);</span>
        <span class="n">delay</span><span class="p">(</span><span class="mi">500000</span><span class="p">);</span>
        <span class="n">GPIOC_ODR</span> <span class="o">|=</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">13</span><span class="p">);</span>
        <span class="n">delay</span><span class="p">(</span><span class="mi">500000</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">SystemInit</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>  <span class="p">}</span>
</code></pre></div></div>

<p>Para não deixar tudo sem explicação: Como informado na documetação da placa, a memória RAM começa a partir do endereço <code class="language-plaintext highlighter-rouge">0x20000000</code>, e por isso declarei o início da Stack bem próximo. A datasheet também nos informa que a IVT deve ficar no endereço de memória <code class="language-plaintext highlighter-rouge">0x08000000</code>, que também é o início da memória flash, e temos também as informações dos pinos e registradores, que utilizei para escrever os endereços de memória corretos.</p>

<p>Ok, temos nosso código, hora de escrever nosso linker!</p>

<pre><code class="language-ld">MEMORY {
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 64K
    RAM (rwx)   : ORIGIN = 0x20000000, LENGTH = 20K
}
ENTRY(Reset_Handler)

SECTIONS {
    .isr_vector : { KEEP(*(.isr_vector)) } &gt; FLASH
    .text : { *(.text) *(.text*) } &gt; FLASH
    .rodata : { *(.rodata) *(.rodata*) } &gt; FLASH
    _la_data = LOADADDR(.data);
    .data : {
        _sdata = .;
        *(.data) *(.data*);
        _edata = .;
    } &gt; RAM AT&gt; FLASH
    .bss : {
        _sbss = .;
        *(.bss) *(.bss*) *(COMMON);
        _ebss = .;
    } &gt; RAM
}
</code></pre>

<p>Essa aqui é, na minha opinião, a parte mais linda da etapa de compilação. Vamos ver o que está acontecendo:</p>

<ul>
  <li>Campo MEMORY
    <ul>
      <li>Alí definimos duas variáveis: FLASH e RAM. Memória flash é aquela usada como “disco”, é onde ficará registrado o binário compilado do nosso software. Dizemos no linker que essa memória iniciará no endereço <code class="language-plaintext highlighter-rouge">0x08000000</code>, e que ela tem tamanho de 64Kb. Ambas as informações nos foram fornecidas pela datasheet. Reparem que alí definimos a Flash como read only e a RAM como read and write.</li>
      <li>Na variável RAM seguimos o mesmo padrão, utilizamos as informações da datasheet para mapear seu tamanho e onde começa.</li>
    </ul>
  </li>
  <li>Campo ENTRY
    <ul>
      <li>Aqui estamo dizendo para o linker o seguinte: Assim que o hardware bootar, o primeiro código executado é essa função. O hardware do Cortex-M3 automaticamente procura pelo endereço do <code class="language-plaintext highlighter-rouge">Reset_Handler</code> dentro da nossa IVT (lembra dela?) para saber por onde começar a rodar nosso programa.</li>
    </ul>
  </li>
  <li>Campo SECTIONS
    <ul>
      <li>É exatamente aqui que mapeamos onde tudo vai ficar. Deixamos claro no linker, assim como manda a datasheet, que a IVT ficará no primeiro endereço da memória Flash</li>
      <li>Depois vem a sessão <code class="language-plaintext highlighter-rouge">.text</code>. Na linguagem do linker, <code class="language-plaintext highlighter-rouge">.text</code> é a sessão onde fica todo o código compilado do nosso software, também armazenado na memória Flash.</li>
      <li><code class="language-plaintext highlighter-rouge">.rodata</code> é uma abreviação de Read Only data. Todas as constantes do nosso software ficarão armazenadas neste segmento de memória.</li>
      <li><code class="language-plaintext highlighter-rouge">.data</code> aqui já é a nossa primeira sessão de memória alocada na RAM. Todas as variáveis inicializadas vivem nesta sessão. Mas detalhe: essas variáveis começam a vida gravadas na Flash (para não serem perdidas quando a placa desligar). É o nosso <code class="language-plaintext highlighter-rouge">Reset_Handler</code> que as copia para a RAM (usando os símbolos _sdata e _edata criados aqui no linker) logo antes do main() rodar.</li>
      <li><code class="language-plaintext highlighter-rouge">.bss</code> esse bloco é bem interessante. Aqui ficam todas as variáveis globais e variáveis estáticas não inicializadas. Elas são armazenadas em um único bloco contínuo de RAM</li>
    </ul>
  </li>
</ul>

<p>Nesse script, estamos basicamente dizendo ao compilador onde tudo deve ser posicionado, para que o software consiga se comunicar perfeitamente com o hardware. O linker é quem faz o software entender o hardware.</p>

<p>Deixei aqui uma foto da placa funcionando. A placa preta é um conversor de USB para UART, que precisei utilizar para escrever direto da porta USB do meu Mac para a STM. Espero que tenham gostado, e que tenha sido possível aprender um pouco mais sobre como os softwares são compilados!</p>

<p><img src="/ariablog/assets/images/linker_baremetal_example.jpeg" alt="imagem stm" /></p>

<h2 id="bibliografia">Bibliografia</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:dragonbook" role="doc-endnote">
      <p>AHO, A. V.; LAM, M. S.; SETHI, R.; ULLMAN, J. D. Compilers: principles, techniques, and tools. 2. ed. Boston: Pearson Addison-Wesley, 2006. p. 3. <a href="#fnref:dragonbook" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:dragonbook:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="compiladores" /><category term="linker" /><category term="baremetal" /><summary type="html"><![CDATA[Nunca escrevi um blog antes, e para o primeiro post do meu primeiro blog, estou meio perdida em como começar, mas vamos lá. Estou escrevendo este artigo na noite de uma terça feira, enquanto escuto a discografia do Pink Floyd pela INTEGER_OVERFLOW vez. Para quem não me conhece, sou a Aria, costumo postar no LinkedIn várias coisas sobre computação e matemática, com bastante foco em desenvolvimento de software low-level. Há muito tempo prometo começar com um blog, e finalmente estou cumprindo essa promessa. E como assunto do primeiro post, quero falar algo que sempre toquei no assunto mas nunca me aprofundei, graças ao limite de caracteres do LinkedIn que não me permite: Linkers.]]></summary></entry></feed>