Języki programowania w safety-critical

Systemy safety-critical - wszystkie wpisy

W poprzednim artykule opisywałem ogólne techniki zalecane przy developmencie systemów safety-critical. Dzisiaj natomiast przyjrzymy się bliżej zaleceniom dotyczącym języków programowania. Wiemy już, że język używany w tego typu systemach powinien być kompilowany i silnie typowany. Jakie jeszcze wymagania powinien spełniać? Jakie języki wykorzystywane są w praktyce? Poniżej znajdziesz odpowiedzi na te pytania.

Pożądane cechy języka

Norma EN 50128 definiuje cechy idealnego języka programowania dla systemów safety-critical. Powinien on posiadać wbudowane funkcje utrudniające lub uniemożliwiające popełnienie błędów. Przykładem może być tu sprawdzanie typów w trakcie kompilacji. Język powinien również mieć przyjazną składnię pozwalającą na bezproblemowy development, weryfikację i utrzymanie. Kolejną ważną cechą jest jednoznaczność, czyli brak zachowań niezdefiniowanych lub zależnych od implementacji.

Najlepiej, jeżeli język jest również popularny, a co za tym idzie mamy do dyspozycji odpowiedniej jakości narzędzia takie jak kompilatory, debugery, IDE, czy zewnętrzne biblioteki. Niemile widziane elementy języka to instrukcje skoku bezwarunkowego (czyli niesławne goto), rekurencja, wskaźniki, dynamiczna alokacja pamięci, czy niejawna deklaracja i inicjalizacja zmiennych.

Stawiane wymagania nie są możliwe do pełnego zrealizowania przez istniejące języki programowania. Zwiększenie bezpieczeństwa stoi często w sprzeczności z łatwością implementacji. Dlatego niezbędny jest pewien kompromis i nie powstał jeszcze język, który idealnie spełnia wszystkie wymagania. W normie możemy znaleźć jednak tabelę przedstawiającą rekomendowane języki.

NrLanguageSIL0SIL1SIL2SIL3SIL4
1ADARHRHRHRHR
2Modula-2RHRHRHRHR
3PASCALRHRHRHRHR
4C or C++RRRRR
5BASICRNRNRNRNR
6AssemblerRRRRR
7C#RRRRR
8JavaRRRRR

W rubrykach znajdują się stopnie rekomendacji: R- recommended, HR – highly recommended, NR – not recommended. Tabela zawiera kilka starych języków, które zdążyły już wypaść z powszechnego użytku. Natomiast niektóre nowsze języki takie jak Rust, czy Go nie zostały w ogóle uwzględnione. Poza tym kilka pozycji wymaga większego komentarza.

Dlaczego Ada jest zalecana?

Ada to język stworzony na potrzeby amerykańskiego wojska do prac nad systemami safety-critical takimi jak np. myśliwce. Głównym założeniem projektowym Ady jest umożliwienie tworzenia bezpiecznych systemów. Dlatego znajdziemy w niej między innymi:

  • możliwość określania zakresów zmiennych,
  • sprawdzanie przepełnienia buforów i wyjścia poza zakres tablicy w runtime,
  • wbudowane wsparcie dla współbieżności (umożliwia np. wykrywanie deadlocków),
  • design by contract,
  • wykrywanie błędów alokacji pamięci.

Zwiększone bezpieczeństwo programów pisanych w Adzie wiąże się z nieco mniejszą wydajnością. Jednak jest to tylko pozorny problem, ponieważ w innych językach musielibyśmy zaimplementować defensywny kod ręcznie. Więcej o Adzie można przeczytać tu i tu.

