Standardowe biblioteki C na goły metal

24

Zajmuję się głównie programowaniem na urządzeniach z portem Linux, więc standardowa biblioteka C zapewnia wiele funkcji dzięki implementacji wywołań systemowych o znormalizowanym zachowaniu.

Jednak w przypadku samego metalu nie ma systemu operacyjnego. Czy istnieje standard związany z tym, w jaki sposób należy zaimplementować bibliotekę ac, czy też trzeba nauczyć się osobliwości implementacji biblioteki po przejściu na nową płytę, która zapewnia inny BSP?

TheMeaningfulEngineer
źródło
4
Niewłaściwa strona dla twojego pytania.
ott--
8
Głosuję za zamknięciem tego pytania jako nie na temat, ponieważ należy ono do przepełnienia stosu .
uint128_t
1
Ogólnie robisz to bez. Dlaczego potrzebujesz takich rzeczy bez systemu operacyjnego do ich obsługi? memcpy i takie pewne. Systemy plików, niekoniecznie, chociaż zaimplementowane fopen, close itp. Są trywialne w stosunku do RAM. printf () jest bardzo, bardzo, bardzo ciężki, wymagane jest mnóstwo ton kodu, bez niego. dowolne I / O zastępują lub nie wymagają. newlib jest dość ekstremalny, ale pomaga, jeśli nie możesz się bez niego obejść, ale i tak musisz zaimplementować system na zapleczu, więc potrzebujesz dodatkowej warstwy?
old_timer
12
Chociaż to pytanie dotyczy oprogramowania, jest ono bardzo specyficzne dla programowania osadzonego, które generalnie jest odrzucane przez SO. Ponieważ mamy już kilka dobrych odpowiedzi, migracja nie jest odpowiednia.
Dave Tweed
1
Chociaż newlib jest wymieniony poniżej w odpowiedzi, może się również przydać newlib-nano - jego przeznaczeniem jest wersja uproszczona do użytku w systemach osadzonych o ograniczonych zasobach. Używam go w projektach na MCU Cortex M0. Szereg kompilatorów (Atollic TrueSTUDIO to jeden) daje opcję użycia newlib lub newlib-nano.
jjmilburn

Odpowiedzi:

20

Tak, jest to standard, po prostu biblioteki standardowej C . Funkcje biblioteczne nie wymagają „pełnego systemu operacyjnego” ani żadnego systemu operacyjnego, a istnieje wiele implementacji dostosowanych do kodu „bare metal”, prawdopodobnie Newlib jest najbardziej znany.

Biorąc za przykład Newlib, wymaga napisania małego podzbioru podstawowych funkcji, głównie sposobu obsługi plików i alokacji pamięci w systemie. Jeśli korzystasz ze wspólnej platformy docelowej, istnieje prawdopodobieństwo, że ktoś już wykonał tę pracę za Ciebie.

Jeśli używasz Linuksa (prawdopodobnie także OSX, a może nawet cygwin / msys?) I piszesz man strlen, powinien on mieć sekcję o nazwie coś podobnego CONFORMING TO, co wskazywałoby, że implementacja jest zgodna z określonym standardem. W ten sposób możesz dowiedzieć się, czy coś, czego używasz, jest funkcją standardową, czy też zależy od konkretnego systemu operacyjnego.

rura
źródło
1
jestem ciekawy, jak stdlibimplementuje się stdiobez uzależnienia od systemu operacyjnego. jak fopen(), fclose(), fread(), fwrite(), putc()i getc()? i jak malloc()działa bez rozmowy z systemem operacyjnym?
Robert Bristol-Johnson
4
W Newlib znajduje się warstwa o nazwie „libgloss”, która zawiera (lub piszesz) kilkadziesiąt funkcji dla twojej platformy. Na przykład a getchari putcharktóre wiedzą o UART twojego sprzętu; następnie warstwy Newlib printfna nich. Plikowe operacje wejścia / wyjścia będą podobnie opierały się na kilku operacjach podstawowych.
Brian Drummond
tak, nie przeczytałem dokładnie drugiego akapitu fajki. Poza tym do czynienia z stdini stdouta stderr (który dba putchar()i getchar()), który kieruje I / O z / do UART, jeśli platforma ma zapisu plików, jak przy użyciu lampy błyskowej, to trzeba napisać, że również dla kleju. i musisz mieć środki na malloc()i free(). myślę, że jeśli rozwiążesz te problemy, możesz praktycznie uruchomić przenośne C we wbudowanym celu (nie argvani argc).
Robert Bristol-Johnson
2
Newlib jest również ogromny, jeśli masz do czynienia z MCU z 1 lub 2kB przestrzeni kodowej ...
Brian Drummond
2
@dwelch Nie tworzysz własnego systemu operacyjnego, tworzysz bibliotekę C. Jeśli tego nie chcesz, to tak, to niepotrzebnie duże.
rura
8

