C bibliotecas estándar en metal desnudo

24

Principalmente estoy haciendo desarrollo en dispositivos que han portado Linux, por lo que la biblioteca C estándar proporciona mucha funcionalidad a través de la implementación de llamadas al sistema que tienen un comportamiento estandarizado.

Sin embargo, para el metal desnudo, no hay un SO subyacente. ¿Existe un estándar relacionado con la forma en que debe implementarse la biblioteca de CA o tiene que volver a aprender la peculiaridad de las implementaciones de una biblioteca cuando cambia a una nueva placa que proporciona un BSP diferente?

El Ingeniero Significante
fuente
44
Sitio equivocado para su pregunta.
ott--
8
Estoy votando para cerrar esta pregunta como fuera de tema porque pertenece a Stack Overflow .
uint128_t
1
Por lo general, prescindir. ¿Por qué necesitarías esas cosas sin un sistema operativo que las soporte? memcpy y tan seguro. Los sistemas de archivos, no necesariamente, aunque implementados fopen, close, etc., son triviales contra ram, por ejemplo. printf () es muy muy muy pesado, se requieren toneladas y toneladas de código, prescindir. cualquier E / S reemplaza o no. newlib es bastante extremo, pero ayuda si no puede prescindir, pero de todos modos tiene que implementar el sistema en el backend, entonces, ¿necesita la capa adicional?
old_timer
12
Si bien esta pregunta es sobre software, es muy específica de la programación integrada, que generalmente SO rechaza. Como ya tenemos algunas buenas respuestas aquí, la migración no es apropiada.
Dave Tweed
1
Si bien newlib se menciona a continuación en una respuesta, también puede encontrar que newlib-nano es útil: está destinado a ser una versión reducida para su uso en sistemas integrados con recursos limitados. Lo uso en proyectos en Cortex M0 MCU. Varios compiladores (Atollic TrueSTUDIO es uno) le dará la opción de usar newlib o newlib-nano.
jjmilburn

Respuestas:

20

Sí, hay una norma, sólo la biblioteca estándar C . Las funciones de la biblioteca no requieren un sistema operativo "completo", o cualquier sistema operativo en absoluto, y hay una serie de implementaciones adaptadas al código "bare metal", tal vez Newlib sea ​​el más conocido.

Tomando Newlib como ejemplo, requiere que escriba un pequeño subconjunto de funciones básicas, principalmente cómo se manejan los archivos y la asignación de memoria en su sistema. Si está utilizando una plataforma de destino común, es probable que alguien ya haya hecho este trabajo por usted.

Si está utilizando Linux (¿probablemente también OSX y tal vez incluso cygwin / msys?) Y escriba man strlen, debería tener una sección llamada algo así CONFORMING TO, que le diría que la implementación se ajusta a un estándar específico. De esta manera, puede averiguar si algo que ha estado usando es una función estándar o si depende de un sistema operativo específico.

tubo
fuente
1
Tengo curiosidad por saber cómo se stdlibimplementa stdiosin depender del sistema operativo. como fopen(), fclose(), fread(), fwrite(), putc()y getc()? ¿Y cómo malloc()funciona sin hablar con el sistema operativo?
Robert Bristow-Johnson
44
Con Newlib, hay una capa debajo de ella llamada "libgloss" que contiene (o escribe) un par de docenas de funciones para su plataforma. Por ejemplo, una getchary putcharlo que saben acerca de su hardware UART; luego capas Newlib printfencima de estas. La E / S de archivo también se basará en algunas primitivas.
Brian Drummond
Sí, no leí el segundo párrafo de Pipe con cuidado. Además de tratar con stdiny stdouty stderr (que se encarga de putchar()y getchar()) que dirige la E / S desde / hacia un UART, si su plataforma tiene almacenamiento de archivos, como con un flash, entonces también debe escribir pegamento para eso. y tienes que tener los medios para malloc()y free(). Creo que si se ocupa de esos problemas, puede ejecutar C portátil en su objetivo incrustado (no argvni argc).
Robert Bristow-Johnson
2
Newlib también es enorme si se trata de MCU con 1 o 2kB de espacio de código ...
Brian Drummond
2
@dwelch No estás creando tu propio sistema operativo, estás creando la biblioteca C. Si no quieres eso, entonces sí, es innecesariamente grande.
tubería
8

