Librerie standard C su bare metal


24

Per lo più sto sviluppando su dispositivi che hanno effettuato il porting di Linux, quindi la libreria C standard fornisce molte funzionalità attraverso l'implementazione di chiamate di sistema che hanno un comportamento standardizzato.

Tuttavia, per il bare metal, non esiste un sistema operativo sottostante. Esiste uno standard relativo a come deve essere implementata la libreria ac o è necessario riapprendere le peculiarità delle implementazioni di una libreria quando si passa a una nuova scheda che fornisce un BSP diverso?


4
Sito errato per la tua domanda.
ott--

8
Sto votando per chiudere questa domanda come fuori tema perché appartiene allo StackTranslate.it .
uint128_t

1
Generalmente ne fai a meno. Perché avresti bisogno di queste cose senza un sistema operativo per supportarle? memcpy e così sicuro. I file system, non necessariamente, sebbene implementati fopen, close, ecc. Sono banali contro ram, ad esempio. printf () è molto, molto, molto pesante, tonnellate e tonnellate di codice richiesto, fare a meno. qualsiasi I / O sostituisce o fa a meno. newlib è piuttosto estremo, ma aiuta se non puoi farne a meno, ma devi comunque implementare il sistema sul backend, quindi hai bisogno del livello extra?
old_timer

12
Sebbene questa domanda riguardi il software, è molto specifica per la programmazione integrata, generalmente respinta da SO. Dato che qui abbiamo già delle buone risposte, la migrazione non è appropriata.
Dave Tweed

1
Mentre newlib è menzionato di seguito in una risposta, potresti anche trovare newlib-nano utile - è pensato per essere una versione ridotta per l'uso in sistemi embedded con risorse limitate. Lo uso in progetti su MCU Cortex M0. Un certo numero di compilatori (Atollic TrueSTUDIO essendo uno) darà un'opzione per usare newlib o newlib-nano.
Jjmilburn,

Risposte:


20

Sì, c'è uno standard, semplicemente la libreria standard C . Le funzioni della libreria non richiedono un sistema operativo "completo", né alcun sistema operativo, e ci sono un certo numero di implementazioni su misura per il codice "bare metal", Newlib forse è il più noto.

Prendendo come esempio Newlib, è necessario scrivere un piccolo sottoinsieme di funzioni principali, principalmente come vengono gestiti i file e l'allocazione di memoria nel sistema. Se stai utilizzando una piattaforma target comune, è probabile che qualcuno abbia già fatto questo lavoro per te.

Se stai usando Linux (probabilmente anche OSX e forse anche cygwin / msys?) E digita man strlen, dovrebbe avere una sezione chiamata qualcosa del genere CONFORMING TO, che ti direbbe che l'implementazione è conforme a uno standard specifico. In questo modo puoi capire se qualcosa che stai usando è una funzione standard o se dipende da un sistema operativo specifico.


1
sono curioso di sapere come si stdlibimplementa stdiosenza essere dipendenti dal sistema operativo. come fopen(), fclose(), fread(), fwrite(), putc()e getc()? e come malloc()funziona senza parlare al sistema operativo?
robert bristow-johnson

4
Con Newlib, sotto c'è un livello chiamato "libgloss" che contiene (o scrivi) un paio di dozzine di funzioni per la tua piattaforma. Ad esempio, a getchare putcharche conoscono l'UART dell'hardware; quindi i livelli di Newlib printfin cima a questi. Analogamente, l'I / O dei file si baserà su alcune primitive.
Brian Drummond,

sì, non ho letto attentamente il secondo paragrafo di pipe. oltre a trattare con stdine stdoute stderr (che si occupa di putchar()e getchar()) che dirige l'I / O da / verso un UART, se la tua piattaforma ha l'archiviazione dei file, come con un flash, allora devi scrivere anche la colla. e devi avere i mezzi per malloc()e free(). penso che se ti occupi di questi problemi, puoi praticamente eseguire C portatile nel tuo target incorporato (no argvargc).
robert bristow-johnson,