Funkcje oferowane przez Adę najlepiej wpisują się w wymagania systemów safety-critical. Dlatego nie jest niespodzianką, że norma szczególnie poleca właśnie Adę. Dziwić natomiast może uznanie za tak samo zalecane języków Pascal i Modula-2. W latach 80-tych i 90-tych były one wykorzystywane także w programowaniu embedded, jednak w dzisiejszych czasach nie są już używane. Posiadają pewne cechy, które czynią je bardziej bezpiecznymi niż na przykład najpopularniejsze w embedded C. Szczególnie system typów jest bardziej restrykcyjny – na przykład typ char nie jest konwertowany domyślnie do inta. Pascal i Modula-2 nie nadają się jednak do pisania nowoczesnych systemów. Nie są już aktywnie rozwijane i brakuje im pewnych funkcjonalności. Jednak Ada może być uznawana za spadkobiercę tych języków. Zapożyczyła część ich składni i jest modernizowana o nowe funkcjonalności wraz z kolejnymi standardami.

C w safety-critical

Najpopularniejszym językiem w systemach embedded jest C. Mimo, że Ada jest bezpieczniejsza, w systemach safety-critical również C jest szeroko wykorzystywane. Głównym założeniem C jest, że programista wie co robi i prawie wszystko jest dozwolone. Dlatego umożliwia napisanie dziwnego, trudnego do zrozumienia i potencjalnie niebezpiecznego, ale za to działającego kodu. Przykładem może być choćby Duff’s Device, czyli kreatywne podejście do składni switch-case i do-while. Poza tym C zawiera tzw. undefined behavior, czyli niektóre błędne operacje nie są zgłaszane w czasie kompilacji, ale ich wynik nie jest zdefiniowany. Dzięki temu wynikowy kod może być bardziej optymalny. Oczywiście powyższe cechy C stoją w sprzeczności z oczekiwaniami normy wobec języka programowania.

Dlatego aby stosować C w systemach safety-critical powinniśmy zdefiniować „Language Subset”, czyli wydzielić z języka potencjalnie niebezpieczną część i jej nie używać. Ogólnie przyjętym standardem w safety-critical jest MISRA-C. Została ona stworzona dla branży samochodowej w latach 90-tych, ale jest również chętnie wykorzystywana w systemach medycznych, wojskowych, czy lotniczych. Zasady zdefiniowane w MISRA-C są skonstruowane tak, aby ich stosowanie mogły wymuszać narzędzia do statycznej analizy. Zarówno treść standardu MISRA-C jak i większość narzędzi są płatne. Trwają jednak prace nad wsparciem w darmowym Cppchecku. Standard MISRA-C jest szerszym tematem i wpis na ten temat jeszcze się pojawi.

Jak to jest z C++?

W tabeli z normy C i C++ zostały potraktowane wspólnie, jednak występują między nimi znaczące różnice. W niektórych aspektach C++ zwiększa bezpieczeństwo (po raz kolejny system typów), ale dodaje również nowe funkcjonalności utrudniające wnioskowanie na temat kodu np. niejawnie wykonywany kod konstruktorów, templaty i wiele innych. Dobrze zastosowany C++ może zwiększyć zarówno bezpieczeństwo kodu jak i jego czytelność. Jednak użyty niewłaściwie będzie miał efekt odwrotny. Dodatkowo C++ rozwija się dużo aktywniej od C i co 3 lata powstaje nowy standard. Aby bezpiecznie korzystać z C++ w safety-critical, potrzebny jest podobny standard jak MISRA-C, a na to potrzeba czasu. Podejmowane są próby standaryzacji takie jak:

  • JSF++ – Coding Guideline na potrzebny Lockheed Martin do prac nad myśliwcami. Powstał na podstawie MISRA-C98 i w pracach brał udział sam Bjarne Stroustup. Zasady dla C++03. Po więcej informacji odsyłam do prezentacji na CppCon:

  • MISRA-C++2008 – Powstał w dużej mierze w oparciu o JSF++, również zawiera zasady dla C++03 i brakuje aktualizacji standardu dla modern C++.
  • AUTOSAR-C++14 – nowszy standard dla branży automotive wydany w 2017, darmowy i obejmujący C++14. Zasady są wzorowane na MISRA-C++, C++ Core Guidelines, czy książce Effective Modern C++.

Jednak nie mają one szans nadążyć za tempem pojawiania się nowych wersji C++.