Czy istnieje standard związany z tym, w jaki sposób należy zaimplementować bibliotekę ac, czy też trzeba nauczyć się osobliwości implementacji biblioteki po przejściu na nową płytę, która zapewnia inny BSP?

Po pierwsze, standard C definiuje coś, co nazywa się implementacją „wolnostojącą”, w przeciwieństwie do implementacji „hostowanej” (co jest znane większości z nas, pełen zakres funkcji C obsługiwanych przez podstawowy system operacyjny).

Implementacja „wolnostojąca” musi definiować tylko podzbiór nagłówków biblioteki C, a mianowicie te, które nie wymagają wsparcia, a nawet definicję funkcji (tylko #defines i typedefs):

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

Kiedy robisz następny krok w kierunku implementacji hostowanej, przekonasz się, że istnieje bardzo niewiele funkcji, które naprawdę muszą w jakikolwiek sposób interfejsować „system”, a resztę biblioteki można zaimplementować na podstawie tych „prymitywów” „. Wdrażając PDCLib , starałem się odizolować je w osobnym podkatalogu, aby ułatwić identyfikację podczas przenoszenia biblioteki lib na nową platformę (przykłady portu Linux w nawiasach):

  • getenv() (extern char * * environ)
  • system() (fork() / execve()/ wait())
  • malloc()i free()( brk()/ sbrk())
  • _Exit()( _exit())
  • time() (jeszcze niezaimplementowane)

I dla <stdio.h>(prawdopodobnie najbardziej „zaangażowanego w system operacyjny” z nagłówków C99):

  • jakiś sposób na otwarcie pliku ( open())
  • jakiś sposób, aby go zamknąć ( close())
  • jakiś sposób, aby go usunąć ( unlink())
  • jakiś sposób na zmianę nazwy ( link()/ unlink())
  • jakiś sposób na napisanie do niego ( write())
  • jakiś sposób na odczytanie z niego ( read())
  • jakiś sposób na repozycjonowanie w nim ( lseek())

Niektóre szczegóły biblioteki są opcjonalne, a standard oferuje jedynie ich implementację w standardowy sposób, ale nie czyni takiej implementacji wymaganiem.

  • time()Funkcja może legalnie prostu wrócić(time_t)-1 , jeśli nie ma mechanika czas utrzymywania są dostępne.

  • Opisane procedury obsługi sygnałów <signal.h>nie muszą być wywoływane przez nic innego niż wezwanie do raise(), nie ma wymogu, aby system faktycznie wysyłał coś podobnego SIGSEGVdo aplikacji.

  • Nagłówek C11 <threads.h>, który (z oczywistych powodów) jest bardzo zależny od systemu operacyjnego, nie musi być wcale dostarczany, jeśli implementacja definiuje __STDC_NO_THREADS__...

Jest więcej przykładów, ale nie mam ich teraz pod ręką.

Resztę biblioteki można zaimplementować bez pomocy środowiska. (*)


(*) Zastrzeżenie: Implementacja PDCLib nie jest jeszcze ukończona, więc mogłem przeoczyć coś lub dwa. ;-)

DevSolar
źródło
4

Standard C jest faktycznie zdefiniowany oddzielnie od środowiska operacyjnego. Nie zakłada się obecności systemu operacyjnego hosta, a części zależne od hosta są zdefiniowane jako takie.

Oznacza to, że C Standard jest już całkiem gołym metalem.

Oczywiście te części językowe, które tak bardzo kochamy, biblioteki, są często miejscem, w którym główny język popycha konkretne rzeczy. Stąd typowe dla kompilatora krzyżowego „xxx-lib” znalezione dla wielu narzędzi platformy typu bare metal.


źródło
3

Przykład minimalnego uruchamiania Newlib

Tutaj podaję wysoce zautomatyzowany i udokumentowany przykład pokazujący newlib w działaniu w QEMU .

Dzięki newlib możesz zaimplementować własne wywołania systemowe dla swojej platformy baremetal.

