14 puntos por GN⁺ 2025-05-06 | Aún no hay comentarios. | Compartir por WhatsApp
  • 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 como printf
  • 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 printf donde funcionan la salida por UART, la entrada con scanf y la asignación dinámica de memoria

Abstracciones de software y biblioteca estándar de C

  • En un SO convencional, al llamar a printf entran 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, printf se 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
  • medany es 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.
  • _sbrk funciona extendiendo la memoria heap desde _end hasta _stack_bottom

Ejemplo de aplicación: entrada y salida

  • En la función main se pueden usar printf y scanf, 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 _end hasta 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=medany para que sea posible generar instrucciones de máquina capaces de manejar direcciones por encima de 0x80000000
  • 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.ld se 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.log se 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.

Aún no hay comentarios.