Ravenscar i ZFP – czyli profile runtime w Adzie

ucgosu.pl 5 lat temu

Dzisiaj zgłębię temat profili runtime’owych w Adzie. Pozwalają one określić z jakich funkcji języka możemy korzystać w projekcie. Dzięki temu mamy lepszą kontrolę nad zużyciem pamięci, wydajnością i bezpieczeństwem aplikacji. Rozwiązanie w Adzie jest dużo lepsze niż w innych językach, z którymi miałem do czynienia, ponieważ blokuje użycie niechcianych konstrukcji już na poziomie kompilacji.

Artykuł powstał w ramach “Tygodnia z Adą” podczas którego od poniedziałku do piątku będą ukazywać się materiały na temat języka Ada. Będzie między innymi dlaczego Ada tak dobrze nadaje się do safety-critical, pokażę swoje pierwsze próby z pisaniem programów w Adzie, a także postaram się uruchomić Adę na STM32.

Podzbiory języka

Pisząc aplikacje safety-critical w C lub C++ zwykle nie korzystamy ze wszystkich funkcji języka. Pewne elementy składni języka nie powinny być stosowane, bo są nieczytelne lub łatwo używając ich popełnić błędy. Niektóre funkcje biblioteki standardowej są uznawane za niebezpieczne. W niektórych systemach nie możemy korzystać z dynamicznej alokacji pamięci itp. Dlatego między innymi powstały standardy takie jak MISRA C, czy AUTOSAR C++ zawierające zbiór tego typu zasad ogólnie przyjęty w danej branży.

Jednak wymuszenie zgodności z takim standardem jest trudne. Zwykle kompilatory skupiają się jedynie na wyłapywaniu błędów składniowych, zwracają również warningi w zależności od przekazanych flag. Jednak o wymuszenie bardziej restrykcyjnego podzbioru języka musimy zadbać przy pomocy analizy statycznej. Istnieją komercyjne kompilatory sprawdzające zgodność z MISRA C. Jednak zarówno te kompilatory, jak i narzędzia do statycznej analizy kodu nie sprawdzą wszystkiego. A choćby jeśli, to pozwalają na odstępstwa od tych reguł. Brakuje sposobu na wymuszenie bezpiecznego podzbioru języka. W Adzie jednak mają na to rozwiązanie.

Profile

Ada umożliwia definiowanie czegoś takiego jak profile, czyli podzbiory języka definiujące jakie funkcje języka są dozwolone. Istnieją standardowe profile, a użytkownik może również definiować swoje. W skład profilu może (ale nie musi) wchodzić biblioteka runtime’owa pozwalająca na realizację tych funkcji. o ile piszemy projekt w Adzie zgodny z jakimś profilem i użyjemy niedozwolonej operacji np. dynamicznej alokacji pamięci, otrzymamy błąd kompilacji.

W Adzie mamy zdefiniowane następujące profile standardowe:

  • Zero Footprint Profile (ZFP) – podzbiór Ady nie wymagający żadnej biblioteki runtime’owej. Nie zawiera dynamicznej alokacji pamięci ani współbieżności. Dostępna jest tylko część funkcji z biblioteki standardowej. Exceptiony są dostępne lokalnie w procedurach, ale nie są propagowane na zewnątrz.
  • Cert – profil będący rozszerzeniem ZFP o minimalne wsparcie tasków dla systemu operacyjnego VxWorks. Profil Cert zawiera ograniczone wsparcie dla dynamicznej alokacji, ale zwalnianie zaalokowanej pamięci nie jest dozwolone.
  • Ravenscar – definiuje zestaw funkcji związanych ze współbieżnością nadających się do działania na systemach hard real time, safety-critical, high-integrity i mających ograniczenia pamięci i szybkości wykonywania. Profile Ravenscar definiuje jedynie elementy związane ze współbieżnością. Dlatego występuje w kilku wariantach definiujących pozostałe cechy języka. Mamy więc Ravenscar SFP (Small Footprint Profile) będącym kombinacją Ravenscara i ZFP. Analogicznie mamy również Ravenscar Cert.
  • Full – pełne wsparcie wszystkich funkcji Ady.

Więcej o profilach można przeczytać w dokumentacji GNATa. Zaraz przyjrzymy się co zawierają takie profile na platformach embedded, ale najpierw musimy je zainstalować.

Instalacja profili

