W systemach safety-critical zadania często rozłożone są na wiele procesorów. Wbrew pozorom przyczyną zwykle nie jest wydajność i potrzeba zapewnienia czasów odpowiedzi spełniających wymagania systemów hard real time. Zabieg ten jest stosowany w celu wydzielenia części krytycznej dla bezpieczeństwa i zabezpieczeniem jej przed niepożądanym wpływem mniej ważnych modułów.

Czy na pewno potrzebujemy wielu procesorów?

Kiedyś, pracując nad systemami embedded ogólnego użytku, uważałem stosowanie architektury wykorzystującej kilka niezależnych procesorów za złą praktykę i przejaw braku umiejętności projektanta lub zespołu. Zadania realizowane w takich systemach bardzo rzadko wymagają pełnej mocy obliczeniowej procesora. Większym problemem wydaje się współpraca wielu tasków aktywujących się cyklicznie lub zdarzeniowo. Można stworzyć wydajny system współbieżny realizujący wiele zadań na jednym procesorze. Dlatego architektura wieloprocesorowa była dla mnie marnotrawstwem i niepotrzebną komplikacją powodującą problemy z integracją i wydłużenie developmentu. W wielu systemach embedded ogólnego użytku faktycznie tak jest.

Jednak projektując system safety-critical dochodzi nam zupełnie nowa klasa problemów. Od początku projektu prowadzimy analizy ryzyka i minimalizujemy potencjalne niebezpieczeństwo. A realizowanie na jednym procesorze komunikacji zewnętrznej, wyświetlania animacji na ekranie i obsługi reaktora jądrowego na jednym procesorze jest po prostu niepotrzebnym ryzykiem.

Podział systemu na część safety i non-safety

Projektując system safety-critical definiujemy, które funkcje wpływają na bezpieczeństwo, a które nie. Część safety musi spełniać dodatkowe warunki określone w normach. Należy w nich stosować bardziej restrykcyjne obostrzenia dotyczące choćby niedozwolonych elementów języka. W C na przykład niedozwolone mogą być niektóre rzutowania, operacje na wskaźnikach, wskaźniki na funkcje czy dynamiczna alokacja pamięci. Część funkcjonalności ważnych z perspektywy użytkownika nie musi być realizowana z takim podwyższonym rygorem.

Aby funkcje niewpływające na bezpieczeństwo realizować prostszym procesem, musimy wykazać, że są one odpowiednio odseparowane od części safety i na nią nie wpływają. Czasem wpływ może być ciężki do przewidzenia. Jeżeli w części non-safety pomylimy się w arytmetyce wskaźników, możemy przypadkowo zmodyfikować zmienną safety. Możemy też np. spowodować deadlocka, czy sterować peryferiami. Dlatego przyjmujemy, że jeżeli część non-safety znajduje się na tym samym procesorze, staje się automatycznie safety-related. Dzieli z częścią safety czas CPU, RAM, FLASH, peryferia. Błąd może skutkować zmianą zachowania w odległym miejscu programu. Dlatego musi być wytwarzana zgodnie z procesem dla części safety-related. Wydzielenie części non-safety do oddzielnego procesora pozwala nam znacząco uprościć implementację.

Dobrym kandydatem do wydzielenia jest obsługa interfejsu użytkownika, która może zawierać bajery takie jak dotykowy ekran, przyciski pojemnościowe, ruchome grafiki, interaktywne GUI. To wszystko zwiększa komfort pracy z końcowym produktem, ale jest bardzo problematyczne z punktu widzenia bezpieczeństwa. Obciążają mocno procesor i mogą być siedliskiem trudnych do wykrycia błędów. Innym przykładem jest stos TCPIP. Również może się on przyczynić do znaczącego zwiększenia funkcjonalności, ale raczej nie chcemy pisać go od zera zgodnie z normami. Co ciekawe, to właśnie takie funkcjonalności są najczęściej głównym źródłem złożoności programu. Procedury safety-critical np. sterowanie silnikiem, odczyt sensorów, czy ustawianie wyjść często nie są zbyt obciążające dla procesora.

Komunikacja między procesorami

Wydzielając część non-safety do oddzielnego procesora oszczędzamy sobie wiele roboty. Procesor safety może otrzymywać komendy po protokole komunikacyjnym i nie musi implementować GUI czy TCPIP. Do danych otrzymanych z części non-safety ma on ograniczone zaufanie, dlatego musi implementować dodatkowe sprawdzenie integralności i sensowności otrzymanych danych.

Komunikacja w systemach safety-critical jest oczywiście przedmiotem osobnych norm. Innaczej obsługuje się komunikację 1:1 a inaczej z wieloma urządzeniami na szynie. Dodatkowe środki ostrożności podejmuje się również, gdy komunikacja zachodzi w kanale otwartym, jest możliwość duplikacji, zmiany kolejności itp. Zdarza się również, że komunikacja musi zachodzić pomiędzy dwoma procesorami safety, a pośredniczące jednostki non-safety są traktowane jako przezroczyste medium transmisyjne. Służą one tylko i wyłącznie do przesyłania danych nie ingerując w ich treść.

Architekturę weryfikujemy najwcześniej jak się da

Projekt architektury najlepiej przeanalizować pod kątem zgodności z normami w jak najwcześniejszej fazie rozwoju produktu. Oszczędzamy sobie w ten sposób ryzyka późniejszej reorganizacji całego systemu w przypadku wykrycia błędnych założeń. Czasem mogą również zdarzyć się problemy specyficzne dla konkretnego projektu. Być może moduł HMI (Human Machine Interface), który robimy również jest elementem bezpieczeństwa i podlega pod bardziej rygorystyczny proces. Może to być na przykład sygnał dźwiękowy informujący o alarmie, czy wyświetlanie jakiś kluczowych danych o monitorowanym procesie chemicznym. Wtedy oczywiście architektura się dodatkowo komplikuje.

Redundancja i nadzór

Zupełnie oddzielnym tematem jest stosowanie wielu procesorów w celu zapewnienia redundancji i nadzoru w części safety. Stosuje się w tym celu pełną redundancję kanału sterowania, procesory nadzorujące, czy systemy głosowania. Jest to bardzo rozległy temat i nie będę go teraz rozwijać. Podstawowe informacje na ten temat można znaleźć w jednym z moich wcześniejszych artykułów.

Systemy bezpieczeństwa – sposoby przeciwdziałania błędom