14 pontos por GN⁺ 2025-05-06 | Ainda não há comentários. | Compartilhar no WhatsApp
  • Apresenta um método para usar funções padrão de C, incluindo printf, mesmo sem sistema operacional, com a biblioteca Newlib
  • Em um ambiente Bare Metal baseado na arquitetura RISC-V, implementa diretamente drivers de UART e funções de alocação de memória e os conecta à Newlib
  • Com a implementação de apenas um conjunto mínimo de chamadas de sistema, como _write, _sbrk e _close, já é possível usar recursos avançados como printf
  • Mostra como criar uma toolchain baseada em Newlib, incluindo o toolchain GCC para RISC-V, automação de build e escrita de linker scripts
  • Como resultado, conseguiu montar um ambiente printf com saída via UART, entrada com scanf e até alocação dinâmica de memória funcionando

Abstrações de software e biblioteca padrão de C

  • Em um SO comum, ao chamar printf, entram em ação várias camadas de abstração, como syscalls do kernel, camada de terminal e renderização de fontes
  • Em um ambiente Bare Metal, é necessário controlar diretamente a entrada e saída sem sistema operacional, o que exige implementar os drivers manualmente
  • A Newlib oferece uma configuração extensível, implementando apenas o mínimo necessário em vez de toda a biblioteca padrão de C

Conceito da Newlib

  • Internamente, printf é implementado com base em funções primitivas simples, como _write
  • Na Newlib, todas as funções começam definidas como stubs, e basta implementar só o que for necessário para usar o restante com os comportamentos padrão
  • Se o desenvolvedor implementar apenas as funções de que precisa, pode usar os recursos da biblioteca C com flexibilidade

Toolchain de compilação cruzada

  • Para fazer cross-compilation de x86_64/Linux → RISC-V, é necessário compilar diretamente a partir do código-fonte do GCC
  • Ao montar uma toolchain com a Newlib como biblioteca C padrão, torna-se possível gerar binários para RISC-V

Detalhes da toolchain

  • Na build da toolchain, são usados os parâmetros --prefix, --enable-multilib, --disable-gdb e --with-cmodel=medany
  • medany é uma configuração do RISC-V que permite acessar regiões de memória em endereços altos
  • Após a compilação, o cross-compiler e as bibliotecas da Newlib podem ser usados a partir de /opt/riscv-newlib

Implementando os blocos de memória e UART

  • A transmissão e recepção de caracteres são implementadas acessando diretamente o endereço de hardware da UART 16550A no ambiente QEMU
  • Funções substitutas de syscalls como _write, _sbrk e _close são implementadas para integração com a Newlib
  • _sbrk funciona expandindo a memória heap do ponto _end até _stack_bottom

Exemplo de aplicação: entrada e saída

  • Na função main, é possível usar printf e scanf, e os valores de entrada também são processados corretamente
  • Embora echo não seja suportado, ainda é possível receber strings com scanf e exibi-las na saída
  • Um runtime separado inicializa a stack, faz o zero-fill da seção BSS e depois chama main

Linker script

  • O endereço inicial de execução é 0x80000000, e o código de runtime é colocado nessa posição
  • A memória é organizada na ordem .text, .rodata, .data e .bss, com a heap definida de _end até antes da stack
  • A stack tem tamanho fixo de 64 KB, e o endereço do topo é 0x80000000 + 64MB
  • A instrução ASSERT é usada para evitar colisão entre heap e stack

O momento do “gotcha”

  • Na configuração da toolchain, é preciso usar --with-cmodel=medany para que sejam geradas instruções de máquina capazes de lidar com endereços acima de 0x80000000
  • Se a biblioteca C e o código da aplicação usarem modelos de endereçamento diferentes, ocorre erro de linkedição

Executando a aplicação

  • Com um Makefile, é possível automatizar tanto o cross-compile quanto a execução no QEMU
  • As opções -specs=nosys.specs, -nostartfiles e -T link.ld permitem usar a configuração mínima da Newlib e um runtime personalizado
  • Ao executar make debug, a entrada e a saída via UART funcionam normalmente no console do QEMU
  • Também é possível verificar o trace real das instruções por meio de qemu_debug.log

Conclusão

  • Foi implementada com a Newlib uma estrutura em que printf, scanf e malloc podem ser usados mesmo sem sistema operacional
  • A estratégia central é aproveitar a estrutura em blocos da Newlib para implementar apenas o mínimo necessário das funcionalidades
  • No futuro, também é possível adicionar recursos como sistema de arquivos e gerenciamento de memória, mantendo compatibilidade com a biblioteca e reutilização em Bare Metal
  • O resultado final do projeto tem cerca de 220 KB, mantendo um tamanho relativamente pequeno e eficiente

Código-fonte no GitHub: popovicu/bare-metal-cstdlib

Ainda não há comentários.

Ainda não há comentários.