Bibliothèques standard C sur métal nu

24

Je fais principalement du développement sur des appareils qui ont porté Linux, de sorte que la bibliothèque C standard fournit de nombreuses fonctionnalités grâce à la mise en œuvre d'appels système qui ont un comportement normalisé.

Cependant, pour le métal nu, il n'y a pas de système d'exploitation sous-jacent. Existe-t-il une norme relative à la façon dont la bibliothèque AC doit être implémentée ou devez-vous réapprendre la particularité des implémentations d'une bibliothèque lorsque vous passez à une nouvelle carte qui fournit un BSP différent?

TheMeaningfulEngineer
la source
4
Site incorrect pour votre question.
ott--
8
Je vote pour fermer cette question comme hors sujet car elle appartient à Stack Overflow .
uint128_t
1
En général, vous vous en sortez. Pourquoi auriez-vous besoin de telles choses sans un système d'exploitation pour les prendre en charge? memcpy et si sûr. Les systèmes de fichiers, pas nécessairement, bien qu'implémentés fopen, close, etc. sont triviaux contre ram par exemple. printf () est très très très lourd, des tonnes et des tonnes de code sont nécessaires, sans cela. toute E / S remplacer ou se passer. newlib est assez extrême, mais aide si vous ne pouvez pas vous en passer, mais vous devez quand même implémenter le système sur le backend, avez-vous donc besoin de la couche supplémentaire?
old_timer
12
Bien que cette question concerne les logiciels, elle est très spécifique à la programmation embarquée, généralement rejetée par SO. Comme nous avons déjà quelques bonnes réponses ici, la migration n'est pas appropriée.
Dave Tweed
1
Bien que newlib soit mentionné ci-dessous dans une réponse, vous pouvez également trouver newlib-nano utile - il est destiné à être une version allégée pour une utilisation dans des systèmes embarqués à ressources limitées. Je l'utilise dans des projets sur les MCU Cortex M0. Un certain nombre de compilateurs (Atollic TrueSTUDIO en étant un) donneront la possibilité d'utiliser newlib ou newlib-nano.
jjmilburn

Réponses:

20

Oui, il existe une norme, il suffit de la bibliothèque standard C . Les fonctions de la bibliothèque ne nécessitent pas de système d'exploitation «complet», ni aucun système d'exploitation, et il existe un certain nombre d'implémentations adaptées au code «nu», Newlib étant peut-être le plus connu.

Prenant Newlib comme exemple, cela vous oblige à écrire un petit sous-ensemble de fonctions de base, principalement comment les fichiers et l'allocation de mémoire sont gérés dans votre système. Si vous utilisez une plate-forme cible commune, il est probable que quelqu'un ait déjà fait ce travail pour vous.

Si vous utilisez linux (probablement aussi OSX et peut-être même cygwin / msys?) Et tapez man strlen, il devrait avoir une section appelée quelque chose comme CONFORMING TO, qui vous dirait que l'implémentation est conforme à une norme spécifique. De cette façon, vous pouvez déterminer si quelque chose que vous avez utilisé est une fonction standard ou si cela dépend d'un système d'exploitation spécifique.

tuyau
la source
1
je suis curieux de savoir comment un stdlibimplémente stdiosans dépendre de l'OS. comme fopen(), fclose(), fread(), fwrite(), putc()et getc()? et comment malloc()fonctionne sans parler au système d'exploitation?
robert bristow-johnson
4
Avec Newlib, il y a une couche en dessous appelée "libgloss" qui contient (ou vous écrivez) quelques dizaines de fonctions pour votre plate-forme. Par exemple, a getcharet putcharqui connaissent l'UART de votre matériel; puis des couches Newlib printfpar dessus. Les E / S de fichiers s'appuieront également sur quelques primitives.
Brian Drummond
ouais, je n'ai pas lu attentivement le 2ème paragraphe de la pipe. en plus de gérer stdinet stdoutet stderr (qui prend en charge putchar()et getchar()) qui dirige les E / S de / vers un UART, si votre plate-forme dispose d'un stockage de fichiers, comme avec un flash, vous devez également écrire de la colle pour cela. et vous devez avoir les moyens de malloc()et free(). Je pense que si vous vous occupez de ces problèmes, vous pouvez à peu près exécuter le C portable dans votre cible intégrée (non argvni argc).
robert bristow-johnson
2
Newlib est également énorme si vous avez affaire à des microcontrôleurs avec 1 ou 2 Ko d'espace de code ...
Brian Drummond
2
@dwelch Vous ne créez pas votre propre système d'exploitation, vous créez la bibliothèque C. Si vous ne voulez pas ça, alors oui, c'est inutile.
pipe
8

