Implementando `printf` sem SO — usando a biblioteca padrão de C em um ambiente Bare Metal
(popovicu.com)- 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,_sbrke_close, já é possível usar recursos avançados comoprintf - 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
printfcom saída via UART, entrada comscanfe 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-gdbe--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,_sbrke_closesão implementadas para integração com a Newlib _sbrkfunciona expandindo a memória heap do ponto_endaté_stack_bottom
Exemplo de aplicação: entrada e saída
- Na função
main, é possível usarprintfescanf, e os valores de entrada também são processados corretamente - Embora echo não seja suportado, ainda é possível receber strings com
scanfe 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,.datae.bss, com a heap definida de_endaté 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=medanypara que sejam geradas instruções de máquina capazes de lidar com endereços acima de0x80000000 - 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,-nostartfilese-T link.ldpermitem 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,scanfemallocpodem 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.