Na przykład w powyższym przykładzie mamy przykładowy program exit.c:

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

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

a w osobnym pliku C common.cimplementujemy semihostingexit z ARM :

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

Inne typowe wywołania systemowe, które zaimplementujesz, to:

  • writewyprowadzać wyniki do hosta. Można to zrobić za pomocą:

    • więcej półhostingu
    • sprzęt UART
  • brkdla malloc.

    Łatwo na boso, ponieważ nie musimy przejmować się stronicowaniem!

TODO Zastanawiam się, czy realistyczne jest osiągnięcie wyprzedzającego wykonania syscalls planowania bez wchodzenia w pełną wersję RTOS, taką jak Zephyr lub FreeRTOS .

Fajną rzeczą w Newlib jest to, że implementuje on wszystkie rzeczy niezwiązane z systemem operacyjnym, takie jak string.h dla ciebie, i pozwala implementować tylko pośredniczące systemu operacyjnego.

Nie musisz też implementować wszystkich kodów pośredniczących, ale tylko tych, których będziesz potrzebować. Na przykład, jeśli twój program potrzebuje tylko exit, nie musisz podawać print.

Drzewo źródeł Newlib ma już pewne implementacje, w tym implementację półhostingu ARM pod newlib/libc/sys/arm, ale w większości musisz implementować własną. Stanowi jednak solidną podstawę do zadania.

Najprostszym sposobem na skonfigurowanie Newlib jest zbudowanie własnego kompilatora za pomocą crosstool-NG, wystarczy powiedzieć, że chcesz używać Newlib jako biblioteki C. Moja konfiguracja obsługuje to automatycznie za pomocą tego skryptu , który korzysta z konfiguracji newlib obecnych w crosstool_ng_config.

Myślę, że C ++ również będzie działał, ale TODO to przetestuje.

Ciro Santilli
źródło
3
@downvoters: proszę wyjaśnij, abym mógł nauczyć się i poprawić informacje. Mam nadzieję, że przyszli czytelnicy zobaczą wartość jedynej wprowadzającej konfiguracji Newlib dostępnej w sieci, która po prostu działa :-)
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
2

Kiedy używasz go od razu, odkrywasz pewne niezaimplementowane zależności i musisz sobie z nimi poradzić. Wszystkie te zależności dotyczą dostosowania elementów wewnętrznych do osobowości twojego systemu. Na przykład, gdy próbowałem użyć sprintf (), który korzysta z malloc () wewnątrz. Malloc ma symbol funkcji „t_sbrk” jako kod przechwytujący, który musi zostać zaimplementowany przez użytkownika w celu wymuszenia konsol sprzętowych. Tutaj mogę go zaimplementować lub stworzyć własny malloc (), jeśli uważam, że mógłbym zrobić lepszy dla wbudowanego sprzętu, głównie do innych zastosowań, nie tylko sprintf.

Ayhan
źródło
Dlaczego sprintf powinien mieć funkcję malloc ()?
supercat
Nie wiem Myślę, że twoim celem jest bufor, który już ma, prawda? Ale nawet printf nie powinien potrzebować malloc. Może dynamicznie alokować niektóre zmienne wewnętrzne, gdy obliczenie żądanego wyniku jest cięższe niż prognozowanie alokacji piętrowej (dynamiczne zmienne funkcji)? Jestem pewien, że sprintf wymagał malloc (arm-none-eabi-newlib). Teraz eksperymentowałem, prosty program używa sprintf na komputerze (glibc). Nigdy nie nazywał się Malloc. Następnie użyłem printf. Nazywało się to Malloc. Malloc był fałszywy, zawsze zwracał 0. Ale działało dobrze. Mieli wydrukować ciąg i zmienną dziesiętną. @supercat
Ayhan
Sam stworzyłem kilka wersji printf lub podobnych metod, dostosowanych do obsługi formatów używanych przez moje aplikacje. Wyjście dziesiętne wymaga bufora wystarczająco długiego, aby pomieścić możliwie najdłuższą liczbę, ale w przeciwnym razie procedura podstawowa akceptuje strukturę, której pierwszym elementem jest funkcja wyjściowa, która przyjmuje wskaźnik do tej struktury wraz z danymi, które mają być wyprowadzane. Taki projekt umożliwia dodanie wariantów printf, które wyświetlają się na konsolach, gniazdach itp. W stylu przekleństwa. Nigdy nie potrzebowałem w tym celu „malloc”.
supercat