2
Newlib è anche enorme se hai a che fare con MCU con 1 o 2kB di spazio di codice ...
Brian Drummond

2
@dwelch Non stai creando il tuo sistema operativo, stai creando la libreria C. Se non lo vuoi, allora sì, è inutilmente grande.
pipe

8

Esiste uno standard relativo a come deve essere implementata la libreria ac o è necessario riapprendere le peculiarità delle implementazioni di una libreria quando si passa a una nuova scheda che fornisce un BSP diverso?

Prima di tutto, lo standard C definisce qualcosa chiamato implementazione "indipendente", al contrario di un'implementazione "ospitata" (che è ciò che la maggior parte di noi conosce, l'intera gamma di funzioni C supportate dal sistema operativo sottostante).

Un'implementazione "indipendente" deve definire solo un sottoinsieme delle intestazioni della libreria C, vale a dire quelle che non richiedono supporto, o anche la definizione di funzioni (semplicemente fanno #definee si typedef):

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

Quando fai il passo successivo verso un'implementazione ospitata, scoprirai che ci sono solo pochissime funzioni che hanno davvero bisogno di interfacciare "il sistema" in alcun modo, con il resto della libreria implementabile su quelle "primitive ". Nell'implementazione del PDCLib , ho fatto uno sforzo per isolarli in una sottodirectory separata per una facile identificazione durante il porting della lib su una nuova piattaforma (esempi per la porta Linux tra parentesi):

  • getenv()( extern char * * environ)
  • system() (fork() / execve()/ wait())
  • malloc() e free() ( brk()/ sbrk())
  • _Exit()( _exit())
  • time() (non ancora implementato)

E per <stdio.h> (probabilmente il più "coinvolto OS" delle intestazioni C99):

  • un modo per aprire un file (open() )
  • un modo per chiuderlo (close() )
  • un modo per rimuoverlo (unlink() )
  • un modo per rinominarlo (link() / unlink())
  • un modo per scrivergliwrite() )
  • un modo per leggere da esso (read() )
  • un modo per riposizionare al suo interno (lseek() )

Alcuni dettagli della libreria sono opzionali, con lo standard che offre semplicemente che vengano implementati in modo standard ma non rende tale implementazione un requisito.

  • La time()funzione può tornare legalmente solo (time_t)-1se non sono disponibili meccanismi di temporizzazione.

  • I gestori del segnale descritti per <signal.h>non devono essere invocati da qualcosa di diverso da una chiamata raise(), non è necessario che il sistema invii effettivamente qualcosa di simile SIGSEGVall'applicazione.

  • L'intestazione C11 <threads.h>, che è (per ovvi motivi) molto dipendente dal sistema operativo, non deve essere fornita affatto se l'implementazione definisce __STDC_NO_THREADS__...

Ci sono altri esempi, ma non li ho a portata di mano in questo momento.

Il resto della libreria può essere implementato senza alcun aiuto dall'ambiente. (*)


(*) Avvertenza: l'implementazione di PDCLib non è ancora completa, quindi potrei aver trascurato una cosa o due. ;-)


4

Lo standard C è in realtà definito separato dall'ambiente operativo. Non si ipotizza la presenza di un sistema operativo host e le parti dipendenti dall'host sono definite come tali.

Cioè, lo standard C è già piuttosto bare metal.

Naturalmente, quelle parti linguistiche che amiamo così tanto, le biblioteche, sono spesso quelle in cui il linguaggio centrale spinge l'hosting di cose specifiche. Quindi, la tipica roba da compilatore incrociato "xxx-lib" trovata per molti strumenti di piattaforma bare metal.


3

Esempio eseguibile minimo di Newlib

Qui fornisco un esempio altamente automatizzato e documentato che mostra newlib in azione in QEMU .

