Tajniki Cortex-M3 – tryby pracy rdzenia
Rdzeń Cortex-M3 może pracować wykonując program normalnie (Thread mode – TM) lub obsługując przerwanie (Handler mode – HM). Takie rozróżnienie ma kluczowe znaczenie dla aplikacji pisanych opartych dla systemów operacyjnych.
Rdzeń Cortex-M3 może pracować wykonując program normalnie (Thread mode – TM) lub obsługując przerwanie (Handler mode – HM) – rys. 1. Takie rozróżnienie ma kluczowe znaczenie dla aplikacji pisanych opartych dla systemów operacyjnych.
Dodatkowo rozróżnia się dwa poziomy o różnych prawach dostępu do kluczowych obszarów w przestrzeni adresowej:
- tryb uprzywilejowany (Privileged Level – PL),
- tryb użytkownika (Unpriviledged/User Level – UL).
Rys. 1. Ilustracja trybów pracy rdzenia Cortex-M3
Podział ten ma zastosowanie w urządzeniach pracujących pod kontrolą systemu operacyjnego (OS – Operating System). System operacyjny pracuje w trybie uprzywilejowanym, natomiast program użytkownika uruchamiany jest przez OS i wykonywany w trybie o ograniczonych możliwościach.
Rys. 2. Mikrokontroler obsługując przerwanie zawsze pracuje w trybie uprzywilejowanym (PL)
Mikrokontroler obsługując przerwanie zawsze pracuje w trybie uprzywilejowanym (PL) nawet, jeśli w trakcie normalnego wykonywania programu ustawiony jest tryb użytkownika (UL). Tę relację przedstawiono na rys. 2, natomiast na rys. 3 przedstawiono działanie mikrokontrolera z włączonym trybem użytkownika. Z tego rysunku jasno wynika, że rdzeń Cortex-M3 pracując w trybie UL, w czasie wchodzenia od obsługi przerwania, przełącza się w tryb PL, natomiast kończąc obsługę przerwania wraca do trybu nieuprzywilejowanego (użytkownika).
Rys. 3. Działanie mikrokontrolera z aktywnym trybem użytkownika
Mechanizm ten większa stabilność systemu mikroprocesorowego, ponieważ aplikacja użytkownika, która ze swej natury posiada większe prawdopodobieństwo wygenerowania błędnych zachowań, ma ograniczony dostęp do kluczowych zasobów (np. NVIC i SysTick), a co za tym idzie, nie może bezpośrednio zachwiać stabilnością pracy mikrokontrolera.
Po uruchomieniu mikrokontroler zawsze rozpoczyna pracę w trybie uprzywilejowanym, a zatem do zadań systemu operacyjnego w pierwszej kolejności należy, jeszcze przed uruchomieniem aplikacji użytkownika, włączenie trybu nieuprzywilejowanego – UL.
Przejście z trybu użytkownika do trybu uprzywilejowanego nie jest możliwe bezpośrednio. Aplikacja uruchomiona w systemie operacyjnym nie ma możliwości wyłączenia trybu użytkownika, ponieważ nie ma dostępu do specjalnego rejestru kontrolnego CONTROL. Zapis do niego z poziomu aplikacji jest ignorowany. Zmiany można dokonać tylko podczas obsługi przerwania, która jest przeprowadzana w trybie pełnych uprawnień – PL. Wygląd całego rejestru CONTROL (z wartościami domyślnymi przedstawiono na rys. 4.
Rys. 4. Budowa rejestru CONTROL
System operacyjny, który zajmuje się przełączaniem kontekstów zadań i nadzorem nad poprawnością pracy całego układu, może zmienić (pracując w przerwaniach) status uprawnień. Innymi słowy, aby przejść do trybu uprzywilejowanego (PL) wykonywania programu należy zmienić w funkcji obsługi przerwania ustawienia specjalnego rejestru kontrolnego CONTROL, czyli wyzerować najmłodszy bit.
Stos
Stos fizycznie jest to obszar w pamięci, adresowany za pomocą specjalnego rejestru wskaźnikowego. Na stosie informacje wpisywane są w sposób LIFO (Last In First Out), co oznacza, że aby odczytać zagrzebane dane, należy najpierw ściągnąć ze stosu dane nowsze (rys. 5).
Rys. 5. Adresowanie stosu za pomocą rejestru R13
Dostęp do stosu realizowany jest przez użycie polecenia zdejmowania (POP) i wkładania (PUSH) na stos. Po każdej operacji PUSH rejestr wskaźnikowy (w przypadku rdzenia Cortex jest to rejestr R13) jest dekrementowany, czyli wskazuje młodszy adres. Analogicznie operacja POP powoduje inkrementację rejestru wskaźnika stosu. Mikrokontrolery STM32 są 32-bitowe, a więc stos jest inkrementowany (lub dekrementowany) zawsze o 4.
Stosy MSP i PSP
Rejestr R13 jest wskaźnikiem stosu – ściślej są to dwa bankowane wskaźniki stosu. W zależności od ustawienia drugiego bitu w specjalnym rejestrze kontrolnym CONTROL (rys. 4), może on wskazywać na stos systemowy MSP (Main Stack Pointer) lub na stos użytkownika PSP (Process Stack Pointer). Poniewż wskaźnik stosu jest bankowany, to w danej chwili R13 może wskazywać tylko jeden stos (MSP lub PSP) – rys. 6. Ponadto, obsługując przerwanie mikrokontroler zawsze korzysta ze stosu MSP.
Rys. 6. W danej chwili R13 może wskazywać tylko jeden stos (MSP lub PSP)
Przy okazji opisu trybów pracy mikrokontrolerów STM32 pojawiła się wzmianka o tym, że po uruchomieniu układ zawsze pracuje w trybie uprzywilejowanym. Konsekwencją tego jest, że również wskaźnik stosu R13 wskazuje na stos systemowy MSP tuż po uruchomieniu się mikrokontrolera.
Wykorzystanie dwóch stosów dodatkowo zwiększa odporność systemu na błędy, ponieważ jeśli aplikacja użytkownika zacznie wykonywać na stosie nieprawidłowe operacje, to nie wpłynie to negatywnie na stabilność działania układu. System operacyjny (jeżeli pracuje wykorzystując przerwania) ma swój, oddzielny stos MSP.
Mikrokontroler rozpoczynając obsługę przerwania umieszcza zawartość kluczowych rejestrów systemowych na stosie, natomiast powracając z obsługi przerwania zdejmuje te wartości, na powrót zapisując je do rejestrów. Dzięki temu przerwany proces nie traci informacji i może być dalej wykonywany bez przeszkód.
Jeśli wykorzystywany jest tylko stos MSP, to sprawa jest oczywista i mikrokontroler zachowuje się tak, jak to opisano powyżej. Wątpliwości rodzą się dopiero w przypadku, gdy wykorzystywane są oba stosy. W takiej sytuacji, do przechowywania informacji o przerywanym procesie wykorzystywany jest stos użytkownika PSP, natomiast MSP wykorzystywany jest tylko wewnątrz obsługi przerwania (rys. 7).
Rys. 7. Gdy wykorzystywane są oba stosy, do przechowywania informacji o przerywanym procesie wykorzystywany jest stos użytkownika PSP, a MSP jest wykorzystywany tylko wewnątrz obsługi przerwania
Gdy mikrokontroler pracuje w trybie uprzywilejowanym, to dostęp (lub zapis) do obydwu stosów jest możliwy bezpośrednio, za pomocą instrukcji odczytu (MRS) lub zapisu (MSR) rejestru specjalnego. Odczyt i zapis stosu MSP i PSP w asemblerze może wyglądać następująco:
MRS r0, PSP ; Odczyt PSP do R0 MSR PSP, r0 ; Zapis R0 do PSP MRS r0, MSP ; Odczyt MSP do R0 MSR MSP, r0 ; Zapis R0 do MSP
Powyższe instrukcje można, rzecz jasna, zapisać przy użyciu funkcji zdefiniowanych przez firmę STMicroelectronics, które zadeklarowano jako makra asemblerowe. W takiej sytuacji, odczyt stosu użytkownika realizowany jest za pomocą kodu pokazanego na list. 1.
List. 1. Przykład odczytu stosu użytownika
Wartość_PSP = __MRS_PSP();
Procedura zapisu na stos PSP natomiast polega na wywołaniu odpowiedniego fragmentu kodu asemblera:
__MSR_PSP( (u32)NOWA_WARTOSC );
Analogicznie zapis i odczyt stosu MSP:
Wartość_MSP = __MRS_MSP(); __MSR_MSP( (u32)NOWA_WARTOSC );
Zastosowanie powyższych instrukcji umożliwia systemowi operacyjnemu dostęp do stosu aplikacji (PSP), a więc pełną kontrolę nad programem użytkownika.
Tryb użytkownika i PSP
Oddzielne wykorzystanie mechanizmu poziomów uprzywilejowania i modelu dwóch stosów jest oczywiście użyteczne, ale znaczne zwiększenie możliwości uzyskuje się przez połączenie obydwu. Na list. 2 przedstawiono fragment programu, który jest odpowiedzialny za włączenie trybu użytkownika i obsługi stosu PSP. Gdy te operacje zostaną zrealizowane, następuje wywołanie przerwania od wyjątku systemowego SVC.
List. 2. Fragment programu odpowiedzialnego za włączenie trybu użytkownika i obsługę stosu PSP
// Inicjalizacja PSP for(Index = 0; Index < 0x200; Index++) PSPMem[Index] = 0x00; __MSR_PSP((u32)PSPMem + 0x200); // Wybor PSP i trybu uzytkownika __MSR_CONTROL(0x03); //Wygenerowanie SVC, powrot to trybu uprzywilejowanego __SVC();
Funkcja obsługi przerwania SVC wyłącza tryb użytkownika. Wywołanie przerwania jest zabiegiem koniecznym do zmiany poziomu uprzywilejowania. Jak było to już napisane, jego zmiana z trybu użytkownika do trybu uprzywilejowanego jest możliwa tylko w funkcji obsługi przerwania. Ilustruje to przykład funkcji obsługi przerwania SVC:
void SVCHandler(void) { __MSR_CONTROL(0); }
Instrukcja wewnątrz ciała funkcji ma za zadanie wpisać do specjalnego rejestru kontrolnego wartość 0, co odpowiada wyzerowaniu bitów CONTROL[0] i CONTROL[1], które odpowiedzialne są za aktualny poziom uprzywilejowania oraz wykorzystywany stos.
Wyjątki systemowe
Kontrolę wykonywanych przez STM32 zadań znacznie ułatwiają trzy wyjątki systemowe, dostępne w architekturze Cortex. Docelowym ich zadaniem jest praca pod kontrolą systemu operacyjnego, aczkolwiek w aplikacjach bez OS również można je z doskonałym Kutkiem wykorzystać do zapewnienia większej kontroli i stabilności pracy.
Do cyklicznego przełączania kontekstu zadań stworzono systemowy, 24–bitowy, timer SysTick. Jego zadaniem jest generowanie w określonych odstępach czasu przerwania, a funkcja jego obsługi może zajmować się właśnie przełączaniem kontekstu zadań.
Wyjątek SVC (System serVice Call)
W dobrze zaprojektowanym systemie operacyjnym, uruchomiona w nim aplikacja nie może bezpośrednio odwołać się do sprzętu. Odnosząc to zdanie do konkretnego przypadku można powiedzieć, że aplikacja użytkownika nie ma możliwości operowania na portach wejścia/wyjścia inaczej, niż za pośrednictwem systemu operacyjnego. Takie ograniczenia w stosunku do aplikacji uruchamianych w systemie operacyjnym mają bardzo istotne znaczenie ze względu na ograniczone zaufanie do programów użytkownika. W związku z tym musi istnieć mechanizm pozwalający na bezpieczne korzystanie ze sprzętu przez uruchomiony w systemie operacyjnym program użytkownika. Do realizacji tego zadania przeznaczono wyjątek SVC.
Jeśli program użytkownika chce skorzystać ze sprzętu, to musi wywołać funkcję SVC, a dopiero ta realizuje zadanie z użyciem wymaganego sprzętu.
Wyjątek PendSV
Jak napisano wcześniej, w najprostszym systemie operacyjnym, za przełączanie kontekstów uruchomionych zadań odpowiada timer SysTick. Podczas pracy takiego systemu może powstać prosty, choć nie zawsze oczywisty problem.
Podczas realizacji zadanie (program użytkownika) może zostać zgłoszone przerwanie, które wywłaszczy dotychczasowy proces. Jeśli podczas obsługi zgłoszonego przerwania timer SysTick przerwie je i system operacyjny rozpocznie przełączanie kontekstów zadań, to wychodząc z funkcji obsługi przerwania od timera SysTick, OS będzie próbował zmusić mikrokontroler do rozpoczęcia realizacji nowego zadania. Jest to rzecz jasna zachowanie błędne, ponieważ obsługa pierwszego przerwania zostanie znacznie opóźniona.
Rozwiązaniem tego problemu jest zastosowanie wyjątku PendSV. Jego programowalny priorytet jest ustawiany na najniższy możliwy, dzięki czemu przerwanie to nigdy nie wywłaszczy innych obsługiwanych przerwań.
Przeanalizujmy teraz zachowanie systemu z zaimplementowaną obsługą PendSV. Załóżmy, że zadanie realizowane w systemie nie ma aktualnie nic do zrobienia. Generuje wyjątek SVC, którego zadaniem jest przygotowanie do przełączenia kontekstu zadań i wywołanie przerwania PendSV. Dopiero to ostatnie przerwanie wykonuje właściwe przełączenie kontekstów tak, że gdy mikrokontroler powraca do normalnego wykonywania programu, to wówczas podejmowane już jest wykonywanie następnego zadania.
Rys. 8. Jeżeli podczas przełączania kontekstów zadań w funkcji obsługi przerwania PendSV system zarejestruje inne przerwanie, to przełączanie kontekstów zostaje wstrzymane przez wywłaszczenie PendSV
Jeśli w trakcie przełączania kontekstów zadań w funkcji obsługi przerwania PendSV system zarejestruje inne przerwanie, to przełączanie kontekstów zostaje wstrzymane przez wywłaszczenie PendSV (pamiętajmy, że jego priorytet jest najniższy – rys. 8).
PendSV i SysTick
Podobnie ma się sprawa wtedy, gdy system operacyjny przygotowuje przełączanie kontekstów zadań przy pomocy przerwania od timera SysTick. W takiej sytuacji, zakładając, że przerwanie od SysTick ma wysoki priorytet, a chwilę wcześniej był obsługiwany jakiś inny wyjątek, nastąpi wywłaszczenie tego ostatniego na rzecz SysTick.
Rys. 9. Gdy czynności obsługi przerwania zostaną zakończone, to oczekujący wyjątek PendSV zaczyna być realizowany, czego wynikiem jest przełączenie kontekstu i rozpoczęcie obsługi kolejnego zadania uruchomionego w systemie
W związku z tym, że obsługa wywłaszczonego przerwania jest zdecydowanie ważniejsza od wykonywania uruchomionych w systemie zadań, to funkcja obsługi przerwania od timera SysTick (podobnie jak SVC) tylko przygotowuje system do przełączenia kontekstu zadań i generuje wyjątek PendSV. Teraz, skoro PendSV ma najniższy priorytet, mikrokontroler wraca do obsługi wywłaszczonego wcześniej przerwania. Gdy czynności obsługi przerwania zostaną zakończone, to oczekujący wyjątek PendSV zaczyna być realizowany, konsekwencją czego jest przełączenie kontekstu i rozpoczęcie obsługi kolejnego zadania uruchomionego w systemie (rys. 9).