Bei der Arbeit mit Echtzeitsystemen müssen bestimmte Abläufe vorgegebene Zeitvorgaben einhalten. Dabei spielen effiziente Systemdienste wie Taskwechsel oder Inter-Task-Synchronisation eine wesentliche Rolle. Viele Systemdienste sind in Bezug auf die Zeitlimits jedoch kritisch, denn die Steuerung wird von der Benutzeraufgabe an das Betriebssystem übertragen, was zu unkontrollierbaren Latenzen führen kann.
Der erste von zwei Beiträgen zu diesem Thema zeigt auf, wie Benchmarks verwendet werden können, um solche Latenzen zu messen. So können Sie einschätzen, wie geeignet bestimmte Betriebssysteme für eine gegebene Echtzeitanwendung sind. Im zweiten Beitrag zum Thema messen wir dann die Leistung von zwei Echtzeit-Betriebssystemen: FreeRTOS und Zephyr (beide Open Source).
Auswahl einer Benchmark-Suite
Bevor wir mit dem Messen beginnen, benötigen wir eine Benchmark-Suite, die sich für unsere Zwecke eignet. Viele Benchmark-Suites zur Messung der Echtzeitleistung sind auf typische Echtzeitanwendungen wie z. B. Videocodierung ausgelegt. Sie beinhalten Benchmarks in Form von Algorithmen, die genau auf diese Art Anwendung zugeschnitten sind. Wenn wir die Dienste des Betriebssystems untersuchen wollen, auf dem die Plattform basiert, brauchen wir jedoch eine Suite mit anderen Testfällen. Die Benchmark-Suite RTOSBench wurde genau zu diesem Zweck entwickelt,
und zwar im Rahmen einer Masterarbeit an der University of Montréal. RTOSBench umfasst Testfälle für Dienste, die in den meisten Kerneln (Systemkernen) vorliegen: (kooperative) Kontextwechsel, Mutexes, Semaphoren, Message Queues und Interrupts. Leider gibt es für dieses Projekt keine Wartungsupdates mehr und es enthält kleinere Bugs. Wir haben es daher geforkt, die Bugs behoben und so die hier verwendete Version erstellt.
Anpassung von RTOSBench an FreeRTOS und Zephyr
Ein wesentlicher Aspekt von RTOSBench ist, dass es an ursprünglich nicht unterstützte Betriebssysteme angepasst werden kann, es kann also ein Porting durchgeführt werden. Dazu wird die API des entsprechenden Kernels in einem dünnen Porting Layer gekapselt. Damit RTOSBench auf einem neuen Betriebssystem läuft, brauchen wir zwei Dateien: eine Headerdatei (config.h) mit den Makros und Datentyp-Definitionen und eine Implementierungsdatei der Funktions-API des Porting Layer (z. B. in zephyr_porting_layer.c)
Festlegung der Typdefinitionen
Um ein besseres Verständnis des Portings zu bekommen, schauen wir uns nun die Aktivierung der Semaphoren-Unterstützung in FreeRTOS und Zephyr an. Zunächst müssen wir den Datentyp von RTOSBench no_sem_t so definieren, dass er die Semaphore des betreffenden Betriebssystems verwendet. FreeRTOS verwendet den Datentyp SemaphoreHandle_t. Deshalb müssen wir unserer config.h-Datei in FreeRTOS die folgende Typdefinition hinzufügen:
typedef SemaphoreHandle_T no_sem_t;
Zephyr verwendet für Semaphoren den Datentyp struct k_sem, also müssen wir der config.h-Datei des Zephyr-Ports die folgende Typdefinition hinzufügen:
typedef struct k_sem no_sem_t;
Implementierung der API-Funktionen
Nachdem wir RTOSBench eine Definition von no_sem_t hinzugefügt haben, können wir die Funktionen der Semaphoren-API implementieren. Insgesamt müssen wir in unserer porting_layer.c-Datei drei Semaphoren-Funktionen implementieren:no_sem_create, no_sem_wait und no_sem_signal. Die Funktion no_sem_create() dient der Initialisierung einer Semaphore sem mit einem Anfangswert. Zephyr bietet denselben Dienst mit der Funktion k_sem_init(). Diese setzt einen Zeiger auf eine Semaphore und initialisiert sie entsprechend. no_sem_create() können wir wie folgt implementieren:
void
no_sem_create(no_sem_t *sem, int value)
{
k_sem_init(sem, (unsigned int)value, K_SEM_MAX_LIMIT);
}
In FreeRTOS gehen wir ein bisschen anders vor. Wir verwenden die Funktion xSemaphoreCreateCounting() , um eine neue Semaphore zu erstellen. Im Gegensatz zur k_sem_init() in Zephyr erhält die Funktion keinen Zeiger auf die Semaphore, sondern bekommt eine Semaphore aus dem Heap-Speicher von FreeRTOS zugewiesen und schickt diese zurück. Die Implementierung von no_sem_create() in FreeRTOS sieht also so aus:
void
no_sem_create(no_sem_t *sem, int value)
{
/*
* Der maximale Wert hängt von Ihrer Plattform ab. Auf der
* Beispielplattform ist UBaseType_T eine Typdefinition für unsigned long.
*/
*sem = xSemaphoreCreateCounting(ULONG_MAX, value);
/* War die Zuweisung der Semaphore erfolgreich? */
wenn (*sem == NULL) {
no_serial_write("sem_create: error");
Error_Handler();
}
}
Für die anderen beiden Semaphoren-Funktionen erfolgt das Porting auf dieselbe Weise und mit allen anderen Kernel-Diensten gehen wir analog dazu vor. Danach sollten wir RTOSBench in beiden Betriebssystemen ausführen können. Für nähere Informationen dazu steht Ihnen unser Zephyr-Port auf GitHub zur Verfügung. Dieser unterstützt allerdings ausschließlich ARM Cortex Chipsätze mit einem Nested Vector Interrupt Controller (NVIC).
Fazit
Die Anpassung von RTOSBench an Zephyr ist relativ einfach. Mit der Toolchain von Zephyr kann man leicht neue Anwendungen erstellen und die Codebasis ermöglicht eine gute Abstraktion der Low-Level-Eigenschaften der Zielplattform. Die Anpassung von RTOSBench an FreeRTOS ist etwas aufwändiger. Das liegt vor allem daran, dass FreeRTOS minimalistischer angelegt ist und weder eine gängige Hardware-Abstraktionsschicht (HAL) noch eine Toolchain wie Zephyr umfasst. Diese müssen bei Hardwareherstellern eingekauft werden. Das bedeutet, dass für die Messung der Leistung von FreeRTOS Teile des Porting Layer von RTOSBench eventuell für jede Plattform, auf der RTOSBench laufen soll, reimplementiert werden müssen.