¿Existe un estándar relacionado con la forma en que debe implementarse la biblioteca de CA o tiene que volver a aprender la peculiaridad de las implementaciones de una biblioteca cuando cambia a una nueva placa que proporciona un BSP diferente?

En primer lugar, el estándar C define algo llamado implementación "independiente", en oposición a una implementación "alojada" (que es con lo que la mayoría de nosotros estamos familiarizados, la gama completa de funciones C compatibles con el SO subyacente).

Un "independiente" necesidades de implementación para definir sólo un subconjunto de las cabeceras de la biblioteca C, es decir, aquellos que no requieren soporte, o incluso la definición de funciones (que simplemente hacen #defines y typedefS):

  • <float.h>
  • <iso646.h>
  • <limits.h>
  • <stdalign.h>
  • <stdarg.h>
  • <stdbool.h>
  • <stddef.h>
  • <stdint.h>
  • <stdnoreturn.h>

Cuando esté dando el siguiente paso hacia una implementación alojada, encontrará que solo hay muy pocas funciones que realmente necesiten interactuar con "el sistema" de alguna manera, con el resto de la biblioteca implementable sobre esas "primitivas". ". Al implementar el PDCLib , hice un esfuerzo para aislarlos en un subdirectorio separado para una fácil identificación al portar la biblioteca a una nueva plataforma (ejemplos para el puerto Linux entre paréntesis):

  • getenv() (extern char * * environ )
  • system()( fork()/ execve()/wait() )
  • malloc()y free()( brk()/sbrk() )
  • _Exit()(_exit() )
  • time() (aún sin implementar)

Y para <stdio.h>(posiblemente el más "OS-OS" de los encabezados C99):

  • alguna forma de abrir un archivo ( open())
  • alguna forma de cerrarlo ( close())
  • alguna forma de eliminarlo ( unlink())
  • alguna forma de cambiarle el nombre ( link()/ unlink())
  • alguna forma de escribirle ( write())
  • alguna forma de leerlo ( read())
  • alguna forma de reposicionar dentro de él ( lseek())

Ciertos detalles de la biblioteca son opcionales, con el estándar simplemente ofreciéndolos para que se implementen de manera estándar, pero no hacen que dicha implementación sea un requisito.

  • La time()función puede regresar legalmente (time_t)-1si no hay mecanismos de cronometraje disponibles.

  • Los manejadores de señal descritos <signal.h>no necesitan ser invocados por otra cosa que no sea una llamada raise(), no hay requisito de que el sistema realmente envíe algo parecido SIGSEGVa la aplicación.

  • No es necesario proporcionar el encabezado C11 <threads.h>, que (por razones obvias) depende mucho del sistema operativo, si la implementación define __STDC_NO_THREADS__...

Hay más ejemplos, pero no los tengo a la mano en este momento.

El resto de la biblioteca se puede implementar sin ninguna ayuda del entorno. (*)


(*) Advertencia: la implementación de PDCLib aún no está completa, por lo que podría haber pasado por alto una o dos cosas. ;-)

DevSolar
fuente
4

El estándar C en realidad se define separado del entorno operativo. No se asume que un SO host esté presente, y las partes que dependen del host se definen como tales.

Es decir, el C Standard ya es bastante simple.

Por supuesto, las partes del lenguaje que tanto amamos, las bibliotecas, son a menudo donde el lenguaje central empuja ese material específico del host. Por lo tanto, el típico compilador cruzado "xxx-lib" encontrado para muchas herramientas de plataforma de metal desnudo.


fuente
3

Ejemplo ejecutable mínimo de Newlib

Aquí proporciono un ejemplo altamente automatizado y documentado que muestra newlib en acción en QEMU .

