Kompilujemy wbudowaną aplikację C / C ++, która jest wdrażana w ekranowanym urządzeniu w środowisku bombardowanym promieniowaniem jonizującym . Używamy GCC i kompilacji krzyżowej dla ARM. Po wdrożeniu nasza aplikacja generuje niektóre błędne dane i ulega awarii częściej niż byśmy tego chcieli. Sprzęt jest przeznaczony dla tego środowiska, a nasza aplikacja działa na tej platformie od kilku lat.
Czy są jakieś zmiany, które możemy wprowadzić w naszym kodzie, lub ulepszenia czasu kompilacji, które można wprowadzić w celu zidentyfikowania / skorygowania błędów miękkich i uszkodzenia pamięci spowodowanych zakłóceniami pojedynczych zdarzeń ? Czy inni programiści odnieśli sukces w zmniejszaniu szkodliwych skutków błędów miękkich w długotrwałej aplikacji?
Odpowiedzi:
Pracując przez około 4-5 lat przy opracowywaniu oprogramowania / oprogramowania układowego i testowaniu środowiska zminiaturyzowanych satelitów *, chciałbym podzielić się tutaj swoimi doświadczeniami.
* ( zminiaturyzowane satelity są znacznie bardziej podatne na zakłócenia pojedynczych zdarzeń niż większe satelity ze względu na stosunkowo małe, ograniczone rozmiary komponentów elektronicznych )
Ta sytuacja jest zwykle obsługiwana zarówno na poziomie sprzętowym, jak i programowym. Tutaj, zgodnie z twoją prośbą, podzielę się tym, co możemy zrobić na poziomie oprogramowania.
... ... celem odzysku . Zapewnij możliwość aktualizacji / ponownej kompilacji / aktualizacji oprogramowania / oprogramowania układowego w prawdziwym środowisku. Jest to niemal niezbędna funkcja każdego oprogramowania / oprogramowania układowego w silnie zjonizowanym środowisku. Bez tego, to mogło mieć redundantny oprogramowania / sprzętu tyle, ile chcesz, ale w pewnym momencie, wszystkie są zamiar wysadzić. Więc przygotuj tę funkcję!
... minimalna wersja robocza ... Posiadaj responsywne, wiele kopii, minimalną wersję oprogramowania / oprogramowania w kodzie. To jest jak tryb awaryjny w systemie Windows. Zamiast mieć tylko jedną, w pełni funkcjonalną wersję oprogramowania, masz wiele kopii minimalnej wersji oprogramowania / oprogramowania układowego. Minimalna kopia ma zwykle znacznie mniejszy rozmiar niż kopia pełna i prawie zawsze ma tylko następujące dwie lub trzy funkcje:
... kopiuj ... gdzieś ... Masz gdzieś zbędne oprogramowanie / oprogramowanie.
Możesz, z redundantnym sprzętem lub bez niego, próbować mieć redundantne oprogramowanie / oprogramowanie w ARM uC. Zwykle odbywa się to poprzez posiadanie dwóch lub więcej identycznych programów / oprogramowania układowego pod oddzielnymi adresami, które wysyłają do siebie bicie serca - ale tylko jedno będzie aktywne jednocześnie. Jeśli wiadomo, że jedno lub więcej oprogramowania / oprogramowania wewnętrznego nie odpowiada, przełącz się na inne oprogramowanie / oprogramowanie wewnętrzne. Korzyścią wynikającą z zastosowania tego podejścia jest możliwość zastąpienia funkcjonalnego natychmiast po wystąpieniu błędu - bez kontaktu z jakimkolwiek zewnętrznym systemem / podmiotem odpowiedzialnym za wykrycie i naprawę błędu (w przypadku satelity jest to zwykle Centrum Kontroli Misji ( MCK)).
Ściśle mówiąc, bez zbędnego sprzętu wadą takiego działania jest to, że nie można wyeliminować wszystkich pojedynczych punktów awarii. Przynajmniej będziesz miał jeden punkt awarii, którym jest sam przełącznik (lub często początek kodu). Niemniej jednak, w przypadku urządzenia ograniczonego wielkością w wysoce zjonizowanym środowisku (takim jak satelity pico / femto), warto rozważyć zmniejszenie pojedynczego punktu awarii do jednego punktu bez dodatkowego sprzętu. Kiedyś fragment kodu do przełączania byłby z pewnością znacznie mniejszy niż kod dla całego programu - znacznie zmniejszając ryzyko dostania się do niego pojedynczego zdarzenia.
Ale jeśli tego nie robisz, powinieneś mieć co najmniej jedną kopię w swoim systemie zewnętrznym, która może wejść w kontakt z urządzeniem i zaktualizować oprogramowanie / oprogramowanie (w przypadku satelity jest to ponownie centrum kontroli misji).
... wykrywalna błędna sytuacja .. Błąd musi być wykrywalny , zwykle przez sprzętowy obwód korekcji / wykrywania błędu lub mały fragment kodu do korekcji / wykrywania błędu. Najlepiej jest umieścić taki kod mały, wielokrotny i niezależny od głównego oprogramowania / oprogramowania układowego. Jego głównym zadaniem jest tylko sprawdzanie / poprawianie. Jeśli obwód sprzętowy / oprogramowanie układowe jest niezawodny(na przykład, że jest bardziej hartowany promieniowaniem niż reszta - lub ma wiele obwodów / logiki), możesz rozważyć dokonanie z nim korekcji błędów. Ale jeśli nie jest, lepiej zrobić to jako wykrywanie błędów. Korekta może być wykonana przez zewnętrzny system / urządzenie. Do korekcji błędów można rozważyć użycie podstawowego algorytmu korekcji błędów, takiego jak Hamming / Golay23, ponieważ można je łatwiej zaimplementować zarówno w obwodzie / oprogramowaniu. Ale ostatecznie zależy to od zdolności twojego zespołu. Do wykrywania błędów zwykle stosuje się CRC.
... sprzęt wspierający odzyskiwanie Teraz dochodzi do najtrudniejszego aspektu tego problemu. Ostatecznie odzyskiwanie wymaga, aby sprzęt odpowiedzialny za odzyskiwanie był przynajmniej funkcjonalny. Jeśli sprzęt jest trwale uszkodzony (zwykle dzieje się po tym, jak jego całkowita dawka jonizująca osiągnie pewien poziom), wówczas (niestety) nie ma sposobu, aby oprogramowanie mogło pomóc w odzyskaniu. Dlatego sprzęt jest słusznie najważniejszy w przypadku urządzeń narażonych na wysoki poziom promieniowania (takich jak satelita).
Oprócz sugestii powyżej przewidywania błędu oprogramowania układowego z powodu zakłócenia pojedynczego zdarzenia, chciałbym również zasugerować, abyś miał:
Algorytm wykrywania błędów i / lub korekcji błędów w protokole komunikacyjnym między podsystemami. To kolejna niemal konieczność, aby uniknąć niekompletnych / złych sygnałów odbieranych z innego systemu
Filtruj odczyt ADC. Czy nie używać ADC odczyt bezpośrednio. Filtruj według mediany, średniej lub innych filtrów - nigdy nie ufaj pojedynczej wartości odczytu. Próbuj więcej, nie mniej - rozsądnie.
źródło
NASA ma artykuł na temat oprogramowania odpornego na promieniowanie . Opisuje trzy główne zadania:
Należy pamiętać, że szybkość skanowania pamięci powinna być wystarczająco częsta, aby rzadko występowały błędy wielobitowe, ponieważ większość pamięci ECC może odzyskać po błędach jednobitowych, a nie błędach wielobitowych.
Solidne odzyskiwanie po błędzie obejmuje transfer kontroli przepływu (zwykle ponowne uruchomienie procesu w punkcie poprzedzającym błąd), zwolnienie zasobów i przywracanie danych.
Ich głównym zaleceniem w zakresie przywracania danych jest unikanie ich konieczności, ponieważ dane pośrednie należy traktować jako tymczasowe, aby ponowne uruchomienie przed błędem również przywróciło dane do wiarygodnego stanu. To brzmi podobnie do pojęcia „transakcji” w bazach danych.
Omawiają techniki szczególnie odpowiednie dla języków obiektowych, takich jak C ++. Na przykład
Tak się składa, że NASA używa C ++ do dużych projektów, takich jak Mars Rover .
Unikali pewnych funkcji C ++, które mogłyby powodować problemy:
new
idelete
)new
aby uniknąć możliwości uszkodzenia sterty systemowej).źródło
Oto kilka myśli i pomysłów:
Korzystaj z ROM w bardziej kreatywny sposób.
Przechowuj wszystko, co możesz, w pamięci ROM. Zamiast obliczać rzeczy, przechowuj tabele przeglądowe w pamięci ROM. (Upewnij się, że Twój kompilator wyświetla tabele przeglądowe w sekcji tylko do odczytu! Wydrukuj adresy pamięci w czasie wykonywania, aby to sprawdzić!) Przechowuj tabelę wektorów przerwań w pamięci ROM. Oczywiście, uruchom kilka testów, aby zobaczyć, jak niezawodna jest twoja pamięć ROM w porównaniu do pamięci RAM.
Użyj swojej najlepszej pamięci RAM dla stosu.
Jednostki SEU na stosie są prawdopodobnie najbardziej prawdopodobnym źródłem awarii, ponieważ tam zwykle występują takie rzeczy, jak zmienne indeksowe, zmienne statusu, adresy zwrotne i wskaźniki różnego rodzaju.
Wdrożenie procedur timera tykania i watchdoga.
Możesz uruchomić procedurę „sprawdzania rozsądku” przy każdym tyknięciu zegara, a także procedurę kontrolną do obsługi blokowania systemu. Twój główny kod może również okresowo zwiększać licznik wskazujący postęp, a procedura sprawdzania czystości może zapewnić, że tak się stało.
Zaimplementuj kody korekcji błędów w oprogramowaniu.
Możesz dodać redundancję do swoich danych, aby móc wykryć i / lub poprawić błędy. To wydłuży czas przetwarzania, potencjalnie pozostawiając procesor narażony na promieniowanie przez dłuższy czas, zwiększając w ten sposób ryzyko błędów, więc musisz rozważyć kompromis.
Pamiętaj o pamięci podręcznej.
Sprawdź rozmiary pamięci podręcznej procesora. Dane, do których ostatnio uzyskano dostęp lub które zmodyfikowano, prawdopodobnie znajdą się w pamięci podręcznej. Uważam, że możesz wyłączyć przynajmniej niektóre pamięci podręczne (przy dużym koszcie wydajności); powinieneś spróbować, aby zobaczyć, jak podatne są pamięci podręczne na SEU. Jeśli pamięci podręczne są trudniejsze niż pamięć RAM, możesz regularnie odczytywać i ponownie zapisywać krytyczne dane, aby upewnić się, że pozostają one w pamięci podręcznej i przywracają pamięć RAM z powrotem do linii.
Używaj sprytnie procedur obsługi błędów stron.
Jeśli zaznaczysz stronę pamięci jako nieobecną, procesor spowoduje błąd strony podczas próby uzyskania do niej dostępu. Można utworzyć moduł obsługi błędów strony, który sprawdza niektóre elementy przed obsłużeniem żądania odczytu. (Systemy operacyjne PC używają tego do przezroczystego ładowania stron, które zostały zamienione na dysk).
Używaj języka asemblera do krytycznych rzeczy (które mogą być wszystkim).
Dzięki językowi asemblera wiesz, co jest w rejestrach, a co w pamięci RAM; ty wiesz jakie tabele specjalny RAM CPU korzysta i można projektować rzeczy w okrężny sposób, aby zachować swoje ryzyko w dół.
Służy
objdump
do przeglądania wygenerowanego języka asemblera i obliczania ilości kodu, jaką zajmuje każda z procedur.Jeśli używasz dużego systemu operacyjnego, takiego jak Linux, to prosisz o kłopoty; jest tyle złożoności i tylu rzeczy do zrobienia.
Pamiętaj, że to gra prawdopodobieństwa.
Komentator powiedział
Chociaż jest to prawda, szanse na błędy w (powiedzmy) 100 bajtach kodu i danych wymaganych do prawidłowego działania procedury sprawdzającej są znacznie mniejsze niż prawdopodobieństwo wystąpienia błędów w innym miejscu. Jeśli twój ROM jest dość niezawodny i prawie cały kod / dane faktycznie znajdują się w ROM, twoje szanse są jeszcze większe.
Użyj nadmiarowego sprzętu.
Użyj 2 lub więcej identycznych konfiguracji sprzętowych z identycznym kodem. Jeśli wyniki różnią się, należy uruchomić reset. Na 3 lub więcej urządzeniach możesz użyć systemu „głosowania”, aby spróbować ustalić, które zostało naruszone.
źródło
Być może zainteresuje Cię także bogata literatura na temat algorytmicznej odporności na uszkodzenia. Obejmuje to stare przypisanie: Napisz rodzaj, który poprawnie sortuje dane wejściowe, gdy nie powiedzie się stała liczba porównań (lub, nieco bardziej zła wersja, gdy asymptotyczna liczba nieudanych porównań skaluje się jak w
log(n)
przypadkun
porównań).Miejscem do rozpoczęcia czytania jest artykuł Huanga i Abrahama z 1984 r. „ Tolerancja błędów oparta na algorytmach dla operacji matrycowych ”. Ich pomysł jest niejasno podobny do homomorficznego szyfrowanego obliczenia (ale tak naprawdę nie jest taki sam, ponieważ próbują wykryć / skorygować błąd na poziomie operacyjnym).
Nowszym potomkiem tego artykułu jest Bosilca, Delmas, Dongarra i Langou „ Tolerancja błędów oparta na algorytmach stosowana do obliczeń o wysokiej wydajności ”.
źródło
Pisanie kodu dla środowisk radioaktywnych nie różni się niczym od pisania kodu dla aplikacji o kluczowym znaczeniu.
Oprócz tego, co już wspomniano, oto kilka różnych wskazówek:
WAŻNE: Musisz zapewnić integralność wewnętrznych rejestrów MCU. Wszystkie rejestry kontroli i statusu urządzeń peryferyjnych, które można zapisywać, mogą znajdować się w pamięci RAM i dlatego są podatne na ataki.
Aby uchronić się przed uszkodzeniem rejestrów, najlepiej wybrać mikrokontroler z wbudowanymi funkcjami rejestrów „jednokrotnego zapisu”. Ponadto należy przechowywać wartości domyślne wszystkich rejestrów sprzętowych w NVM i regularnie kopiować te wartości do rejestrów. W ten sam sposób możesz zapewnić integralność ważnych zmiennych.
Uwaga: zawsze używaj programowania obronnego. Oznacza to, że musisz skonfigurować wszystkie rejestry w MCU, a nie tylko te używane przez aplikację. Nie chcesz, aby przypadkowe urządzenia peryferyjne nagle się obudziły.
Istnieją różne metody sprawdzania błędów w pamięci RAM lub NVM: sumy kontrolne, „wzorce kroczące”, oprogramowanie ECC itp. Obecnie najlepszym rozwiązaniem jest nieużywanie żadnego z nich, ale użycie MCU z wbudowanym ECC i podobne kontrole. Ponieważ robienie tego w oprogramowaniu jest skomplikowane, a samo sprawdzenie błędów może w związku z tym powodować błędy i nieoczekiwane problemy.
Zrozum i przyjmij koncepcję programowania obronnego. Oznacza to, że Twój program musi obsługiwać wszystkie możliwe przypadki, nawet te, które nie mogą wystąpić w teorii. Przykłady .
Wysokiej jakości oprogramowanie krytyczne dla misji wykrywa jak najwięcej błędów, a następnie ignoruje je w bezpieczny sposób.
WAŻNE: Nie wdrażaj żadnego polegania na domyślnych wartościach statycznych zmiennych czasu przechowywania. Oznacza to, że nie ufaj domyślnej zawartości
.data
lub.bss
. Od momentu inicjalizacji do momentu, w którym zmienna jest rzeczywiście używana, może upłynąć dowolna ilość czasu, może być dużo czasu na uszkodzenie pamięci RAM. Zamiast tego napisz program, aby wszystkie takie zmienne były ustawiane z NVM w czasie wykonywania, tuż przed pierwszym użyciem takiej zmiennej.W praktyce oznacza to, że jeśli zmienna zostanie zadeklarowana w zakresie pliku lub jako
static
, nie należy nigdy używać jej=
do inicjalizacji (lub można, ale jest to bezcelowe, ponieważ nie można w żaden sposób polegać na wartości). Zawsze ustaw go w czasie wykonywania, tuż przed użyciem. Jeśli możliwe jest wielokrotne aktualizowanie takich zmiennych z NVM, zrób to.Podobnie w C ++, nie polegaj na konstruktorach dla zmiennych czasu przechowywania statycznego. Poproś konstruktorów o wywołanie publicznej procedury „konfiguracji”, którą możesz wywołać później w czasie wykonywania, bezpośrednio z aplikacji wywołującej.
Jeśli to możliwe, usuń kod startowy „kopiuj” , który całkowicie inicjuje
.data
i.bss
(i wywołuje konstruktory C ++), aby uzyskać błędy linkera, jeśli napiszesz na nim kod. Wiele kompilatorów ma możliwość pominięcia tego, zwykle nazywanego „minimalnym / szybkim uruchomieniem” lub podobnym.Oznacza to, że wszelkie biblioteki zewnętrzne muszą zostać sprawdzone, aby nie zawierały takich zależności.
Zaimplementuj i zdefiniuj bezpieczny stan programu, do którego powrócisz w przypadku błędów krytycznych.
źródło
TRUE
zrównanie z0xffffffff
użyciemPOPCNT
progu.%01010101010101010101010101010101
, XOR, a następnie POPCNT?.text
sekcji, zmieniając kod operacyjny lub podobny.Możliwe jest użycie C do pisania programów, które zachowują się solidnie w takich środowiskach, ale tylko wtedy, gdy większość form optymalizacji kompilatora jest wyłączona. Kompilatory optymalizujące zostały zaprojektowane w celu zastąpienia wielu pozornie redundantnych wzorców kodowania wzorcami „bardziej wydajnymi” i mogą nie mieć pojęcia, że programista testuje,
x==42
gdy kompilator wie, że nie ma innego sposobu,x
aby zatrzymać coś innego, ponieważ programista chce temu zapobiec wykonanie określonego kodu zx
zachowaniem jakiejś innej wartości - nawet w przypadkach, w których jedynym sposobem na utrzymanie tej wartości byłby, gdyby system otrzymał jakąś usterkę elektryczną.Deklarowanie zmiennych jako
volatile
często pomocne, ale może nie być panaceum. Szczególnie ważne jest, aby pamiętać, że bezpieczne kodowanie często wymaga, aby niebezpieczne operacje posiadały blokady sprzętowe, które wymagają wielu kroków do aktywacji, i aby kod był zapisywany przy użyciu wzorca:Jeśli kompilator tłumaczy kod w stosunkowo dosłowny sposób i jeśli wszystkie sprawdzenia stanu systemu są powtarzane po
prepare_for_activation()
, system może być odporny na prawie każde możliwe zdarzenie pojedynczej usterki, nawet te, które arbitralnie uszkodzą licznik programu i stos. Jeśli usterka wystąpi tuż po wywołaniuprepare_for_activation()
, oznacza to, że aktywacja byłaby odpowiednia (ponieważ nie było innego powodu,prepare_for_activation()
który zostałby wywołany przed usterką). Jeśli usterka spowodujeprepare_for_activation()
nieprawidłowe dotarcie kodu , ale nie będzie żadnych następnych zdarzeń usterki, kod nie będzie mógł później dotrzećtrigger_activation()
bez przejścia sprawdzania poprawności lub wywołania anulowania_preparacji w pierwszej kolejności [jeśli stos zostanie uszkodzony, wykonanie może przejść do określonego miejsca przed chwilątrigger_activation()
po kontekście, który wywołałprepare_for_activation()
zwraca, ale wywołanie docancel_preparations()
miałoby miejsce między wywołaniami doprepare_for_activation()
itrigger_activation()
, co uczyniłoby to drugie nieszkodliwym.Taki kod może być bezpieczny w tradycyjnym C, ale nie w nowoczesnych kompilatorach C. Takie kompilatory mogą być bardzo niebezpieczne w tego rodzaju środowisku, ponieważ agresywne starają się zawierać tylko kod, który będzie odpowiedni w sytuacjach, które mogłyby wystąpić za pomocą dobrze zdefiniowanego mechanizmu i których wynikające konsekwencje byłyby również dobrze określone. Kod, którego celem byłoby wykrywanie i usuwanie awarii po awarii, w niektórych przypadkach może pogorszyć sytuację. Jeśli kompilator ustali, że próba odzyskania w niektórych przypadkach wywołałaby niezdefiniowane zachowanie, może wywnioskować, że warunki, które wymagałyby takiego odzyskania w takich przypadkach, nie mogą wystąpić, eliminując w ten sposób kod, który by je sprawdził.
źródło
-O0
przełącznika? GCC zrobi wiele dziwnych rzeczy, jeśli dasz mu na to zgodę , ale jeśli poprosisz, aby tego nie robił, ogólnie może być dość dosłowny.-O2
.-O0
jest to zły pomysł, jest to, że emituje on znacznie więcej bezużytecznych instrukcji. Przykład: wywołanie bez wstawiania zawiera instrukcje dotyczące zapisywania rejestrów, wykonywania połączenia, przywracania rejestrów. Wszystko to może zawieść. Instrukcja, której nie ma, nie może zawieść.-O0
którego zły pomysł: ma tendencję do przechowywania zmiennych w pamięci zamiast w rejestrze. Teraz nie jest pewne, czy pamięć jest bardziej podatna na SEU, ale dane w locie są bardziej podatne niż dane w spoczynku. Należy unikać niepotrzebnego przenoszenia danych i-O2
pomaga w tym.v1=v2+0xCAFEBABE
I wszystkie aktualizacje tych dwóch zmiennych zostały wykonane ...To niezwykle szeroki temat. Zasadniczo nie można tak naprawdę odzyskać sprawności po uszkodzeniu pamięci, ale można przynajmniej spróbować szybko zakończyć się niepowodzeniem . Oto kilka technik, których możesz użyć:
stałe dane kontrolne . Jeśli masz jakieś dane konfiguracyjne, które pozostają stałe przez długi czas (w tym skonfigurowane rejestry sprzętowe), oblicz jego sumę kontrolną przy inicjalizacji i okresowo ją weryfikuj. Kiedy zobaczysz niedopasowanie, czas ponownie zainicjować lub zresetować.
przechowuj zmienne z redundancją . Jeśli masz ważną zmienną
x
, napisać swoją wartośćx1
,x2
ax3
i odczytać go jako(x1 == x2) ? x2 : x3
.wdrożyć monitorowanie przepływu programu . XOR globalna flaga o unikalnej wartości w ważnych funkcjach / gałęziach wywoływanych z głównej pętli. Uruchomienie programu w środowisku wolnym od promieniowania z niemal 100% pokryciem testowym powinno dać ci listę dopuszczalnych wartości flagi na końcu cyklu. Zresetuj, jeśli zobaczysz odchylenia.
monitorować wskaźnik stosu . Na początku głównej pętli porównaj wskaźnik stosu z jego oczekiwaną wartością. Resetuj po odchyleniu.
źródło
To, co może ci pomóc, to strażnik . Strażnicy byli szeroko wykorzystywani w komputerach przemysłowych w latach 80. Awarie sprzętowe były wtedy znacznie częstsze - inna odpowiedź dotyczy również tego okresu.
Watchdog to połączona funkcja sprzętowo-programowa. Sprzęt jest prostym licznikiem, który odlicza od liczby (powiedzmy 1023) do zera. Można zastosować TTL lub inną logikę.
Oprogramowanie zostało tak zaprojektowane, aby jedna procedura monitorowała prawidłowe działanie wszystkich niezbędnych systemów. Jeśli ta procedura zakończy się poprawnie = znajdzie komputer działający poprawnie, ustawi licznik z powrotem na 1023.
Ogólny projekt jest taki, że w normalnych okolicznościach oprogramowanie zapobiega zerowaniu licznika sprzętowego. W przypadku, gdy licznik osiągnie zero, sprzęt licznika wykonuje swoje jedyne zadanie i resetuje cały system. Z perspektywy licznika zero wynosi 1024, a licznik kontynuuje odliczanie ponownie.
Ten watchdog zapewnia, że podłączony komputer zostanie zrestartowany w wielu, wielu przypadkach awarii. Muszę przyznać, że nie znam sprzętu, który jest w stanie wykonać taką funkcję na dzisiejszych komputerach. Interfejsy do zewnętrznego sprzętu są teraz o wiele bardziej złożone niż kiedyś.
Nieodłączną wadą watchdoga jest to, że system nie jest dostępny od momentu awarii, aż licznik watchdoga osiągnie zero + czas ponownego uruchomienia. Chociaż czas ten jest na ogół znacznie krótszy niż jakakolwiek interwencja zewnętrzna lub ludzka, obsługiwane urządzenia będą musiały być w stanie działać bez kontroli komputera w tym czasie.
źródło
Ta odpowiedź zakłada, że obawiasz się, że system działa poprawnie, oprócz tego, że jest to system o minimalnym koszcie lub szybki; większość osób bawiących się rzeczami radioaktywnymi ceni poprawność / bezpieczeństwo w stosunku do prędkości / kosztów
Kilka osób zasugerowało zmiany sprzętowe, które możesz wprowadzić (dobrze - w odpowiedziach jest już wiele dobrych rzeczy i nie zamierzam powtarzać wszystkiego), a inni zasugerowali nadmiarowość (co do zasady świetne), ale nie sądzę ktoś sugerował, jak ta nadmiarowość może działać w praktyce. Jak przestawić się na awarię? Skąd wiesz, że coś poszło nie tak? Wiele technologii działa na zasadzie, że wszystko będzie działać, a porażka jest więc trudną sprawą. Jednak niektóre technologie przetwarzania rozproszonego zaprojektowane na skalę oczekują awarii (w końcu przy wystarczającej skali, awaria jednego z wielu węzłów jest nieunikniona w przypadku dowolnego MTBF dla jednego węzła); możesz wykorzystać to dla swojego środowiska.
Oto kilka pomysłów:
Upewnij się, że cały sprzęt jest replikowany
n
razy (gdzien
jest większy niż 2, a najlepiej nieparzysty) i że każdy element sprzętowy może komunikować się ze sobą. Ethernet jest jednym oczywistym sposobem, aby to zrobić, ale istnieje wiele innych, znacznie prostszych tras, które zapewniłyby lepszą ochronę (np. CAN). Minimalizuj typowe komponenty (nawet zasilacze). Może to na przykład oznaczać próbkowanie danych wejściowych ADC w wielu miejscach.Upewnij się, że stan aplikacji znajduje się w jednym miejscu, np. W maszynie skończonej. Może to być całkowicie pamięć RAM, ale nie wyklucza stabilnego przechowywania. Będzie zatem przechowywany w kilku miejscach.
Przyjęcie protokołu kworum dla zmian stanu. Zobacz na przykład RAFT . Podczas pracy w C ++ istnieją do tego dobrze znane biblioteki. Zmiany w FSM zostaną wprowadzone tylko za zgodą większości węzłów. Skorzystaj ze znanej dobrej biblioteki dla stosu protokołów i protokołu kworum, a nie samodzielnie rozwijaj bibliotekę, albo cała Twoja dobra praca nad redundancją zostanie zmarnowana, gdy protokół kworum się rozłączy.
Upewnij się, że suma kontrolna (np. CRC / SHA) twojego FSM, i przechowuj CRC / SHA w samym FSM (jak również przesyłaj w wiadomości i sprawdzaj same wiadomości). Poproś węzły, aby regularnie sprawdzały swój FSM pod kątem tej sumy kontrolnej, sumy kontrolnej wiadomości przychodzących i sprawdzały, czy ich suma kontrolna odpowiada sumie kontrolnej kworum.
Zbuduj w systemie jak najwięcej innych wewnętrznych kontroli, dzięki czemu węzły, które wykryją swój własny restart, uruchamiają się ponownie (jest to lepsze niż kontynuowanie połowy pracy, pod warunkiem, że masz wystarczającą liczbę węzłów). Spróbuj zrezygnować z czystego usuwania się z kworum podczas ponownego uruchamiania na wypadek, gdyby nie pojawiły się ponownie. Po ponownym uruchomieniu uruchom sumę kontrolną obrazu oprogramowania (i wszystkiego, co ładują) i wykonaj pełny test pamięci RAM przed ponownym wprowadzeniem się do kworum.
Do obsługi używaj sprzętu, ale rób to ostrożnie. Możesz na przykład uzyskać pamięć RAM ECC i regularnie ją czytać / zapisywać, aby poprawić błędy ECC (i panikować, jeśli błędu nie da się naprawić). Jednak (z pamięci) statyczna pamięć RAM jest o wiele bardziej tolerancyjna na promieniowanie jonizujące niż pamięć DRAM, więc może być lepiej zamiast tego użyć statycznej pamięci DRAM. Zobacz także pierwszy punkt w „rzeczach, których nie zrobiłbym”.
Załóżmy, że masz 1% szansy na awarię dowolnego węzła w ciągu jednego dnia, i udawajmy, że możesz całkowicie uniezależnić awarie. Przy 5 węzłach będziesz potrzebować trzech, aby zawieść w ciągu jednego dnia, co daje 0,00001% szansy. Z więcej, cóż, masz pomysł.
Czego bym nie zrobił:
Nie doceniaj wartości braku problemu na początek. O ile waga nie stanowi problemu, duży blok metalu wokół twojego urządzenia będzie znacznie tańszym i bardziej niezawodnym rozwiązaniem, niż może wymyślić zespół programistów. Ditto optyczne sprzężenie sygnałów wejściowych EMI jest problemem itp. Niezależnie od tego, staraj się podczas pozyskiwania komponentów, aby pozyskiwać te, które najlepiej pasują do promieniowania jonizującego.
Rzuć własne algorytmy . Ludzie robili to wcześniej. Skorzystaj z ich pracy. Tolerancja błędów i algorytmy rozproszone są trudne. W miarę możliwości korzystaj z pracy innych osób.
Używaj skomplikowanych ustawień kompilatora w naiwnej nadziei, że wykryjesz więcej awarii. Jeśli masz szczęście, możesz wykryć więcej awarii. Bardziej prawdopodobne jest, że użyjesz ścieżki kodu w kompilatorze, który został mniej przetestowany, szczególnie jeśli sam go rzuciłeś.
Używaj technik, które nie zostały przetestowane w twoim środowisku. Większość osób piszących oprogramowanie o wysokiej dostępności musi symulować tryby awarii, aby sprawdzić, czy ich HA działa poprawnie, i w rezultacie pomija wiele trybów awarii. Jesteś w „szczęśliwej” sytuacji, gdy często popełniają awarie na żądanie. Przetestuj więc każdą technikę i upewnij się, że jej zastosowanie poprawia MTBF o kwotę przekraczającą złożoność jej wprowadzenia (ze złożonością pojawiają się błędy). Szczególnie stosuj to do moich rad dotyczących algorytmów kworum itp.
źródło
Skoro konkretnie pytasz o rozwiązania programowe i używasz C ++, dlaczego nie użyć przeciążenia operatora, aby stworzyć własne, bezpieczne typy danych? Na przykład:
Zamiast korzystać
uint32_t
(idouble
,int64_t
etc), tworzyć własneSAFE_uint32_t
która zawiera wielokrotność (minimum 3) z uint32_t. Przeciąż wszystkie operacje, które chcesz wykonać (* + - / << >> = ==! = Itd.) I spraw, aby przeciążone operacje wykonały niezależnie dla każdej wartości wewnętrznej, tzn. Nie rób tego ani razu i skopiuj wynik. Zarówno przed, jak i po, sprawdź, czy wszystkie wartości wewnętrzne są zgodne. Jeśli wartości się nie zgadzają, możesz zaktualizować niewłaściwy do najbardziej powszechnego. Jeśli nie ma najczęstszej wartości, możesz bezpiecznie powiadomić o wystąpieniu błędu.W ten sposób nie ma znaczenia, czy nastąpi uszkodzenie w ALU, rejestrach, pamięci RAM lub w autobusie, nadal będziesz mieć wiele prób i bardzo duże szanse na złapanie błędów. Należy jednak pamiętać, że działa to tylko w przypadku zmiennych, które można zastąpić - na przykład wskaźnik stosu nadal będzie podatny.
Historia poboczna: napotkałem podobny problem, również na starym układzie ARM. Okazało się, że jest to zestaw narzędzi, który korzystał ze starej wersji GCC, która wraz z konkretnym układem, którego użyliśmy, spowodowała błąd w niektórych przypadkach krawędzi, który (czasami) uszkodziłby wartości przekazywane do funkcji. Upewnij się, że twoje urządzenie nie ma żadnych problemów przed obwinianiem go o aktywność radiową, i tak, czasami jest to błąd kompilatora =)
źródło
Oświadczenie: Nie jestem specjalistą od radioaktywności ani nie pracowałem dla tego rodzaju aplikacji. Ale pracowałem nad miękkimi błędami i redundancją w celu długoterminowej archiwizacji krytycznych danych, która jest nieco powiązana (ten sam problem, różne cele).
Moim zdaniem główny problem z radioaktywnością polega na tym, że radioaktywność może zmieniać bity, a zatem radioaktywność może / będzie manipulować każdą pamięcią cyfrową . Błędy te są zwykle nazywane błędami miękkimi , gniciem bitów itp.
Pytanie brzmi zatem: jak niezawodnie obliczyć, kiedy twoja pamięć jest zawodna?
Aby znacznie zmniejszyć częstość błędów miękkich (kosztem narzutu obliczeniowego, ponieważ będą to głównie rozwiązania programowe), możesz:
polegać na starym dobrym schemacie redundancji , a dokładniej na bardziej wydajnych kodach korygujących błędy (ten sam cel, ale z bardziej inteligentnymi algorytmami, dzięki czemu można odzyskać więcej bitów przy mniejszej redundancji). Czasami jest to (błędnie) nazywane sumowaniem kontrolnym. Dzięki tego rodzaju rozwiązaniu będziesz musiał w dowolnym momencie zapisać pełny stan swojego programu w zmiennej głównej / klasie (lub strukturze?), Obliczyć ECC i sprawdzić, czy ECC jest poprawny przed zrobieniem czegokolwiek, a jeśli nie, napraw pola. To rozwiązanie nie gwarantuje jednak, że twoje oprogramowanie może działać (po prostu, że będzie działało poprawnie, gdy będzie to możliwe, lub przestanie działać, jeśli nie, ponieważ ECC może powiedzieć ci, czy coś jest nie tak, iw takim przypadku możesz zatrzymać swoje oprogramowanie, abyś mógł nie otrzymuj fałszywych wyników).
lub możesz użyć elastycznych algorytmicznych struktur danych, co gwarantuje, do pewnego stopnia, że Twój program będzie nadal dawał prawidłowe wyniki nawet w przypadku wystąpienia błędów miękkich. Algorytmy te można postrzegać jako połączenie typowych struktur algorytmicznych z natywnymi domieszkowanymi schematami ECC, ale jest to o wiele bardziej odporne, ponieważ schemat odporności jest ściśle związany ze strukturą, dzięki czemu nie trzeba kodować dodatkowych procedur aby sprawdzić ECC, i zwykle są one znacznie szybsze. Struktury te zapewniają sposób, że Twój program będzie działał w każdych warunkach, aż do teoretycznej granicy błędów miękkich. Możesz także łączyć te elastyczne struktury ze schematem redundancji / ECC dla dodatkowego bezpieczeństwa (lub kodować najważniejsze struktury danych jako odporne, a resztę - dane jednorazowe, które możesz ponownie obliczyć z głównych struktur danych,
Jeśli jesteś zainteresowany elastycznymi strukturami danych (które są najnowszą, ale ekscytującą, nową dziedziną w zakresie algorytmiki i inżynierii redundancji), radzę przeczytać następujące dokumenty:
Wprowadzenie do struktur danych o odpornych algorytmach autorstwa Giuseppe F.Italiano, Universita di Roma „Tor Vergata”
Christiano, P., Demaine, ED i Kishore, S. (2011). Bezstratne, odporne na uszkodzenia struktury danych z dodatkowym obciążeniem. W Algorytmach i strukturach danych (str. 243–254). Springer Berlin Heidelberg.
Ferraro-Petrillo, U., Grandoni, F. i Italiano, GF (2013). Struktury danych odporne na uszkodzenia pamięci: eksperymentalne badanie słowników. Journal of Experimental Algorytmics (JEA), 18, 1-6.
Italiano, GF (2010). Odporne algorytmy i struktury danych. W Algorytmach i złożoności (s. 13–24). Springer Berlin Heidelberg.
Jeśli chcesz dowiedzieć się więcej na temat odpornych struktur danych, możesz sprawdzić prace Giuseppe F. Italiano (i przejść przez referencje) oraz model Faulty-RAM (wprowadzony w Finocchi i in. 2005; Finocchi i Italiano 2008).
/ EDIT: Zilustrowałem zapobieganie / odzyskiwanie po błędach programowych głównie dla pamięci RAM i przechowywania danych, ale nie mówiłem o błędach obliczeniowych (CPU) . Inne odpowiedzi wskazywały już na stosowanie transakcji atomowych, takich jak w bazach danych, dlatego zaproponuję inny, prostszy schemat: redundancja i głosowanie większościowe .
Chodzi o to, że po prostu wykonujesz x razy to samo obliczenie dla każdego obliczenia, które musisz wykonać, i zapisujesz wynik w x różnych zmiennych (przy x> = 3). Następnie możesz porównać swoje zmienne x :
Ten schemat redundancji jest bardzo szybki w porównaniu do ECC (praktycznie O (1)) i zapewnia wyraźny sygnał, gdy potrzebujesz zabezpieczenia przed awarią . Większość głosów jest również (prawie) gwarantowana, aby nigdy nie wytwarzać uszkodzonych danych wyjściowych, a także aby odzyskać po drobnych błędach obliczeniowych , ponieważ prawdopodobieństwo, że obliczenia x dają takie same dane wyjściowe, jest nieskończenie małe (ponieważ istnieje ogromna liczba możliwych wyników, prawie niemożliwe jest losowo uzyskaj 3 razy takie same, jeszcze mniejsze szanse, jeśli x> 3).
Tak więc przy większości głosów jesteś bezpieczny przed uszkodzonym wyjściem, a dzięki redundancji x == 3 możesz odzyskać 1 błąd (przy x == 4 można odzyskać 2 błędy itp. - dokładne równanie to
nb_error_recoverable == (x-2)
gdzie x jest liczbą powtórzeń obliczeń, ponieważ potrzebujesz co najmniej 2 zgodnych obliczeń, aby odzyskać większość głosów).Wadą jest to, że musisz obliczyć x razy zamiast raz, więc masz dodatkowy koszt obliczeniowy, ale liniowa złożoność tak asymptotycznie, że nie tracisz wiele za korzyści, które zyskujesz. Szybkim sposobem na głosowanie większością głosów jest obliczenie trybu na tablicy, ale można również użyć filtra mediany.
Ponadto, jeśli chcesz mieć pewność, że obliczenia są przeprowadzane poprawnie, jeśli możesz stworzyć własny sprzęt, możesz zbudować urządzenie z x procesorami i połączyć system tak, aby obliczenia były automatycznie duplikowane na x procesorach z większością głosów na końcu mechanicznie (na przykład za pomocą bramek AND / OR). Jest to często realizowane w samolotach i urządzeniach o kluczowym znaczeniu (patrz potrójna redundancja modułowa ). W ten sposób nie będziesz mieć żadnych narzutów obliczeniowych (ponieważ dodatkowe obliczenia będą wykonywane równolegle) i masz kolejną warstwę ochrony przed błędami miękkimi (ponieważ duplikacja obliczeń i głosowanie większościowe będą zarządzane bezpośrednio przez sprzęt, a nie przez oprogramowanie - które może łatwiej ulec uszkodzeniu, ponieważ program jest po prostu bitami zapisanymi w pamięci ...).
źródło
Jedna kwestia, o której nikt chyba nie wspomniał. Mówisz, że rozwijasz się w GCC i kompilujesz na ARM. Skąd wiesz, że nie masz kodu, który zakłada założenia dotyczące wolnej pamięci RAM, rozmiaru liczb całkowitych, rozmiaru wskaźnika, ile czasu zajmuje wykonanie określonej operacji, jak długo system będzie działał w sposób ciągły, czy różnych podobnych rzeczy? To bardzo częsty problem.
Odpowiedzią są zwykle zautomatyzowane testy jednostkowe. Napisz wiązki testowe, które ćwiczą kod w systemie programistycznym, a następnie uruchom te same wiązki testowe w systemie docelowym. Poszukaj różnic!
Sprawdź także erratę na swoim urządzeniu osadzonym. Może się okazać, że jest coś takiego: „nie rób tego, bo się zawiesi, więc włącz tę opcję kompilatora, a kompilator obejdzie to”.
Krótko mówiąc, najprawdopodobniej źródłem awarii są błędy w kodzie. Dopóki nie upewnisz się, że tak nie jest, nie martw się (jeszcze) o więcej ezoterycznych trybów awarii.
źródło
Chcesz ponad 3 niewolników z urządzeniem nadrzędnym poza środowiskiem promieniowania. Wszystkie wejścia / wyjścia przechodzą przez moduł główny, który zawiera mechanizm głosowania i / lub ponawiania. Niewolnicy muszą mieć każdego strażnika sprzętowego, a wezwanie do ich uderzenia powinno być otoczone CRC lub podobnymi, aby zmniejszyć prawdopodobieństwo mimowolnego uderzenia. Podbijanie powinno być kontrolowane przez urządzenie nadrzędne, więc utracone połączenie z urządzeniem nadrzędnym oznacza ponowne uruchomienie w ciągu kilku sekund.
Jedną z zalet tego rozwiązania jest to, że można używać tego samego interfejsu API dla urządzenia master, jak i urządzeń slave, dzięki czemu nadmiarowość staje się funkcją przezroczystą.
Edycja: Na podstawie komentarzy uważam za konieczne wyjaśnienie „pomysłu CRC”. Prawdopodobieństwo, że Slave podbił swój własny organ nadzorczy, jest bliskie zeru, jeśli otoczysz wybrzuszenie CRC lub podsumujesz kontrole losowych danych z mastera. Te losowe dane są wysyłane z mastera tylko wtedy, gdy kontrolowany slave jest wyrównany z innymi. Losowe dane i CRC / skrót są natychmiast usuwane po każdym uderzeniu. Częstotliwość uderzeń master-slave powinna być większa niż dwukrotnie limit czasu watchdoga. Dane wysyłane z urządzenia nadrzędnego są generowane jednoznacznie za każdym razem.
źródło
Co powiesz na uruchamianie wielu wystąpień aplikacji? Jeśli awarie są spowodowane przypadkowymi zmianami bitów pamięci, istnieje prawdopodobieństwo, że niektóre instancje Twojej aplikacji przejdą i wygenerują dokładne wyniki. Prawdopodobnie dość łatwo (dla kogoś z wykształceniem statystycznym) obliczyć, ile wystąpień potrzebujesz, biorąc pod uwagę prawdopodobieństwo bit flop, aby osiągnąć tak mały ogólny błąd, jak chcesz.
źródło
To, o co pytasz, jest dość złożonym tematem - trudno na nie odpowiedzieć. Inne odpowiedzi są w porządku, ale obejmowały tylko niewielką część wszystkich rzeczy, które musisz zrobić.
Jak widać w komentarzach , nie można naprawić problemów sprzętowych w 100%, jednak z dużym prawdopodobieństwem można je zmniejszyć lub złapać za pomocą różnych technik.
Na twoim miejscu stworzyłbym oprogramowanie o najwyższym poziomie nienaruszalności bezpieczeństwa (SIL-4). Pobierz dokument IEC 61513 (dla przemysłu jądrowego) i postępuj zgodnie z nim.
źródło
Ktoś wspomniał o stosowaniu wolniejszych czipów, aby jony nie były tak łatwo odwracane. W podobny sposób być może użyj specjalnego procesora / pamięci RAM, który faktycznie używa wielu bitów do przechowywania jednego bitu. W ten sposób zapewnia się sprzętową odporność na uszkodzenia, ponieważ bardzo mało prawdopodobne jest, aby wszystkie bity zostały odwrócone. Tak więc 1 = 1111, ale musiałby zostać trafiony 4 razy, aby faktycznie przerzucić. (4 może być złą liczbą, ponieważ jeśli 2 bity zostaną odwrócone, to już jest niejednoznaczna). Więc jeśli wybierzesz 8, otrzymasz 8 razy mniej pamięci RAM i nieco krótszy czas dostępu, ale znacznie bardziej niezawodną reprezentację danych. Prawdopodobnie można to zrobić zarówno na poziomie oprogramowania za pomocą wyspecjalizowanego kompilatora (alokacja x więcej miejsca na wszystko), jak i implementacji językowej (napisz opakowania dla struktur danych, które alokują to w ten sposób).
źródło
Być może pomogłoby wiedzieć, czy oznacza to, że sprzęt jest „zaprojektowany dla tego środowiska”. Jak to poprawia i / lub wskazuje na obecność błędów SEU?
W jednym projekcie związanym z eksploracją kosmosu mieliśmy niestandardową jednostkę MCU, która wywoływałaby wyjątek / przerwanie w przypadku błędów SEU, ale z pewnym opóźnieniem, tj. Niektóre cykle mogą przejść / instrukcje zostaną wykonane po jednej insn, która spowodowała wyjątek SEU.
Szczególnie narażona była pamięć podręczna danych, więc program obsługi unieważniałby niewłaściwą linię pamięci podręcznej i ponownie uruchamiał program. Tyle, że z powodu nieprecyzyjnego charakteru wyjątku sekwencja insn kierowanych przez insn zgłaszających wyjątek może nie zostać ponownie uruchomiona.
Zidentyfikowaliśmy niebezpieczne (nie do ponownego uruchomienia) sekwencje (jak
lw $3, 0x0($2)
, po których następuje insn, który modyfikuje$2
i nie jest zależny od danych$3
), i dokonałem modyfikacji w GCC, więc takie sekwencje nie występują (np. W ostateczności, oddzielając dwie insynnop
).Tylko coś do rozważenia ...
źródło
Jeśli twój sprzęt ulegnie awarii, możesz użyć mechanicznej pamięci masowej, aby go odzyskać. Jeśli baza kodu jest niewielka i ma trochę przestrzeni fizycznej, możesz użyć mechanicznego magazynu danych.
Powstanie powierzchnia materiału, na którą nie będzie miało wpływu promieniowanie. Będzie tam wiele biegów. Czytnik mechaniczny będzie działał na wszystkich biegach i będzie mógł się poruszać w górę iw dół. W dół oznacza, że wynosi 0, a w górę oznacza, że wynosi 1. Z 0 i 1 możesz wygenerować bazę kodu.
źródło
Użyj cyklicznego harmonogramu . Daje to możliwość dodawania regularnych czasów konserwacji w celu sprawdzenia poprawności krytycznych danych. Najczęściej spotykanym problemem jest uszkodzenie stosu. Jeśli twoje oprogramowanie jest cykliczne, możesz ponownie zainicjować stos między cyklami. Nie używaj ponownie stosów do połączeń przerywających, ustaw osobny stos każdego ważnego połączenia przerywającego.
Podobnie do koncepcji Watchdog są liczniki czasu. Uruchom sprzętowy timer przed wywołaniem funkcji. Jeśli funkcja nie powróci przed upływem terminu, przeładuj stos i spróbuj ponownie. Jeśli nadal nie powiedzie się po 3/5 próbach, musisz załadować ponownie z ROM.
Podziel swoje oprogramowanie na części i izoluj te części, aby użyć osobnych obszarów pamięci i czasów wykonania (szczególnie w środowisku kontrolnym). Przykład: akwizycja sygnału, przejęcie danych, główny algorytm i implementacja / transmisja wyników. Oznacza to, że awaria jednej części nie spowoduje awarii w pozostałej części programu. Tak więc, gdy naprawiamy akwizycję sygnału, reszta zadań jest kontynuowana na nieaktualnych danych.
Wszystko potrzebuje CRC. Jeśli wykonujesz z pamięci RAM, nawet twój .text potrzebuje CRC. Sprawdzaj CRC regularnie, jeśli używasz cyklicznego harmonogramu. Niektóre kompilatory (nie GCC) mogą generować CRC dla każdej sekcji, a niektóre procesory mają dedykowany sprzęt do wykonywania obliczeń CRC, ale myślę, że wypadałoby to poza zakresem twojego pytania. Sprawdzanie CRC monituje również kontroler ECC w pamięci o naprawienie błędów bitów, zanim stanie się to problemem.
źródło
Po pierwsze, zaprojektuj swoją aplikację pod kątem awarii . Upewnij się, że w ramach normalnej operacji przepływu oczekuje się zresetowania (w zależności od aplikacji i rodzaju awarii miękkiej lub twardej). Trudno to osiągnąć perfekcyjnie: operacje krytyczne wymagające pewnego stopnia transakcyjności mogą wymagać sprawdzenia i dostosowania na poziomie zespołu, aby przerwa w kluczowym punkcie nie mogła spowodować niespójnych poleceń zewnętrznych. Awaria szybko, jak tylko nie do odzyskania wykryte zostanie uszkodzenie pamięci lub odchylenie przepływu sterowania. Rejestruj awarie, jeśli to możliwe.
Po drugie, jeśli to możliwe, napraw korupcję i kontynuuj . Oznacza to częste sprawdzanie i ustawianie stałych tabel (i kodu programu, jeśli możesz); być może przed każdą większą operacją lub przerwą czasową i przechowywaniem zmiennych w strukturach, które autokorektują się (ponownie przed każdą większą operacją lub przerwą czasową, podejmują większość głosów z 3 i poprawiają, jeśli jest to pojedyncze odchylenie). Rejestruj poprawki, jeśli to możliwe.
Po trzecie, niepowodzenie testu . Skonfiguruj powtarzalne środowisko testowe, które losowo przerzuca bity w pamięci psuedo. Pozwoli to na odtworzenie sytuacji korupcji i pomoże zaprojektować aplikację wokół nich.
źródło
Biorąc pod uwagę komentarze superkata, tendencje współczesnych kompilatorów i inne rzeczy, kusiłbym się, aby wrócić do starożytnych czasów i napisać cały kod w asemblerze i przydziałach pamięci statycznej wszędzie. Wydaje mi się, że dla tego rodzaju niezawodności montaż nie wiąże się już z dużą różnicą procentową kosztów.
źródło
Oto ogromna liczba odpowiedzi, ale postaram się podsumować moje pomysły na ten temat.
Coś się zawiesza lub nie działa poprawnie może być wynikiem twoich własnych błędów - wtedy powinno być łatwo to naprawić po zlokalizowaniu problemu. Ale są też możliwe awarie sprzętu - a to jest trudne, jeśli nie niemożliwe, do naprawienia w ogóle.
Poleciłbym najpierw spróbować złapać problematyczną sytuację, logując się (stos, rejestry, wywołania funkcji) - albo logując je gdzieś do pliku, albo przesyłając jakoś bezpośrednio („o nie - mam awarię”).
Odzyskiwanie po takiej sytuacji błędu to albo restart (jeśli oprogramowanie nadal żyje i kopanie), albo reset sprzętowy (np. Hw watchdogs). Łatwiej zacząć od pierwszego.
Jeśli problem jest związany ze sprzętem - rejestrowanie powinno pomóc Ci zidentyfikować, w którym wystąpił problem z wywołaniem funkcji i może dać ci wewnętrzną wiedzę o tym, co nie działa i gdzie.
Także jeśli kod jest względnie złożony - sensowne jest „dzielenie go i podbijanie” - co oznacza, że usuwasz / wyłączasz niektóre wywołania funkcji tam, gdzie podejrzewasz problem - zazwyczaj wyłączasz połowę kodu i włączasz drugą połowę - możesz dostać „działa” / decyzja „nie działa”, po której można skupić się na innej połowie kodu. (Gdzie jest problem)
Jeśli problem pojawi się po pewnym czasie - wówczas można podejrzewać przepełnienie stosu - lepiej monitorować rejestry punktów stosu - jeśli stale rosną.
A jeśli uda ci się w pełni zminimalizować kod do czasu, aż aplikacja „hello world” - i nadal zawiedzie losowo - wtedy spodziewane są problemy ze sprzętem - i musi być „aktualizacja sprzętu” - co oznacza wynalezienie takiego procesora / ram / ... kombinacja sprzętu, która lepiej tolerowałaby promieniowanie.
Najważniejszą rzeczą jest prawdopodobnie sposób odzyskania logów, jeśli maszyna całkowicie zatrzymana / zresetowana / nie działa - prawdopodobnie pierwsza rzecz, którą powinien zrobić bootstap - to powrót do domu, jeśli wystąpi problematyczna sytuacja.
Jeśli w twoim środowisku jest również możliwe przesyłanie sygnału i odbieranie odpowiedzi - możesz spróbować zbudować jakieś zdalne środowisko debugowania online, ale wtedy musisz mieć przynajmniej działające media komunikacyjne i jakiś procesor / trochę pamięci RAM w stanie roboczym. A przez zdalne debugowanie mam na myśli albo podejście GDB / gdb albo własną implementację tego, co musisz odzyskać z aplikacji (np. Pobieranie plików dziennika, pobieranie stosu połączeń, pobieranie ram, restart)
źródło
Naprawdę przeczytałem wiele świetnych odpowiedzi!
Oto moje 2 centy: zbuduj model statystyczny nieprawidłowości pamięci / rejestru, pisząc oprogramowanie do sprawdzania pamięci lub częstych porównań rejestrów. Ponadto utwórz emulator w stylu maszyny wirtualnej, w której możesz eksperymentować z problemem. Sądzę, że jeśli zmienisz rozmiar złącza, częstotliwość zegara, sprzedawcę, obudowę itp. Zaobserwujesz inne zachowanie.
Nawet nasza pamięć komputera stacjonarnego ma pewien stopień awarii, co jednak nie wpływa na codzienną pracę.
źródło