W poprzednim artykule opisałem trochę podstawowych informacji dotyczących systemów bezpieczeństwa. Skupiłem się tam na podstawowych pojęciach i procesach. Dzisiaj mam zamiar omówić kilka aspektów technicznych. Będzie więc o tym jak takie systemy zachowują się w przypadku wykrycia błędu i jak minimalizują efekty ich wystąpienia.

Safe State

Celem działania systemów safety critical zwykle nie jest zapewnienie działania w każdej sytuacji (jest to zadanie systemów high availability, o których można przeczytać tutaj – link). Wręcz przeciwnie, systemy safety mogą celowo się wyłączyć, lub dezaktywować część swych funkcjonalności, jeśli wykryją błąd. Przechodzą wtedy do safe state. W tym stanie wyjścia są ustawiane tak, aby system nie stwarzał niebezpieczeństwa dla zdrowia i życia ludzi. Może to być na przykład wyłączenie silników w robocie przemysłowym, czy ustawienie czerwonego światła dla pociągu w systemie zarządzania ruchem. System nie może samodzielnie wyjść z tego stanu, wymagana jest interwencja człowieka.

Czasami procedura wejścia w stan bezpieczny jest dosyć skomplikowana i może zajmować więcej czasu. Na przykład w systemie sterującym przebiegiem reakcji chemicznych sterownik powinien najpierw w kontrolowany sposób stłumić reakcję, a dopiero potem przejść do docelowego stanu bezpiecznego. W przypadku gwałtownego odłączenia sterowania niebezpieczna reakcja wymknęłaby się spod kontroli.

Niektóre systemy nie mogą po prostu przejść w safestate. Dobrym przykładem może być tutaj samolot znajdujący się kilka kilometrów nad ziemią. Tak samo w przypadku aparatury medycznej podtrzymującej życie jak np. respirator, czy rozrusznik serca. Takie systemy mogą co najwyżej zasygnalizować potrzebę awaryjnego lądowania, albo interwencji lekarza. Muszą jednak zapewnić poprawne działanie do czasu, kiedy zniknie zagrożenie dla życia.

Wybór odpowiednich elementów

Wybór odpowiednich komponentów elektronicznych może dodatkowo zwiększać bezpieczeństwo systemu. Poza wymaganiami dotyczącymi tolerancji wartości, czy czynników środowiskowych (np. temperatura, wilgotność, wstrząsy) wykorzystywane są również własności fizyczne niektórych elementów. Dobrym przykładem są tutaj przekaźniki często wykorzystywane do zadawania stanów, czy sterowania urządzeniami zewnętrznymi.

Przekaźnik składa się z obwodu sterującego (na schemacie po lewej stronie) i wykonawczego. Styk obwodu wykonawczego ma pewne położenie domyślne. Jeżeli w obwodzie sterującym popłynie odpowiedni prąd, styk obwodu wykonawczego jest przełączany. Istnieją dwa rodzaje przekaźników:

  •  NO (normally open) – w położeniu domyślnym obwód jest otwarty, czyli nie płynie prąd.
  • NC (normally closed) – w położeniu domyślnym obwód jest zamknięty, czyli płynie prąd.

Jeżeli sterujemy układem, który w przypadku wejścia w safe state ma zostać odcięty od zasilania, powinniśmy zastosować przekaźnik NO. Dzięki temu nawet jak nastąpi awaria zasilania w obwodzie sterującym i system nie będzie sterować przekaźnikiem, zasilanie od urządzenia wykonawczego zostanie odłączone.

Redundancja

Projektując system bezpieczeństwa musimy zapewnić, że w przypadku błędu system poprawnie przejdzie w stan bezpieczny, albo że będzie dalej działać w przypadku awarii jakiegoś kluczowego komponentu. Oczywiście jeśli w tym samym momencie skumuluje się wystarczająco dużo błędów nie uda się tego zadania zrealizować. Możemy jednak znacząco zmniejszyć prawdopodobieństwo wystąpienia takiej sytuacji. W tym celu stosuje się redundancję, czyli zastosowanie nadmiarowych bloków realizujących tą samą funkcjonalność. Jeżeli jeden z nich się zepsuje, drugi będzie w stanie przejąć jego zadania, albo wejść w safe state.

Aby zapewnić odporność systemu na błędy ważna jest eliminacja single point of failure (SPOF), czyli elementów, których awaria spowoduje błąd całego systemu. Dlatego zastosowanie redundantnego procesora jest niewystarczające, jeśli korzysta on na przykład z tych samych czujników, czy tego samego toru zasilania. W przypadku redundancji procesora idzie się nawet dalej i specjalnie wybiera się procesory o innych architekturach, a soft na nie jest rozwijany przez niezależne zespoły. Dzięki temu błędy procesora, kompilatora, czy implementacji nie są SPOF.

