[STM32] Wyjątki C++ na przykładzie systemu ISIX-RTOS
Klasa led_blink odpowiedzialna jest za cykliczne mruganie diodą D1, które realizowane jest w pętli głównej wątku led_blink::main(). Mruganie diodą informuje nas o prawidłowym działaniu systemu operacyjnego, i nie wymaga dalszego komentarza. Główna demonstracja mechanizmu wyjątków realizowana jest w klasie ledkey. W konstruktorze klasy led_blink porty PE.0 oraz PE.15 odpowiedzialne odpowiednio za pin testowy dla badania czasu wykonania wyjątku oraz sterowanie diodą LED, ustawiane są w kierunku wyjścia. Po wykonaniu konstruktorów, system operacyjny w osobnym wątku wykonuje metodę ledkey::main(). W sekcji try zawarto pętlę nieskończona main(), która wywołuje metodę ledkey::execute_keycheck(), natomiast w sekcji przechwytywania wyjątków catch będziemy przechwytywać wyjątek typu podstawowego int oraz wspomniany wcześniej wyjątek klasy std::logic_error(). W momencie zgłoszenia jednego z tych wyjątków przez metodę ledkey::execute_keycheck(), wykonanie pętli nieskończonej zostanie przerwane, poprzez przejście do sekcji catch jednego z wyjątków. Efektem tego będzie wypisanie na porcie szeregowym komunikatu o rodzaju wyjątku, a następnie przejście poza sekcję catch, co spowoduje zakończenie wykonania tego wątku. A zatem program przestanie reagować na wciskanie klawisza OK. W cyklicznie wywoływanej metodzie ledkey::execute_keycheck(), sprawdzany jest stan klawisza OK. W przypadku wykrycia wciśnięcia tego klawisza stan diody zmieniany jest na przeciwny. Symulacja wystąpienia sytuacji wyjątkowych, realizowana jest po wciśnięciu klawisza UP lub DOWN. W przypadku wciśnięcia klawisza UP, zgłaszany jest wyjątek typu int o wartości -1. W przypadku wciśnięciu klawisza DOWN zgłaszany jest wyjątek klasy std::logic_error. Tuż przed zgłoszeniem wyjątków linia PE0, ustawiana jest w stan wysoki. Wciśnięcie odpowiedniego klawisza i zgłoszenie wyjątku, spowoduje przejście do odpowiedniej sekcji catch, w której zerowany jest stan linii PE0, oraz wypisywana albo wartość wyjątku int (-1), lub opis tekstowy wyjątku który zwróci metoda what().
Przyjrzyjmy się teraz zasobom pamięci Flash zajmowanym przez mechanizm obsługi wyjątków. W tym celu opisany wcześniej przykład najpierw pozbawimy obsługi wyjątków realizowanych przez klasę (std::logic_error), a zostawimy zgłaszanie wyjątku typu prostego int. Kolejną czynnością będzie całkowite wyłączenie kodu obsługi wyjątków w tym również zmianę w pliku Makefile zmiennej CPP_EXCEPTIONS=n wyłączającej obsługę wyjątków w ISIX, a następnie sprawdzenie rozmiaru zajmowanej pamięci, co pozwoli mieć orientacyjny pogląd na to ile zajmuje dodatkowy kod obsługi wyjątków. Wyniki przedstawionych badań przedstawiają się w sposób pokazany w tabeli 3.
Tab. 3. Wymagane pojemności pamięci Flash w zalezności od zastosowanych mechanizmów obsługi wyjątków
Opis | Pojemność Flash |
Kompletny przykład z wyjątkami std::logic_error oraz int | ~35 kB |
Przykład z obsługą wyłącznie wyjątków POD typu int | ~19 kB |
Przykład bez obsługi wyjątków | ~7 kB |
Jak łatwo możemy zauważyć sam kod odpowiedzialny za podstawową obsługę wyjątków zajmuje około 12 kB pamięci Flash. W przypadku większych mikrokontrolerów rodziny connectivity line, czy performance line jest to wielkość pomijalna, ponieważ jest to koszt całkowity użycia wyjątków niezależnie od tego ile razy później będą użyte w programie. Dodatkowo 16 kB będzie użyte w przypadku, gdy wykorzystamy standardowe wyjątki z hierarchii wyjątków biblioteki STD, co jest związane głównie z dołączeniem do kodu klasy std::string odpowiedzialnej za łańcuchy tekstowe. Jednak w przypadku większych mikrokontrolerów jest to koszt całkowicie do zaakceptowania. Spójrzmy teraz na czas jaki upływa od momentu zgłoszenia wyjątku do jego przechwycenia w klauzuli catch(…) – na rysunkach 1 oraz 2 przedstawiono odpowiednio oscylogramy na linii PE0 reprezentujące czas przechwycenia wyjątku typu int oraz wyjątku klasy std::logic_error().
Rys. 1. Czas obsługi wyjątku typu int
Rys. 2. Czas obsługi wyjątku klasy std::logic_error()
Czas jaki upłynął od momentu zgłoszenia wyjątku do jego przechwycenia, dla typu wyjątku int wynosi około 107 µs. Czas od momentu zgłoszenia wyjątku do jego przechwycenia dla typu std::logic_error() wynosi 151 µs przy taktowaniu mikrokontrolera częstotliwością 72 MHz. Zdaniem autora uzyskane czasy są całkiem zadowalające, ponieważ jak już wcześniej wspomnieliśmy, mechanizm wyjątków powinien być używany tylko do zgłaszania sytuacji wyjątkowych jak na przykład błędy. Należy także pamiętać o tym, że wykonanie programu, gdy przebiega on zgodnie ze ścieżką podstawową (bez wystąpienia wyjątku [błędu]), jest bardziej wydajne niż w przypadku ręcznej obsługi wyjątków poprzez wartości zwracane jak w C, ponieważ nie ma konieczności aby w każdej z warstw sprawdzać rezultat błędu. Warto tutaj wspomnieć jeszcze o zasobach pamięci RAM potrzebnych do obsługi wyjątków. Praktyczne próby pokazały że minimalna wartość stosu dla wątku, jeżeli chcemy wykorzystywać w nim wyjątki powinna wynosić około 1 kB.
Zaprezentowany przykład obsługi wyjątków dostępny jest wraz z pozostałymi przykładami dla systemu ISIX-RTOS na stronie: http://bryndza.boff.pl/index.php?dz=rozne&id=isixrtos a najświeższe przykłady można pobrać z repozytorium mercurial za pomocą polecenia: hg clone http://ww.boff.pl/hg/isix/isix_samples.