Existe-t-il une norme relative à la façon dont la bibliothèque AC doit être implémentée ou devez-vous réapprendre la particularité des implémentations d'une bibliothèque lorsque vous passez à une nouvelle carte qui fournit un BSP différent?

Tout d'abord, la norme C définit quelque chose appelé une implémentation "autonome", par opposition à une implémentation "hébergée" (ce que la plupart d'entre nous connaissent, la gamme complète des fonctions C prises en charge par le système d'exploitation sous-jacent).

Une implémentation "autonome" doit définir uniquement un sous-ensemble des en-têtes de bibliothèque C, à savoir ceux qui ne nécessitent pas de support, ou même la définition de fonctions (ils font simplement #defines et typedefs):

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

Lorsque vous passez à l'étape suivante vers une implémentation hébergée, vous constaterez qu'il n'y a que très peu de fonctions qui ont vraiment besoin d'interfacer "le système" de quelque manière que ce soit, le reste de la bibliothèque pouvant être implémenté en plus de ces "primitives". ". En implémentant le PDCLib , j'ai fait quelques efforts pour les isoler dans un sous-répertoire séparé pour une identification facile lors du portage de la lib vers une nouvelle plateforme (exemples pour le port Linux entre parenthèses):

  • getenv()( extern char * * environ)
  • system()( fork()/ execve()/ wait())
  • malloc()et free()( brk()/ sbrk())
  • _Exit()( _exit())
  • time() (pas encore mis en œuvre)

Et pour <stdio.h>(sans doute le plus "OS impliqué" des en-têtes C99):

  • un moyen d'ouvrir un fichier ( open())
  • un moyen de le fermer ( close())
  • un moyen de le supprimer ( unlink())
  • un moyen de le renommer ( link()/ unlink())
  • un moyen de lui écrire ( write())
  • un moyen d'en lire ( read())
  • un moyen de se repositionner en son sein ( lseek())

Certains détails de la bibliothèque sont facultatifs, la norme proposant simplement de les implémenter de manière standard mais ne faisant pas d'une telle implémentation une exigence.

  • La time()fonction peut légalement juste revenir (time_t)-1si aucun mécanisme de chronométrage n'est disponible.

  • Les gestionnaires de signaux décrits pour <signal.h>n'ont pas besoin d'être invoqués par autre chose qu'un appel à raise(), il n'y a aucune exigence que le système envoie réellement quelque chose comme SIGSEGVà l'application.

  • L'en-tête C11 <threads.h>, qui est (pour des raisons évidentes) très dépendant du système d'exploitation, n'a pas du tout besoin d'être fourni si l'implémentation définit __STDC_NO_THREADS__...

Il y a d'autres exemples, mais je ne les ai pas sous la main pour le moment.

Le reste de la bibliothèque peut être implémenté sans aucune aide de l'environnement. (*)


(*) Avertissement: la mise en œuvre de PDCLib n'est pas encore terminée, donc j'aurais peut-être oublié une chose ou deux. ;-)

DevSolar
la source
4

La norme C est en fait définie séparément de l'environnement d'exploitation. Aucune hypothèse n'est faite sur la présence d'un OS hôte, et les parties qui dépendent de l'hôte sont définies comme telles.

Autrement dit, la norme C est déjà assez de métal nu.

Bien sûr, les parties de langage que nous aimons tant, les bibliothèques, sont souvent là où le langage principal pousse à héberger des choses spécifiques. Par conséquent, le compilateur croisé "xxx-lib" typique trouvé pour de nombreux outils de plate-forme bare metal.


la source
3

Exemple exécutable minimal de Newlib

Ici, je fournis un exemple hautement automatisé et documenté qui montre newlib en action dans QEMU .

Avec newlib, vous implémentez vos propres appels système pour votre plateforme baremetal.

