|
18. September 2019

Robuste Entwicklung mit Rust

Mit Rust für Embedded-Systeme kommt eine neue Sprache ins Rennen, die traditionell eingesetzte Hochsprachen wie Go oder Python und auch C und C++ starke Konkurrenz machen könnte.

42 Technology's hauseigene Softwareentwicklungserfahrung umfasst die Entwicklung der weltweit ersten rostbasierten Single-Chip-IoT-Anwendung. (Bild: 42 Technology Limited)

42 Technology’s hauseigene Softwareentwicklungserfahrung umfasst die Entwicklung der weltweit ersten rostbasierten Single-Chip-IoT-Anwendung. (Bild: 42 Technology Limited)

Wenn Softwareingenieure über die Vorteile von Rust sprechen, beziehen sie sich oft auf die drei Säulen für Entwicklungswerkzeuge: Leistung, Sicherheit und Produktivität. War es bisher fast unmöglich, alle drei in einer Hochsprache, C oder C++ zu bekommen, bietet die relativ neue Sprache nun die reale Aussicht, alle drei Funktionen in einer Sprache zu vereinen. So ist es mit Rust nicht mehr notwendig, Geschwindigkeit gegen Sicherheit zu tauschen, da Buffer Overflows, Integer Overflows und andere allgemeine Sicherheitsprobleme vom Compiler genau definiert werden. Zudem müssen dank der Verfügbarkeit leistungsfähiger Tools, wie integriertem Build System, Package Manager und einer Standardbibliothek mit einer Reihe nützlicher Datenstrukturen (wie Vector, HashMap<k,v> und UTF-8 Strings), Entwickler das Rad nicht ständig neu erfinden. Zu Beginn startete Rust auf Linux-, Windows- und MacOS-Systemen. Im Jahr 2018 begann jedoch die Entwicklergemeinschaft rund um die Sprache damit, sich auf kompilierten Rust Code für Embedded Systeme zu konzentrieren. Mit großem Aufwand entwickelte Embedded Rust die gleiche Reife und Stabilität wie die Desktop-Versionen. Nach der erfolgreichen Codegenerierung bleibt jedoch eines der nächsten Probleme für die relativ neue Sprache. So braucht es noch etwas Zeit, gemeinsam die beste Art und Weise zu finden, um bestimmte Operationen durchzuführen. Klarheit fehlt beispielsweise bei der Frage, wie Daten mit einem IRQ-Handler ausgetauscht werden sollten. Im Laufe des Jahres 2019 entwickelte die Embedded Rust Community eine Reihe von Embedded Rust-Anwendungen in beträchtlicher Größe, die im Allgemeinen weniger Zeit benötigen, um zu einem Produktionsstandard zu gelangen, während sie gleichzeitig etwa die gleichen Hardwareressourcen wie ein Bare-Metal-C-Programm verbrauchen. Damit bietet Rust Entwicklern eine echte Win-Win-Situation.

Gefährliche Unterbrechungsbehandlung in C (Bild: 42 Technology Limited)

Gefährliche Unterbrechungsbehandlung in C (Bild: 42 Technology Limited)

Produktiver mit Rust

