Podczas Rescue Sprints u klientów budujących kontrolery lotu, sterowniki napędów czy węzły robotyczne, jedno z pierwszych pytań, jakie słyszę od CTO, brzmi: „Zostaliśmy przy FreeRTOS, ale czy nie powinniśmy byli wybrać Zephyra?”. Zwykle pada ono za późno - gdy architektura jest już zamrożona, a zespół zaczyna zderzać się z limitami wybranego jądra.
Decyzja Zephyr vs FreeRTOS to nie kwestia gustu ani popularności na GitHubie. To wybór modelu determinizmu, budżetu pamięci i tego, jak trudno (lub łatwo) będzie Ci później udowodnić, że system zachowuje się poprawnie w najgorszym możliwym przypadku. Poniżej rozkładam ten wybór na czynniki, które faktycznie mają znaczenie w projekcie klasy safety-critical - z matematyką, którą warto znać, żeby rozumieć dlaczego dany RTOS zachowuje się tak, a nie inaczej.
1. Model schedulera: co naprawdę gwarantuje priorytet
Oba jądra są w rdzeniu preempcyjnymi schedulerami o stałym priorytecie (fixed-priority preemptive). Zadanie o wyższym priorytecie zawsze wywłaszcza niższe. Diabeł tkwi w tym, co dzieje się przy równych priorytetach i jakie masz alternatywy.
- FreeRTOS: czysto priorytetowy, z opcjonalnym round-robin (time-slicing) dla wątków o tym samym priorytecie, gdy
configUSE_TIME_SLICINGjest włączone. Prosty, przewidywalny, minimalny. - Zephyr: priorytetowy z rozróżnieniem na wątki kooperacyjne (priorytety ujemne, niewywłaszczalne) i preempcyjne (priorytety dodatnie), plus opcjonalny scheduler EDF (Earliest Deadline First) przez
CONFIG_SCHED_DEADLINEoraz wątki meta-IRQ. Bogatszy model, więcej narzędzi, ale i więcej powierzchni do pomyłki.
Kluczowe: sam fakt, że masz priorytety, niczego nie gwarantuje. Zestaw zadań może być nieszedulowalny nawet przy idealnym schedulerze, jeśli łączne obciążenie CPU przekracza granicę teoretyczną. I tu wchodzi klasyka.
Przy planowaniu Rate Monotonic (RMS), gdzie zadanie o krótszym okresie dostaje wyższy priorytet, Liu i Layland (1973) udowodnili górną granicę wykorzystania CPU, poniżej której zbiór n niezależnych zadań okresowych jest zawsze szedulowalny:
gdzie Cᵢ to czas wykonania (WCET) zadania i, a Tᵢ to jego okres. Dla n = 1 granica to 1.0 (100%), dla n = 2 spada do 0.828, a w granicy dla dużej liczby zadań:
To znaczy: jeśli masz wiele zadań, a łączne obciążenie CPU przekracza ~69%, RMS może już nie dać gwarancji dotrzymania deadline'ów, mimo że procesor „ma jeszcze 30% wolnego”. To liczba, którą zna zbyt mało zespołów - i powód, dla którego przeładowany procesor gubi ramki zanim watchdog zdąży cokolwiek zauważyć.
Praktyka: zarówno FreeRTOS, jak i Zephyr pozwalają zaimplementować RMS ręcznym przypisaniem priorytetów wg okresów. Różnica polega na tym, że Zephyr daje Ci dodatkowo EDF, który teoretycznie sięga U ≤ 1.0, ale kosztem trudniejszej analizy najgorszego przypadku i wyższego narzutu schedulera.
2. Inwersja priorytetów i dlaczego mutex to nie semafor
Najbardziej podstępny błąd czasu rzeczywistego nie jest widoczny w testach jednostkowych. To inwersja priorytetów (priority inversion): zadanie wysokopriorytetowe czeka na zasób trzymany przez zadanie niskopriorytetowe, które z kolei jest wywłaszczane przez zadania średniopriorytetowe. W efekcie „wysoki” czeka na „średnie”, choć nigdy nie powinien.
Historyczny przykład to Mars Pathfinder (1997), gdzie właśnie inwersja priorytetów na magistrali danych powodowała resety całego lądownika. Ważniejszy niż anegdota jest mechanizm ochrony: Priority Inheritance Protocol (dziedziczenie priorytetów).
- FreeRTOS: mutexy (w odróżnieniu od binarnych semaforów) implementują podstawowe dziedziczenie priorytetów - zadanie trzymające mutex tymczasowo dostaje priorytet najwyższego zablokowanego oczekującego. Nie ma pełnego Priority Ceiling Protocol.
- Zephyr: mutexy również dziedziczą priorytet oczekującego wątku o najwyższym priorytecie, z podobnym mechanizmem przywracania po zwolnieniu.
Konsekwencja projektowa jest jedna i twarda: do sekcji krytycznych zawsze używaj muteksu, nigdy binarnego semafora do ochrony współdzielonego zasobu. Semafor nie zna właściciela, więc nie może podnieść mu priorytetu.
Dziedziczenie priorytetów nie eliminuje blokowania - ono je ogranicza i czyni policzalnym. Zadanie wysokopriorytetowe może zostać zablokowane najwyżej na czas najdłuższej sekcji krytycznej zadań o niższym priorytecie, które współdzielą z nim zasób:
Ten składnik blokowania Bᵢ wchodzi wprost do testu szedulowalności. Rozszerzony warunek Liu & Layland dla zadania i (uwzględniając wszystkie zadania j o wyższym priorytecie oraz blokowanie) przyjmuje postać:
Wniosek: każda sekcja krytyczna, którą trzyma zadanie niskopriorytetowe, „kradnie” budżet czasowy zadaniu wysokopriorytetowemu. Dlatego w firmware klasy safety-critical sekcje krytyczne muszą być krótkie i ograniczone czasowo - to nie stylistyka, to zmienna w nierówności, od której zależy dotrzymanie deadline'u.
Jeśli nie umiesz podać górnej granicy najdłuższej sekcji krytycznej w systemie, nie umiesz też policzyć Bᵢ - a bez Bᵢ Twoja analiza szedulowalności jest życzeniowa, nie inżynierska.
3. Latencja przerwań i koszt przełączenia kontekstu
Determinizm mierzy się w cyklach zegara, nie w deklaracjach. Na tym polu FreeRTOS ma przewagę wynikającą wprost z minimalizmu: cieńsze jądro to krótsza ścieżka od przerwania do reakcji.
- FreeRTOS na Cortex-M4: przełączenie kontekstu (context switch) mieści się typowo w ~1-3 µs przy 168 MHz, a rdzeń przełącza wątki w handlerze PendSV. Latencja wejścia w przerwanie na Cortex-M to sprzętowe ~12 cykli NVIC (odłożenie kontekstu na stos realizuje sam rdzeń).
- Zephyr: narzut jest zauważalnie wyższy, bo ścieżka obsługi obejmuje bogatszy kernel (model urządzeń, podsystem sterowników, opcjonalnie tracing/logging). To realny trade-off - płacisz kilkaset nanosekund do pojedynczych mikrosekund więcej za znacznie bogatszą infrastrukturę.
Realny czas reakcji na zdarzenie to suma składników, z których każdy da się zmierzyć na oscyloskopie lub w cycle counterze (DWT->CYCCNT):
Policzmy na Cortex-M4 taktowanym 168 MHz. Jeden cykl to:
Sprzętowa latencja wejścia w wektor przerwania (~12 cykli NVIC) to około 12 × 5.95 ns ≈ 71 ns. Do tego dochodzi czas samego ISR, przełączenie kontekstu (przyjmijmy 2 µs w FreeRTOS) i dopiero wykonanie zadania właściwego. Jeśli Twoja pętla regulacji stabilizacji drona działa przy 1 kHz (okres 1000 µs), a łączny narzut jądra na cykl to 3-4 µs, „tracisz” ~0.4% budżetu na sam RTOS. Przy 8 kHz (125 µs) ten sam narzut to już ~3%, i dobór jądra zaczyna mieć wymierne znaczenie.
Nie ufaj liczbom z whitepaperów. Zmierz context switch na swoim targetcie, przy swojej konfiguracji kompilatora i opcji optymalizacji - to jedno z pierwszych zadań, które zlecam w audycie firmware.
4. Budżet pamięci: cena bogatego ekosystemu
Tu różnica jest najbardziej namacalna i najczęściej decyduje o wyborze na małych mikrokontrolerach ARM Cortex-M (ST, TI, Nordic, Silicon Labs).
- FreeRTOS: samo jądro to zazwyczaj ~6-15 KB ROM, a koszt RAM na zadanie to blok TCB (kilkadziesiąt do ok. 100 bajtów) plus stos wątku, który dobierasz sam. Zmieści się komfortowo na Cortex-M0+ z 32 KB flash.
- Zephyr: zależnie od włączonych podsystemów (device tree, logging, stos sieciowy, shell) realistycznie od ~20 do 100+ KB ROM. Minimalna konfiguracja potrafi zejść nisko, ale gdy wchodzisz z BLE, USB czy TCP/IP, footprint rośnie szybko.
To nie jest wada Zephyra - to cena jego zalet. Płacisz pamięcią za gotowy, przetestowany device model i sterowniki, których w FreeRTOS musiałbyś dopisać (i utrzymywać) samodzielnie. Na MCU z 1-2 MB flash to nieistotne. Na węźle czujnikowym z 64 KB flash to może być decyzja typu „go/no-go”.
5. WCET, statyczna alokacja i droga do certyfikacji
Dla systemów, które projektuję z rygorem zbliżonym do DO-178C DAL-C (podejście inspirowane standardem, nie formalna certyfikacja), liczy się przede wszystkim jedno: czy potrafisz policzyć i udowodnić WCET (Worst-Case Execution Time) każdej istotnej ścieżki.
WCET jest policzalny tylko wtedy, gdy eliminujesz źródła niedeterminizmu. Dwa filary to:
- Statyczna alokacja pamięci: zero
malloc/freew czasie działania. FreeRTOS wspiera to wprost przezxTaskCreateStatici pochodne, gdzie każdy obiekt jądra dostaje bufor dostarczony przez Ciebie. Zephyr również pozwala na w pełni statyczne definiowanie wątków i obiektów jądra na etapie kompilacji. Dynamiczna sterta to wróg WCET - fragmentacja i nieograniczony czas alokacji rozbijają analizę. - Deterministyczny scheduler: stałe, ograniczone czasy operacji jądra, żeby narzut RTOS był stałym składnikiem w budżecie czasowym, a nie zmienną losową.
WCET łączy się bezpośrednio z pokryciem testowym. Aby wykazać, że policzyłeś najgorszy przypadek dla każdej gałęzi logiki, stosuje się MC/DC coverage (Modified Condition/Decision Coverage) - ten sam poziom rygoru, którego DO-178C wymaga dla DAL-A/B. Do tego dochodzi FMEA na poziomie architektury, żeby zmapować tryby awarii na mechanizmy zabezpieczające.
Powiązanie ze standardami jest tu wprost: klasy nienaruszalności bezpieczeństwa (IEC 61508 SIL, ISO 26262 ASIL w motoryzacji, DO-178C DAL w awionice) wymagają udokumentowanego determinizmu czasowego i pokrycia strukturalnego. Wybór RTOS wpływa na to, ile pracy dowodowej Cię czeka.
- FreeRTOS ma wariant komercyjny SafeRTOS (WITTENSTEIN) z pakietami pre-certyfikacyjnymi pod IEC 61508 / ISO 26262 / IEC 62304 - jeśli certyfikacja jest celem, to realna ścieżka.
- Zephyr rozwija inicjatywy funkcjonalnego bezpieczeństwa (Safety Working Group), ale bogactwo jądra oznacza większą powierzchnię kodu do zakwalifikowania. Mniejsza baza kodu bywa łatwiejsza do „obronienia” dowodowo.
6. Ochrona pamięci: MPU, watermarking i izolacja
Ostatni wymiar to izolacja błędów - kluczowa, gdy jeden zbłąkany wskaźnik nie może położyć całego systemu.
- Wykrywanie przepełnienia stosu: oba jądra wspierają stack watermarking (wypełnienie stosu wzorcem i sprawdzanie, jak głęboko zszedł), co pozwala wymiarować stosy z zapasem zamiast zgadywać. W FreeRTOS to
uxTaskGetStackHighWaterMark. - Ochrona MPU: tu Zephyr wyprzedza - ma dojrzały userspace oparty na sprzętowym MPU, izolujący wątki i egzekwujący granice pamięci między nimi. FreeRTOS ma wariant FreeRTOS-MPU, ale historycznie bardziej ograniczony i rzadziej wdrażany.
Jeśli architektura wymaga separacji domen bezpieczeństwa (np. oddzielenie zadań krytycznych od niekrytycznych na jednym MCU), sprzętowy userspace Zephyra jest realnym argumentem, którego FreeRTOS w wersji open source nie dorównuje.
Podsumowanie: kiedy który
Sprowadźmy to do konkretnych kryteriów decyzyjnych, zamiast akademickiego remisu.
Wybierz FreeRTOS, gdy:
- pracujesz na małym MCU (dziesiątki KB flash) i liczy się każdy kilobajt;
- potrzebujesz maksymalnie cienkiego, przewidywalnego jądra z najniższym narzutem context switch;
- chcesz minimalną bazę kodu do przeglądu/kwalifikacji, a certyfikację planujesz ścieżką SafeRTOS;
- Twój system to zwarty zestaw zadań kontrolnych bez rozbudowanej łączności.
Wybierz Zephyr, gdy:
- potrzebujesz bogatego ekosystemu out-of-the-box: BLE, USB, TCP/IP, LoRa, spójny device model i device tree;
- zależy Ci na sprzętowej izolacji pamięci (userspace/MPU) między domenami bezpieczeństwa;
- masz zapas ROM/RAM (setki KB do MB flash) i chcesz przenośność między rodzinami MCU;
- rozważasz zaawansowane strategie schedulingu (EDF) lub wielordzeniowość.
Najgorszy scenariusz to wybór podjęty odruchowo - „bo tak było w poprzednim projekcie” - a potem miesiące walki z konsekwencjami. Jeśli Twój zespół stoi przed tą decyzją albo już utknął z RTOS, który nie domyka budżetu czasowego lub pamięci, daj znać. W ramach Rescue Sprint w 1-2 tygodnie robię analizę szedulowalności, mierzę realny WCET i context switch na Twoim targetcie, i dostarczam rekomendację (albo naprawę) na kodzie klienta - z liczbami, nie opiniami.