Java i C#

Co ciekawe Java i C# w tabeli wcale nie są mniej zalecane niż C, C++ czy Asembler. W końcu te języki wyparły C++, bo są łatwiejsze w użyciu i bezpieczniejsze. Jednak w przypadku Javy i C# główne problemy są związane z zarządzaniem pamięcią. Bazują one na dynamicznej alokacji, która nie jest mile widziana w safety-critical. Poza tym posiadają garbage collector wprowadzający do systemu niedeterministyczność. Systemy czasu rzeczywistego muszą mieć zapewnione czasy (na poziomie mili czy nawet mikrosekund) wykonania pewnych operacji IO takich jak np. odcięcie zasilania sterowanego silnika w przypadku awarii.

Kolejnym ograniczeniem Javy i C# jest zależność od systemu operacyjnego. W systemach embedded nie mamy do dyspozycji Windowsa, czy Linuxa. Najczęściej działamy na systemach operacyjnych czasu rzeczywistego (RTOSach), albo w ogóle bez żadnego systemu, czyli mamy aplikację bare-metal. Podejmowane są próby wykorzystania tych języków w systemach embedded, ale raczej są to projekty eksperymentalne.

Jednak systemy safety-critical nie ograniczają się tylko do systemów embedded. Owszem – zwykle sterowniki odpowiedzialne za najbardziej krytyczne operacje to systemy embedded, jednak istnieją też systemy wyższego poziomu, które mogą na przykład zajmować się nadzorem, czy być interfejsem użytkownika dla operatorów. Tutaj mogą sprawdzić się już standardowe aplikacje desktopowe, które jak najbardziej można pisać w Javie albo C#.

Co z Rustem?

Bardzo ciekawą alternatywą dla języków aktualnie wykorzystywanych w systemach safety-critical ostatnio staje się Rust. Podobnie jak Ada jest on tworzony z myślą o bezpieczeństwie. Z kolei składnia jest trochę wzorowana na C++. Rust między innymi uniemożliwia używanie null pointerów i zapobiega problemom ze współbieżnością takim jak wyścigi. Rust posiada dwa tryby – safe i unsafe. W trybie safe niedozwolone są pewne operacje mogące skutkować niezdefiniowanym zachowaniem. Jeżeli natomiast chcemy np. odnosić się bezpośrednio do szczegółów hardware’owych – możemy skorzystać z trybu unsafe.

Możliwości Rusta czynią go bardzo kuszącym wyborem do nowych projektów, niestety w safety-critical jest jeszcze zbyt wcześnie, żeby go używać. Dopiero niedawno Rust stał się w miarę stabilny, a nowe wersje dalej wychodzą co kilka tygodni. Musimy również poczekać na biblioteki, narzędzia i wsparcie od producentów procesorów. Dlatego Rust bez wątpienia znajdzie zastosowanie w safety-critical, ale pewnie dopiero za kilka lat. Najpierw musi dojrzeć i sprawdzić się w różnych mniej wymagających projektach.

 

Systemy safety-critical - Nawigacja