Par exemple, dans l'exemple ci-dessus, nous avons un exemple de programme exit.c:

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

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

et dans un fichier C séparé common.c, nous implémentons le exitavec ARM semihosting :

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

Les autres appels système typiques que vous implémenterez sont:

  • writepour afficher les résultats sur l'hôte. Cela peut être fait soit avec:

    • plus semihosting
    • un matériel UART
  • brkpour malloc.

    Facile sur baremetal, car nous n'avons pas à nous soucier de la pagination!

TODO Je me demande s'il est réaliste d'atteindre l'exécution de syscalls de planification préemptive sans entrer dans un RTOS complet comme Zephyr ou FreeRTOS .

La chose intéressante à propos de Newlib, c'est qu'il implémente toutes les choses non spécifiques au système d'exploitation comme string.hpour vous, et vous permet d'implémenter uniquement les talons du système d'exploitation.

De plus, vous n'avez pas à implémenter tous les stubs, mais seulement ceux dont vous aurez besoin. Par exemple, si votre programme a seulement besoin exit, vous n'avez pas à fournir de print.

L'arbre source de Newlib a déjà quelques implémentations, y compris une implémentation semi-hôte ARM sous newlib/libc/sys/arm, mais pour la plupart, vous devez implémenter la vôtre. Il fournit cependant une base solide pour la tâche.

La façon la plus simple de configurer Newlib est de construire votre propre compilateur avec crosstool-NG, il vous suffit de lui dire que vous souhaitez utiliser Newlib comme bibliothèque C. Ma configuration gère cela automatiquement pour vous avec ce script , qui utilise les configurations newlib présentes sur crosstool_ng_config.

Je pense que C ++ fonctionnera également, mais TODO le teste.

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
la source
3
@downvoters: veuillez expliquer afin que je puisse apprendre et améliorer les informations. Espérons que les futurs lecteurs pourront voir la valeur de la seule configuration d'introduction de Newlib disponible sur le Web qui fonctionne :-)
Ciro Santilli 事件 改造 中心 法轮功 六四 事件
2

Lorsque vous l'utilisez baremetal, vous découvrez des dépendances non implémentées et devez les gérer. Toutes ces dépendances concernent le réglage des internes en fonction de la personnalité de votre système. Par exemple, lorsque j'ai essayé d'utiliser sprintf () qui utilise malloc () à l'intérieur. Malloc a le symbole de fonction "t_sbrk" comme un crochet dans le code, qui doit être implémenté par l'utilisateur pour appliquer les contraintes matérielles. Ici, je peux l'implémenter, ou faire mon propre malloc () si je crois que je pourrais en faire un meilleur pour le matériel embarqué, principalement pour d'autres utilisations, pas seulement sprintf.

Ayhan
la source
Pourquoi sprintf devrait-il avoir besoin de malloc ()?
supercat
Je ne sais pas. Je pense que votre point est le tampon qu'il a déjà, n'est-ce pas? Mais même printf ne devrait pas avoir besoin de malloc. Peut-être pour allouer dynamiquement certaines variables internes, lorsque le calcul de la sortie demandée est plus lourd que la prévision de l'allocation empilée (variables dynamiques de fonction)? Je suis sûr que sprintf avait besoin de malloc (arm-none-eabi-newlib). Maintenant, j'ai expérimenté un programme simple qui utilise sprintf sur ordinateur (glibc). Il n'a jamais appelé malloc. Puis utilisé printf. Il a appelé malloc. Malloc était faux, retournant toujours 0. Mais cela a bien fonctionné. Ils devaient imprimer une chaîne et une variable décimale. @supercat
Ayhan
J'ai créé moi-même quelques versions de printf ou de méthodes similaires, personnalisées pour prendre en charge les formats utilisés par mes applications. La sortie décimale nécessite un tampon suffisamment long pour contenir le nombre le plus long possible, mais sinon la routine de base accepte une structure dont le premier membre est une fonction de sortie qui accepte un pointeur vers cette structure avec les données à sortir. Une telle conception permet d'ajouter des variantes printf qui sortent sur des consoles de style curses, des sockets, etc. Je n'ai jamais eu besoin de "malloc" dans une telle chose.
supercat