Implementar `printf` sin SO: uso de la biblioteca estándar de C en un entorno Bare Metal
(popovicu.com)- Introducción a un método para usar funciones estándar de C, incluido
printf, incluso sin sistema operativo, aprovechando la biblioteca Newlib - En un entorno Bare Metal basado en la arquitectura RISC-V, se implementan directamente el driver UART y las funciones de asignación de memoria para conectarlos con Newlib
- Con solo implementar un conjunto mínimo de llamadas al sistema como
_write,_sbrk,_close, etc., es posible usar funciones avanzadas comoprintf - Guía para crear un toolchain basado en Newlib, incluyendo cómo construir automáticamente el toolchain GCC para RISC-V y escribir el script de enlace
- Como resultado, se logró construir un entorno de
printfdonde funcionan la salida por UART, la entrada conscanfy la asignación dinámica de memoria
Abstracciones de software y biblioteca estándar de C
- En un SO convencional, al llamar a
printfentran en juego diversas capas de abstracción como llamadas al sistema del kernel, la capa de terminal y el renderizado de fuentes - En un entorno Bare Metal, es necesario controlar la E/S directamente sin sistema operativo, por lo que se requiere implementar los drivers manualmente
- Newlib ofrece una configuración extensible en la que, en lugar de implementar toda la biblioteca estándar de C, solo se implementa la funcionalidad mínima
Concepto de Newlib
- Internamente,
printfse implementa sobre funciones primitivas simples como_write - En Newlib, al inicio todas las funciones están definidas como stubs, y si solo se implementan las partes necesarias, el resto puede usar los valores predeterminados
- Si el desarrollador implementa solo las funciones que necesita, puede usar de forma flexible las capacidades de la biblioteca de C
Toolchain de compilación cruzada
- Para compilar de forma cruzada de x86_64/Linux → RISC-V, es necesario construir directamente desde el código fuente de GCC
- Se configura un toolchain con Newlib como biblioteca de C predeterminada para poder generar binarios para RISC-V
Detalles del toolchain
- Al construir el toolchain se usan las opciones
--prefix,--enable-multilib,--disable-gdb,--with-cmodel=medany medanyes una configuración que en RISC-V permite acceder a regiones de memoria con direcciones altas- Una vez finalizada la compilación, se puede usar el cross-compiler y la biblioteca Newlib desde la ruta
/opt/riscv-newlib
Implementación de los bloques base de memoria y UART
- Se implementa la transmisión y recepción de caracteres accediendo directamente a la dirección del hardware UART 16550A en el entorno QEMU
- Se conecta con Newlib mediante funciones sustitutivas de llamadas al sistema como
_write,_sbrk,_close, etc. _sbrkfunciona extendiendo la memoria heap desde_endhasta_stack_bottom
Ejemplo de aplicación: entrada y salida
- En la función
mainse pueden usarprintfyscanf, y los valores de entrada también se procesan correctamente - No se admite echo, pero es posible recibir y mostrar cadenas mediante
scanf - Se implementa un runtime independiente que inicializa la pila, hace zero-fill de la sección BSS y luego llama a
main
Script del enlazador
- La dirección de inicio de ejecución es
0x80000000, y el código del runtime se ubica en esa posición - La memoria se organiza en el orden
.text,.rodata,.data,.bss, y el heap se configura desde_endhasta antes de la pila - La pila tiene un tamaño fijo de 64KB, y la dirección superior es
0x80000000 + 64MB - Se evita la colisión entre heap y pila mediante la sentencia
ASSERT
El momento del “gotcha”
- Al configurar el toolchain, se debe usar
--with-cmodel=medanypara que sea posible generar instrucciones de máquina capaces de manejar direcciones por encima de0x80000000 - Si la biblioteca de C y el código de la aplicación usan modelos de direcciones distintos, se producen errores de enlace
Ejecutar la aplicación
- Con un Makefile es posible automatizar la compilación cruzada y la ejecución en QEMU
- Con las opciones
-specs=nosys.specs,-nostartfiles,-T link.ldse usa la configuración mínima de Newlib y un runtime definido por el usuario - Al ejecutar
make debug, la entrada y salida por UART funcionan correctamente en la consola de QEMU - A través de
qemu_debug.logse puede verificar el trazo real de instrucciones
Conclusión
- Se implementó con Newlib una estructura que permite usar
printf,scanf,malloc, etc. incluso sin sistema operativo - La estrategia clave es aprovechar la estructura basada en bloques de construcción de Newlib e implementar solo lo necesario, con el mínimo indispensable
- Más adelante también se pueden agregar funciones adicionales como sistema de archivos o gestión de memoria, y seguir reutilizándolo en Bare Metal manteniendo la compatibilidad con la biblioteca
- El resultado total del proyecto ocupa alrededor de 220KB, un tamaño relativamente pequeño y eficiente
Código fuente en GitHub: popovicu/bare-metal-cstdlib
Aún no hay comentarios.