Część profili dla różnych platform embedded jest instalowana automatycznie razem z kompilatorem GNAT w wersji dla ARMów. Wsparcie dla większej ilości platform możemy zapewnić ściągając repozytorium Ada Drivers Library, o którym już wspominałem przy okazji odpalania przykładów na STM32. Następnie musimy uruchomić skrypt pythonowy znajdujący się w scripts/install_dependencies.py. Skrypt jest napisany w Pythonie 2, więc jeżeli mamy 3 – należy go przekonwertować razem ze wszystkimi innymi skrpytami w folderze scripts przy pomocy narzędzia pythonowego 2to3.py. Skrypt ten ściągnie nam folder bb-runtimes zawierający pliki profili i dokona instalacji. W Pythonie 3 musimy po raz kolejny dokonać konwersji dla wszystkich skryptów z bb-runtimes. o ile pracujemy na Windowsie to będziemy musieli jeszcze przekonwertować w plikach z bb-runtimes znaki końca linii na uniksowe. Bez tego miałem błędy kompilacji. Po tych zabiegach skrypt install_dependencies.py powinien zainstalować dodatkowe profile.

Niestety wspieranych targetów embedded pozostało stosunkowo mało. Na przykład dla STM32 mamy wsparcie tylko dla STM32F4 i F7. Brakuje wsparcia dla mniejszych jednostek z Cortexem-M0 i M3.

Co zawiera profil?

Pliki profilu można znaleźć w folderze: GNAT\2018-arm-elf\arm-eabi\lib\gnat\<nazwa_profilu>-<nazwa_targetu> w nawigacji bardzo pomoże plik README, który możemy znaleźć w Ada_Drivers_Library\bb-runtimes\BSPs\README- <nazwa_targetu> .txt. Przyjrzyjmy się więc, co można znaleźć w profilu ravenscar-sfp-stm32f4.

Część plików jest nam dobrze znana z projektów w C. W folderze ld mamy skrypty linkera. Są dwie wersje – do programu przechowywanego w RAMie i we FLASHu. W folderach gnat i gnarl poza wieloma plikami .ads i .adb mamy również kilka plików asemblerowych. Zawierają one procedury standardowe i definicje wektorów przerwań.

Pliki .ads i .adb natomiast zawierają funkcje biblioteki standardowej (niektóre importowane z C np. memset), ustawienia zegarów (s-stm32.adb), adresy peryferiów (i-stm32.ads), definicje przerwań i wiele innych. Duża część z plików specyficznych dla STM32 została wygenerowane z plików .svd używanych przez CubeMX. W podobny sposób są generowane biblioteki C++ modm. W folderze gnarl znajduje się biblioteka runtime i pliki .ads i .adb implementujące funkcjonalność zbliżoną do RTOSa.

Podsumowanie

W dzisiejszym odcinku nie było ani jednej linijki kodu w Adzie. Zamiast tego zajrzeliśmy pod maskę i mogliśmy zobaczyć implementację profili dla STM32 łącznie z procedurami startupowymi w asemblerze. Informacje o wewnętrznym działaniu języka są niezbędne o ile chcemy robić w nim większe projekty. Wcześniej czy później przyjdzie nam debugować procedurę startupową albo coś do niej dopisywać – na przykład testy RAMu. Poza tym znajomość profili pozwala nam na lepszą kontrolę pamięci. Teraz już na przykład wiem dlaczego eksperymenty ze zużyciem FLASHa na STM32 dawały takie wyniki. Po prostu używałem profilu ravenscar-full-stm32f4. Powtórzenie testu na ravenscar-sfp-stm32f4 dało 30kB mniej pamięci programu.

Same profile są fajną cechą Ady pozwalającą wymusić trzymanie się przyjętego standardu. Warto tu jeszcze wspomnieć o innym podzbiorze Ady, który nie jest profilem – o języku SPARK. Jest to podzbiór Ady umożliwiający formalną weryfikację kodu i udowodnienie, iż w napisanym kodzie nie mogą wystąpić żadne błędy runtime’owe.

To już koniec „Tygodnia z Adą”. Dajcie znać w komentarzach, czy się podobało! Jak widzicie – ten tydzień posłużył mi zaledwie do poznania zupełnych podstaw Ady. Żeby go dobrze opanować pewnie będę musiał napisać w tym języku jakieś projekty. Przy okazji okazało się, iż Ada owszem jest świetnym językiem rozwiązującym problemy, które mocno mi przeszkadzają w C i C++, jednak są też poważne minusy. Najbardziej doskwierało mi kilka informacji w necie o problemach, na które trafiałem. Brakuje również ekosystemu – nie wiem jak obsługiwać większy projekt z wieloma modułami, unit testami itp.

Idź do oryginalnego materiału