Redundancja jest również realizowana na poziomie kodu źródłowego za pomocą defensive programming. Jest to technika, w której dodajemy do kodu pewne nadmiarowe operacje w celu zwiększenia niezawodności kodu w przypadku jakiś nieprzewidzianych sytuacji. Na przykład jeśli funkcja może przyjąć jako argument tylko liczby od 1 do 10 – dodajemy sprawdzenie sygnalizujemy ewentualny błąd. Inny przykład to konstrukcja if – else if, gdzie pokryliśmy wszystkie możliwe przypadki, a tak dodajemy na końcu blok else, tak samo z blokiem default w instrukcji switch. Dzięki takim zabiegom nawet jeśli wystąpi np. przepełnienie stosu, czy przekłamanie program countera zwiększamy szansę, że program nie wykona niebezpiecznych operacji.

Inną techniką redundancji na poziomie softu jest przechowywanie krytycznych zmiennych w dwóch różnych sektorach pamięci. W jednym w postaci standardowej, a w drugim negację bitową. Przed użyciem zmiennej odczytujemy ją z obu sektorów i wykonujemy operację XOR. Jeżeli w wyniku nie otrzymamy 0xFFFF, zgłaszamy błąd.

Procesor nadzorujący

Omówię teraz kilka rozwiązań architektury systemu starających się spełnić wymagania dotyczące systemów bezpieczeństwa. Jedną z możliwości jest zastosowanie jednego procesora realizującego podstawowe funkcjonalności systemu i drugiego procesora działającego jako nadzorca sprawdzający, czy pierwszy procesor działa poprawnie. Jeżeli nadzorca wykryje jakiś błąd, może wprowadzić system w stan bezpieczny. Sam procesor podstawowy również może przejść w safe state.

Na przedstawionym schemacie warto zwrócić uwagę, że CPU2 korzysta z niezależnego sensora nie pozwalając w ten sposób, aby błędne odczyty były SPOF. Z kolei aby sam CPU2 nie był SPOF można skorzystać z kilku rozwiązań. W prostym przypadku jak na rysunku, gdzie sterujemy przekaźnikiem, zabezpieczeniem są jego właściwości fizyczne. W przypadku bardziej skomplikowanego mechanizmu safe state można dodać dodatkowy system diagnostyczny sprawdzający CPU2. Innym rozwiązaniem może być przekazanie informacji statusowej z CPU2 do CPU1 i decyzja o safe state na jej podstawie.

Dwa niezależne kanały

Inną koncepcją jest zastosowanie dwóch niezależnych kanałów realizujących to samo zadanie. Jak wspomniałem wcześniej, dobrze jeśli kanały są realizowane na różnych procesorach przez niezależne zespoły developerskie.

Rozwiązanie to może występować w kilku wariantach. Po pierwsze jeden z kanałów może pracować jako główny, a drugi jako zapasowy. W razie awarii może nastąpić zmiana. Zaletą takiego rozwiązania jest możliwość dalszego działania w przypadku błędu. Warto zastanowić się nad okresowymi zmianami głównego systemu, aby mieć pewność, że zapasowy system jest sprawny.

Drugim wariantem jest jednoczesna praca obu kanałów i porównywanie stanu pomiędzy nimi. System działa normalnie dopóki stany w obu kanałach są zgodne. W przypadku wykrycia niezgodności, następuje przejście w stan bezpieczny.

Jeszcze innym sposobem jest połączenie dwóch wcześniej omawianych wariantów wykorzystując cztery kanały, gdzie po 2 kanały działają na raz i monitorują nawzajem swój stan, a w przypadku błędu następuje przełączenie na pozostałe dwa kanały.

System głosowania

Kolejnym możliwym rozwiązaniem jest zastosowanie trzech lub więcej niezależnych kanałów i systemu głosowania wysyłającego na wyjście wartość wskazaną przez większość. W przypadku wykrycia, że jeden kanał głosuje inaczej, zostaje on odłączony.

Jedną z trudności w takim rozwiązaniu jest moduł głosujący, który może być SPOF. Innym problemem jest praca z wartościami float, czy sygnałami ciągłymi, gdzie porównanie jest utrudnione. Trzeba wtedy wprowadzić jakieś zakresy wartości i trudniej określić kiedy któryś kanał działa błędnie. Poza tym sterując sygnałami ciągłymi trzeba brać pod uwagę procesy przejściowe i stany nieustalone, które mogą różnić się pomiędzy implementacjami.

Podsumowanie

W tym artykule przedstawiłem pewne koncepcje wykorzystywane przy tworzeniu systemów bezpieczeństwa. Jak widać częstą praktyką jest dodawanie elementów nadzorujących lub dublujących podstawowe działanie systemu.  Projektując konkretny system należy zapoznać się z obowiązującymi normami z danego obszaru. Często sugerują lub narzucają one pewne rozwiązania architektoniczne.

Źródła

https://www.embedded.com/design/configurable-systems/4429867/2/Practical-tips-on-designing-safety-critical-software

https://www.embedded.com/design/prototyping-and-development/4006464/Architecture-of-safety-critical-systems