Con newlib, implementa sus propias llamadas al sistema para su plataforma baremetal.

Por ejemplo, en el ejemplo anterior, tenemos un programa de ejemplo exit.c:

#include <stdio.h>
#include <stdlib.h>

void main(void) {
    exit(0);
}

y en un archivo C separado common.c, implementamos exitcon semihosting ARM :

void _exit(int status) {
    __asm__ __volatile__ ("mov r0, #0x18; ldr r1, =#0x20026; svc 0x00123456");
}

Los otros syscalls típicos que implementará son:

  • writepara dar resultados al host. Esto se puede hacer con:

    • más semihosting
    • un hardware UART
  • brk para malloc .

    ¡Fácil en metal desnudo, ya que no tenemos que preocuparnos por la paginación!

TODO Me pregunto si es realista alcanzar la ejecución preventiva de syscalls de programación sin entrar en un RTOS completo como Zephyr o FreeRTOS .

Lo bueno de Newlib es que implementa todas las cosas que no son específicas del sistema operativo, como string.husted, y le permite implementar solo los apéndices del sistema operativo.

Además, no tiene que implementar todos los apéndices, sino solo los que necesitará. Por ejemplo, si su programa solo lo necesita exit, entonces no tiene que proporcionar a print.

El árbol fuente de Newlib ya tiene algunas implementaciones, incluida una implementación de semihosting ARM newlib/libc/sys/arm, pero en su mayor parte debe implementar la suya propia. Sin embargo, proporciona una base sólida para la tarea.

La forma más fácil de configurar Newlib es compilando su propio compilador con crosstool-NG, solo tiene que decirle que desea usar Newlib como la biblioteca C. Mi configuración lo maneja automáticamente con este script , que usa las configuraciones newlib presentes encrosstool_ng_config .

Creo que C ++ también funcionará, pero TODO lo prueba.

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
fuente
3
@downvoters: explique para que pueda aprender y mejorar la información. Esperemos que los futuros lectores puedan ver el valor de la única configuración introductoria de Newlib disponible en la web que simplemente funciona :-)
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
2

Cuando lo usa baremetal, descubre algunas dependencias no implementadas y tiene que manejarlas. Todas estas dependencias se tratan de ajustar los elementos internos de acuerdo con la personalidad de su sistema. Por ejemplo, cuando intenté usar sprintf () que usa malloc () dentro. Malloc tiene el símbolo de función "t_sbrk" como un código de enlace, que debe ser implementado por el usuario para aplicar las restricciones de hardware. Aquí puedo implementarlo o crear mi propio malloc () si creo que podría hacer uno mejor para el hardware incorporado, principalmente para otros usos, no solo sprintf.

Ayhan
fuente
¿Por qué sprintf necesita malloc ()?
supercat
No lo sé. Creo que tu punto es el búfer que ya tiene, ¿no? Pero incluso printf no debería necesitar malloc. ¿Tal vez para asignar dinámicamente algunas variables internas, cuando el cálculo de la salida solicitada es más pesado que la previsión de la asignación apilada (variables dinámicas de función)? Estoy seguro de que sprintf requirió malloc (arm-none-eabi-newlib). Ahora experimenté que un programa simple usa sprintf en la computadora (glibc). Nunca se llamó malloc. Luego usé printf. Se llama malloc. Malloc era falso, siempre devolvía 0. Pero funcionó bien. Debían imprimir una cadena y una variable decimal. @supercat
Ayhan
He hecho algunas versiones de printf o métodos similares, personalizadas para admitir los formatos que utilizan mis aplicaciones. La salida decimal requiere un búfer lo suficientemente largo como para contener el número más largo posible, pero de lo contrario, la rutina base acepta una estructura cuyo primer miembro es una función de salida que acepta un puntero a esa estructura junto con los datos que se emitirán. Tal diseño hace posible agregar variantes de printf que se envían a consolas, enchufes, etc., estilo curses. Nunca he tenido la necesidad de "malloc" en tal cosa.
supercat