Con newlib, implementate le vostre chiamate di sistema per la vostra piattaforma baremetal.

Ad esempio, nell'esempio sopra, abbiamo un programma di esempio exit.c:

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

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

e in un file C separato common.c, implementiamo il semihostingexit con ARM :

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

Le altre tipiche syscalls che implementerai sono:

  • writeper produrre risultati sull'host. Questo può essere fatto con:

    • più semihosting
    • un hardware UART
  • brk per malloc .

    Facile su baremetal, dal momento che non dobbiamo preoccuparci del paging!

TODO Mi chiedo se sia realistico raggiungere l'esecuzione preventiva di syscall di programmazione senza entrare in un RTOS completo come Zephyr o FreeRTOS .

La cosa bella di Newlib è che implementa tutte le cose non specifiche del SO string.h te e ti consente di implementare solo gli stub del sistema operativo.

Inoltre, non devi implementare tutti gli stub, ma solo quelli di cui avrai bisogno. Ad esempio, se il tuo programma ha solo bisogno exit, non devi fornire un print.

L'albero dei sorgenti di Newlib ha già alcune implementazioni, inclusa un'implementazione di semihosting ARM sotto newlib/libc/sys/arm, ma per la maggior parte devi implementare la tua. Fornisce tuttavia una solida base per l'attività.

Il modo più semplice per installare Newlib è costruire il tuo compilatore con crosstool-NG, devi solo dirgli che vuoi usare Newlib come libreria C. La mia configurazione lo gestisce automaticamente per te con questo script , che utilizza le nuove configurazioni conf. Presenti incrosstool_ng_config .

Penso che anche C ++ funzionerà, ma TODO lo prova.


3
@downvoters: per favore, spiega, così posso imparare e migliorare le informazioni. Speriamo che i futuri lettori possano vedere il valore dell'unica installazione introduttiva di Newlib disponibile sul web che funziona :-)
Ciro Santilli 8 改造 中心 法轮功 六四 事件

2

Quando lo usi in metallo nudo, scopri alcune dipendenze non implementate e devi gestirle. Tutte queste dipendenze riguardano l'ottimizzazione degli interni in base alla personalità del sistema. Ad esempio, quando ho provato ad usare sprintf () che usa malloc () all'interno. Malloc ha il simbolo della funzione "t_sbrk" come hook in code, che deve essere implementato dall'utente per imporre i vincoli hardware. Qui posso implementarlo o creare il mio malloc () se credo di poterne fare uno migliore per l'hardware incorporato, principalmente per altri usi, non solo per sprintf.


Perché sprintf dovrebbe aver bisogno di malloc ()?
supercat

Non lo so. Penso che il tuo punto sia il buffer che ha già, no? Ma anche printf non dovrebbe aver bisogno di malloc. Forse allocare dinamicamente alcune variabili interne, quando il calcolo dell'output richiesto è più pesante della previsione dell'allocazione in pila (variabili dinamiche di funzione)? Sono sicuro che lo sprintf richiedesse malloc (arm-none-eabi-newlib). Ora ho sperimentato un semplice programma che utilizza sprintf su computer (glibc). Non ha mai chiamato malloc. Quindi usato printf. Si chiama malloc. Malloc era falso, restituendo sempre 0. Ma ha funzionato bene. Dovevano stampare una stringa e una variabile decimale. @supercat
Ayhan

Ho realizzato personalmente alcune versioni di printf o metodi simili, personalizzati per supportare i formati utilizzati dalle mie applicazioni. L'output decimale richiede un buffer abbastanza lungo da contenere il numero più lungo possibile, ma per il resto la routine di base accetta una struttura che il cui primo membro è una funzione di output che accetta un puntatore a quella struttura insieme ai dati da emettere. Un tale design rende possibile l'aggiunta di varianti printf che vengono emesse su console, socket, ecc. In stile maledetto. Non ho mai avuto bisogno di "malloc" in una cosa simile.
supercat
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.