Sieht man sich den Vergleich von C und Rust für das klassische IRQ-Handler-Beispiel an, so kann vor allem die Produktivitätssteigerung hervorgehoben werden. Abbildung 1 zeigt hierzu einen exemplarischen C-Code, der ein Modell verwendet, das wahrscheinlich vielen Embedded-Entwicklern bekannt ist: Zwischen dem Hauptthread und dem IRQ-Handler wird ein globaler Puffer geteilt und es gibt nur sehr wenig Möglichkeiten für den Umgang mit Race-Hazards. Wenn wir diesen Ansatz nun einfach in Rust portieren, dann wird der Rust Compiler standardmäßig einen Fehler melden. Hier könnte man zum Schlüsselwork ‚unsafe‘ greifen, dass einige Sicherheitsüberprüfungen deaktiviert und dem User überlässt. Etwas besser wäre es jedoch ein Objekt, das erst dann Zugriff auf seinen Wert gewährt, wenn nachweisbar Unterbrechungen deaktiviert wurden, oder direkt die exakten Unterbrechungen, die auf dieses Objekt zugreifen, deaktiviert wurden. Alternativ könnte aber auch eine Lock-Less-Struktur aufgebaut werden, die es dem Unterbrecher und der Hauptanwendung ermöglichen, den Datenzugriff zu teilen, ohne Unterbrechungen überhaupt zu deaktivieren. Rust gibt dem Softwareentwickler die Möglichkeit, all diese Ansätze zu erforschen und die am besten geeignete Lösung für seine Zielanwendung zu finden. Dank Paketmanager gibt es aber eine noch bessere Lösung, nämlich die atomaren Lock-Less-Strukturen eines anderen Entwicklers zu nutzen. Abbildung 2 zeigt die Verwendung der Heapless Crate von Jorge Aparicio, bei der die Mpmc-Queue für Multi-Producer- und Multi-Consumer-Operationen konzipiert wurde und atomare Vergleichs- und Speicheroperationen verwendet. So kann sichergestellt werden, dass bei einer Unterbrechung der Dequeue-Operation eine sichere Wiederholung erfolgt. Natürlich sind es nicht nur raffinierte Lock-Less-Warteschlangen, die man jetzt mit Rust schreiben kann. Die Sprache wurde von Grund auf um UTF-8-kodierte Zeichenketten herum aufgebaut (die nicht null-terminiert sind), was die Mehrsprachenunterstützung extrem einfach macht. Auch eine native Unterstützung für das Slicing von Arrays bietet die Sprache. Ebenso fügt das System automatisch Grenzwertprüfungen überall hinzu, wo es sich statisch nicht vergewissern kann, dass nie über die Länge eines Arrays hinaus zugegriffen werden kann. Beim Abstürzen von Rust-Programmen verhindert ein ‚kontrollierter Panikablauf‘ Unsicherheit beim Entwickler. Ohne dass das Programm in einem undefinierten Zustand mitläuft, kann dank hilfreicher Rückverfolgung, genau nachgesehen werden was falsch gelaufen ist. Sollte das Programm tatsächlich in dieser Weise abstürzen, liegt es meist an einem Fehler innerhalb eines der ‚unsafe‘-Blöcke, weil der Rest des Codes vom Compiler überprüft und verifiziert wurde, da er frei von kritischen Race-Situationen, Speichersicherheitsfehlern und anderem undefiniertem Verhalten ist. So ist die zu überprüfende Menge an Code im Vergleich zu einem C-Programm viel geringer.

Sichere, lockfreie Unterbrechungsbehandlung in Rust (Bild: 42 Technology Limited)

Sichere, lockfreie Unterbrechungsbehandlung in Rust (Bild: 42 Technology Limited)

Verwendung des Embedded HAL zum Zeichnen auf einem I2C OLED-Display (Bild: 42 Technology Limited)

Module mit Rust verbinden

Abbildung 2 zeigt auch die Verwendung des extern-‚C‘-Konstrukts, um den Aufruf des genau gleichen UART-Treibercodes aus Abbildung 1 zu demonstrieren, jedoch mit einer Vielzahl von Treibern, die in pure-Rust für UART, I2C, SPI und andere gängige Peripheriegeräte geschrieben wurden und auf eine lange Liste von SoCs einer Reihe von Anbietern ausgerichtet sind. Zu guter Letzt zeigt Abbildung 3 ein Beispiel für eine einfache SPI-LCD-Anwendung, die die Embedded HAL nutzt: Eine Rust-HAL, die Peripheriegeräte verschiedener Hersteller aus Sicht der Software-API genau gleich aussehen lässt. Die Verwendung dieses HAL im Vergleich zum traditionellen herstellerspezifischen Ansatz, macht es viel einfacher mehrere verschiedene Module von Drittanbietern zusammenzuführen und alle auf einer ausgewählten Plattform zusammenarbeiten zu lassen. In diesem speziellen Fall verwendet 42 Technologies den HAL-basierten Treiber für das SSD1306 I2C OLED-Display mit der HAL-Implementierung für das Nordic nRF52840.

|
Ausgabe:
www.42technology.com

Das könnte Sie auch interessieren