4 Comments

  1. W C można CAŁKIEM wszysto, nie wiesz o __asm{//czy jakoś tak?}

    • GAndaLF

      29 lipca 2021 at 13:44

      No wstawki asemblerowe można robić w C. Tak samo można linkować pliki obiektowe napisane w innych językach. Safety-critical te wszystkie możliwości próbuje właśnie ograniczyć

  2. Lucjan Dobrowolski

    9 kwietnia 2023 at 11:54

    „Pascal i Modula-2 nie nadają się jednak do pisania nowoczesnych systemów. ”

    Czy można wiedzieć dlaczego się nie nadają? Czy czegoś im brakuje? Bo jeśli Autor ma na myśli to, że brak jest kompilatorów to trafniejsze byłoby stwierdzenie: „W tych językach nie da się obecnie programować z powodu braku kompilatorów”.

    Nie wiem czy obecnie istnieją współczesne kompilatory języka Modula (pewnie łatwo to sprawdzić), ale wiem na pewno, że istnieją kompilatory języka Pascal. Ponadto oprócz języka Pascal istnieje też język Object Pascal (sama nazwa mówi za siebie, czym różni się od Pascal’a). Komercyjny kompilator jest zawarty w środowisku Delphi. Natomiast kompilator open sorce to Free Pascal. Jest dostępny dla sporej liczby rodzin procesorów (i OS’ów) a także niektórych rodzin mikrokontrolerów. Prawda, że język ten jest obecnie mało popularny, ale nie prawdą jest, że nie da się go obecnie użyć. Skoro są kompilatory, biblioteki i debuggery, nie jest problemem programować z ich użyciem. Od wielu lat używam zarówno C, C++ jak i Pascal’a, Object Pascala a także Javy i C#. Obecny Pascal i Obejct Pascal można posumować następująco: „to nie jest Pascal twojego dziadka”. Podobnie jak obecne C i C++ „to nie są C i C++ twojego dziadka”.

    Z moich obserwacji i doświadczenia wynika, że mała popularność Pascal’a (i Object Pascal’a) nie jest spowodowana jakimiś jego ogromnymi wadami czy brakami, tylko raczej walką pomiędzy firmami. Kompilatory, biblioteki czy IDE to produkty. A firmy tworzy się po to, aby przynosiły dochód. Produkt trzeba rozpropagować, a to wymaga prowadzenia odpowiedniej kampanii. Dowodem na to jest ogromna popularność C#. Nie jest przypadkiem, że jego dostawcą jest jedna z największych i najbogatszych firm IT. Microsoft przez lata wpompował gigantyczną ilość pieniędzy zarówno w promocję C# a także w kampanię negatywną (słynne FUD’y) wobec produktów swoich głównych konkurentów (obecnie jest to Java)*. Tymczasem jak Autor słusznie zauważa, języki generujące kod wykorzystujący VM i GC do pewnych zastosowań się nie nadają. Gorzej, obecny trend w IT promuje języki skryptowe, które posiadają więcej wad niż zalet. Owszem one mogą być użyteczne do automatyzacji prostych prac, ale nie do tworzenia pełnowartościowego oprogramowania. Całkowicie zgadzam się więc z Autorem, że pewne kategorie oprogramowania wymagają stosowania narzędzi o ściśle określonych cechach.

    Tak czy inaczej, naprawdę jestem ciekaw argumentów Autora, które wyjaśniałaby dlaczego Pascal nie nadaje się do pisania nowoczesnych systemów. Być może są jakieś aspekty na które ja nie zwróciłem uwagi przez tyle lat programowania (zacząłem w 1998 roku). Wszak nie ma ludzi nieomylnych.


    * Takich kampanii negatywnych było więcej w firmie Microsoft. Myślę, że wszyscy dobrze pamiętamy deprecjonowanie Linux’a za czasów rządów Steve’a Balmer’a.

    • GAndaLF

      9 kwietnia 2023 at 22:22

      W tekście napisałem: „Pascal i Modula-2 nie nadają się jednak do pisania nowoczesnych systemów. Nie są już aktywnie rozwijane i brakuje im pewnych funkcjonalności.” To że nie są aktywnie rozwijane to dla mnie synonim braku nowoczesnych narzędzi w jakości produkcyjnej do pracy nad komercyjnymi projektami. A jeżeli chodzi o same języki Pascal i Modula to też w tekście piszę że „by design” są bardziej odporne na głupie błędy i strzelanie sobie w stopę niż C.

      A co do popularności języków i kontroli przez firmy to się w pełni zgadzam. W dużej mierze ograniczona migracja z C na C++ na mikrokontrolerach wynika z niechęci producentów chipów do pisania bibliotek peryferiów pod C++. A mała popularność Ady wynika z tego, że języki oparte na C wygrały walkę i teraz są dużo bardziej popularne. Uczenie się kolejnych języków wywodzących się z C jest dużo prostsze niż przestawienie się na Adę wywodzącą się z Pascala.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *