TockOS Hail: Difference between revisions

From
Jump to navigation Jump to search
Line 4: Line 4:
'''Idee'''
'''Idee'''


Bei Tock handelt es sich um ein open-source Betriebssystem für den Embedded-Bereich, welches u.a. Plattformen wie Cortex-M und RISCV unterstützt. Besonders dabei ist, dass dabei der Kernel als auch die Treiber in der Programmiersprache Rust geschrieben sind. Hinzu kommt die Schwierigkeit, dass Tock die limitierten Systemressourcen sinnvoll und effizient verwalten muss, da im Embedded-Bereich die Systemressourcen sehr minimalistisch gehalten werden. Dabei werden die Speicherschutzmechanismen von Rust genutzt, um für Safety und Security innerhalb des Kernels aber auch der Anwendungen zu gewährleisten. Dabei sind 2 Techniken für den Speicherschutz besonders hervorzuheben:
Bei Tock handelt es sich um ein Open-Source-Betriebssystem für den Embedded-Bereich, welches u.a. Plattformen wie Cortex-M und RISCV unterstützt. Besonders dabei ist, dass dabei der Kernel als auch die Treiber in der Programmiersprache Rust geschrieben sind. Hinzu kommt die Schwierigkeit, dass Tock die limitierten Systemressourcen sinnvoll und effizient verwalten muss, da im Embedded-Bereich die Systemressourcen sehr minimalistisch gehalten werden. Dabei werden die Speicherschutzmechanismen von Rust genutzt, um für Safety und Security innerhalb des Kernels aber auch der Anwendungen zu gewährleisten. Dabei sind 2 Techniken für den Speicherschutz besonders hervorzuheben:


1. Compile-Time Memory-Safety durch den Rust Compiler
1. Compile-Time Memory-Safety durch den Rust Compiler,


2. Nutzung von Speicherschutzeinheiten (u.a. auf Hardwareebene) um Anwendungen vom Kernel zu isolieren
2. Nutzung von Speicherschutzeinheiten (u.a. auf Hardwareebene) um Anwendungen vom Kernel zu isolieren.




Line 17: Line 17:
Einzigartig an Tock ist auch das Trust-Modell, wie in der Abbildung zu erkennen ist. Grundsätzlich wird nur den Komponenten des Kernels vertraut, jedoch schon nicht mehr den Treibern oder anderer Software, welche mit noch privilegierten Rechten in sog. "Capsules" laufen. Anwendungen wird grundsätzlich nicht vertraut in dieser Architektur. Allgemein kann festgehalten werden, dass das Hail-Board sowie Tock über keine virtuelle Adressierung verfügt, da wir lediglich eine MPU (Memory Proctection Unit) und keine MMU (Memory Management Unit) zur Verfügung haben.
Einzigartig an Tock ist auch das Trust-Modell, wie in der Abbildung zu erkennen ist. Grundsätzlich wird nur den Komponenten des Kernels vertraut, jedoch schon nicht mehr den Treibern oder anderer Software, welche mit noch privilegierten Rechten in sog. "Capsules" laufen. Anwendungen wird grundsätzlich nicht vertraut in dieser Architektur. Allgemein kann festgehalten werden, dass das Hail-Board sowie Tock über keine virtuelle Adressierung verfügt, da wir lediglich eine MPU (Memory Proctection Unit) und keine MMU (Memory Management Unit) zur Verfügung haben.


Die Architektur von Tock besteht also mehr oder weniger aus 3 Ebenen, wobei der Kernel die erste Ebene bildet. Im Kernel laufen nur die notwendigsten und esszenziellsten Programme, darunter der Scheduler, die boardspezifische Kernel-Konfiguration sowie das HAL (Hardware Abstraction Layer). Jedes Board besitzt seine eigene Kernel-Konfiguration, in der z.B. auch festegelegt wird, wie der Kernel reagieren soll, wenn eine Anwendung oder Capsule abstürzt. Beim Scheduler handelt es sich bei Tock um einen MLFQ-Scheduler (Multi-Level-Feedback-Queue). Außerdem kann der Kernel durch sog. "Upcalls" mit den Anwendungen im Userland kommunizieren.
Die Architektur von Tock besteht also mehr oder weniger aus 3 Ebenen, wobei der Kernel die erste Ebene bildet. Im Kernel laufen nur die notwendigsten und es­sen­zi­ellsten Programme, darunter der Scheduler, die boardspezifische Kernel-Konfiguration sowie das HAL (Hardware Abstraction Layer). Jedes Board besitzt seine eigene Kernel-Konfiguration, in der z.B. auch festgelegt wird, wie der Kernel reagieren soll, wenn eine Anwendung oder Capsule abstürzt. Beim Scheduler handelt es sich bei Tock um einen MLFQ-Scheduler (Multi-Level-Feedback-Queue). Außerdem kann der Kernel durch sog. "Upcalls" mit den Anwendungen im Userland kommunizieren.


Die zweite Ebene bilden die Capsules, welche zwischen der Anwendungsebene und dem Kernel leben. Die Capsules an sich unterscheiden sich grundsätzlich von Prozessen, da sie vollkommen anders vom Kernel und Scheduler behandelt werden. Programme in Capsules laufen im privilegierten Modus, dennoch kommt dies mit einigen wichtigen Restriktionen, welche auch der Programmiersprache Rust zugrunde liegen:
Die zweite Ebene bilden die Capsules, welche zwischen der Anwendungsebene und dem Kernel leben. Die Capsules an sich unterscheiden sich grundsätzlich von Prozessen, da sie vollkommen anders vom Kernel und Scheduler behandelt werden. Programme in Capsules laufen im privilegierten Modus, dennoch kommt dies mit einigen wichtigen Restriktionen, welche auch der Programmiersprache Rust zugrunde liegen:


* Kooperatives Scheduling: Capsules sind nicht preemptiv durch den Scheduler verwaltbar, sodass dieser stets warten muss, bis die Capsule die Kontrolle zurück an den Kernel gibt, wenn deren Operationen vollständig abgeschlossen sind. Das impliziert aber auch, dass falls die Capsule in eine unendlich Schleife gerät, diese für immer läuft, da der Scheduler wartet, bis die Operationen der Capsule abgeschlossen sind.
* Kooperatives Scheduling: Capsules sind nicht preemptiv durch den Scheduler verwaltbar, sodass dieser stets warten muss, bis die Capsule die Kontrolle zurück an den Kernel gibt, wenn deren Operationen vollständig abgeschlossen sind. Das impliziert aber auch, dass falls die Capsule in eine unendliche Schleife gerät, diese für immer läuft, da der Scheduler wartet, bis die Operationen der Capsule abgeschlossen sind.


* Keine dynamische Speicherallokierung: Die Speicherallokation für Capsules ist statisch und wird mit dem Systemstart festgelegt. Es ist also nicht ohne weiteres möglich, Speicher für Capsules zu allokieren. Dafür werden sog. "Grants" benötigt, welche im Grunde spezielle Speicherregionen innerhalb des Prozessspeichers sind. Diese werden herangezogen, falls Capsules zusätzlichen Speicher benötigen. Die Entscheidung, dass Capsules nicht dynamisch Speicher allokieren können, ist darauf zurückzuführen, dass dem gesamten System nur wenige Kilobyte an Hauptspeicher zur Verfügung stehen, wodurch es für den Kernel durch dynamische Allokationen schwer ist vorherzusagen, wenn der Hauptspeicher erschöpft ist.
* Keine dynamische Speicherallokierung: Die Speicherallokation für Capsules ist statisch und wird mit dem Systemstart festgelegt. Es ist also nicht ohne weiteres möglich, Speicher für Capsules zu allokieren. Dafür werden sog. "Grants" benötigt, welche im Grunde spezielle Speicherregionen innerhalb des Prozessspeichers sind. Diese werden herangezogen, falls Capsules zusätzlichen Speicher benötigen. Die Entscheidung, dass Capsules nicht dynamisch Speicher allokieren können, ist darauf zurückzuführen, dass dem gesamten System nur wenige Kilobyte an Hauptspeicher zur Verfügung stehen, wodurch es für den Kernel durch dynamische Allokationen schwer ist vorherzusagen, wenn der Hauptspeicher erschöpft ist.


* Kein Unsafe-Rust: Um Memory-Safety in Capsules zu gewährleisten, werden sich die Eigenschaften von Safe Rust zunutze gemacht. So kann schon zur Compile-Time schon festgestellt werden, ob das Programm Memory-Safety erfüllt. Da Unsafe-Rust in Capsules nicht erlaubt ist per Konvention, ist es aber auch nicht möglich, bestimme Programme in Capsules zu schreiben, da wir bestimmte Operationen nur mit Unsafe-Rust ausdrücken können. Dennoch benötigen wir durch Safe Rust keine zusätzlichen Mechanismen, um Memory-Safety zu überprüfen an dieser Stelle.
* Kein Unsafe-Rust: Um Memory-Safety in Capsules zu gewährleisten, werden sich die Eigenschaften von Safe Rust zunutze gemacht. So kann schon zur Compile-Time schon festgestellt werden, ob das Programm Memory-Safety erfüllt. Da Unsafe-Rust in Capsules nicht erlaubt ist per Konvention, ist es aber auch nicht möglich, bestimme Programme in Capsules zu schreiben, da wir bestimmte Operationen nur mit Unsafe-Rust ausdrücken können. Dennoch benötigen wir durch Safe Rust keine zusätzlichen Mechanismen, um Memory-Safety zu überprüfen, an dieser Stelle.


Zudem kann hier noch erwähnt werden, dass Capsules sich mit dem Kernel einen Stack teilen und auch während der Laufzeit nicht änderbar sind. Das führt aber auch dazu, dass wenn eine Capsule abstürzt bzw. den Kernel-Stack korrumpiert, das System sich davon nich ohne weiteres erholen kann und ein Neustart des Kernels erforderlich ist. Rust liefert hierbei mit seinen eigenen Sicherheitsgarantien die Memory-Safety der Capsules, sodass es keine fehlerhaften Speicherzugriffe während der Laufzeit gibt.
Zudem kann hier noch erwähnt werden, dass Capsules sich mit dem Kernel einen Stack teilen und auch während der Laufzeit nicht änderbar sind. Das führt aber auch dazu, dass wenn eine Capsule abstürzt, bzw. den Kernel-Stack korrumpiert, das System sich davon nicht ohne Weiteres erholen kann und ein Neustart des Kernels erforderlich ist. Rust liefert hierbei mit seinen eigenen Sicherheitsgarantien die Memory-Safety der Capsules, sodass es keine fehlerhaften Speicherzugriffe während der Laufzeit gibt.




'''Implementierungen'''
'''Implementierungen'''


Insbesondere existieren bisher 2 Implementierungen von Bibliotheken, welche eine Schnittstelle für die Anwendungsentwicklung in Tock zur Verfügung stellen. Zum einen die [https://github.com/tock/libtock-c libtock-c], welche C-Bindings bereitstellt und die [https://github.com/tock/libtock-rs libtock-rs], welche direkt Rust-Bindings für die Anwendungentwicklung bereitstellt. Dabei ist die Entwicklung in der libtock-c deutlich weiter vorangeschritten als in der in Rust geschriebenen Version, da dort deutlich mehr Bindings für die Features der Systeme und die des Boards bereitgestellt werden.
Insbesondere existieren bisher 2 Implementierungen von Bibliotheken, welche eine Schnittstelle für die Anwendungsentwicklung in Tock zur Verfügung stellen. Zum einen die [https://github.com/tock/libtock-c libtock-c], welche C-Bindings bereitstellt und die [https://github.com/tock/libtock-rs libtock-rs], welche direkt Rust-Bindings für die Anwendungsentwicklung bereitstellt. Dabei ist die Entwicklung in der libtock-c deutlich weiter vorangeschritten als in der in Rust geschriebenen Version, da dort deutlich mehr Bindings für die Features der Systeme und die des Boards bereitgestellt werden.


Weitere Informationen zu Tock sind in der Dokumentation des [https://github.com/tock/tock Github Repositories von Tock] zu finden.
Weitere Informationen zu Tock sind in der Dokumentation des [https://github.com/tock/tock Github Repositories von Tock] zu finden.
Line 39: Line 39:
''' Bootloader '''
''' Bootloader '''


Zudem ist der Bootloader von Tock ebenfalls in Rust geschrieben, allerdings leider nicht mehr als Open-Source verfügbar für das Hail Board. Als [https://github.com/tock/tock-bootloader/tree/master/boards/hail-bootloader Grund] gaben die Entwickler an, dass es sicht vom Aufwand her nicht lohnt, diesen Bootloader zu warten und zu aktualisieren, da Hail sich auf dem abesteigenden Ast befindet und der Support dafür in Zukunft eingestellt würde. Von daher war es uns nicht möglich, in den Quellcode des Bootloaders zu schauen, der auf diesem Board installiert ist und neue Erkenntnisse dadurch zu gewinnen.
Zudem ist der Bootloader von Tock ebenfalls in Rust geschrieben, allerdings leider nicht mehr als Open-Source verfügbar für das Hail Board. Als [https://github.com/tock/tock-bootloader/tree/master/boards/hail-bootloader Grund] gaben die Entwickler an, dass es sich vom Aufwand her nicht lohnt, diesen Bootloader zu warten und zu aktualisieren, da Hail sich auf dem absteigenden Ast befindet und der Support dafür in Zukunft eingestellt würde. Von daher war es uns nicht möglich, in den Quellcode des Bootloaders zu schauen, der auf diesem Board installiert ist und neue Erkenntnisse dadurch zu gewinnen.




''' Prozesse und Prozessmanagement'''
''' Prozesse und Prozessmanagement'''


Prozesse erhalten wie in anderen Betriebssystemen auch einen eigenen Bereich im Hauptspeicher, welcher in im Falle von Tock in folgende Bereiche eingeteilt ist:
Prozesse erhalten wie in anderen Betriebssystemen auch einen eigenen Bereich im Hauptspeicher, welcher im Fall von Tock in folgende Bereiche eingeteilt ist:


* Grant-Segment: Der Prozess kann auf dieses Segment standardmäßig weder lesend noch schreibend zugreifen. Dieses Segment dient dem Kernel zur Speicherverwaltung von den Capsules, kann aber auch asl Buffer für Daten verwendet werden, die der Prozess dann nutzen Kann. In dem Fall ist es dann möglich, über eine <code>allow_readonly()</code> Funktion diese Bereiche für den Prozess lesbar zu machen.
* Grant-Segment: Der Prozess kann auf dieses Segment standardmäßig weder lesend noch schreibend zugreifen. Dieses Segment dient dem Kernel zur Speicherverwaltung von den Capsules, kann aber auch asl Buffer für Daten verwendet werden, die der Prozess dann nutzen kann. In dem Fall ist es dann möglich, über eine <code>allow_readonly()</code> Funktion diese Bereiche für den Prozess lesbar zu machen.


* Heap-Segment: Das Heap-Segment wird dynamisch allokiert für den Prozess und die Größe dieses Segments wird in der Kernel-Konfikuration spezifisch für jedes Board festgelegt.
* Heap-Segment: Das Heap-Segment wird dynamisch allokiert für den Prozess und die Größe dieses Segments wird in der Kernel-Konfiguration spezifisch für jedes Board festgelegt.


* Daten-Segment: Hier liegen Konstanten und weitere Daten, sowie statische Variablen.
* Daten-Segment: Hier liegen Konstanten und weitere Daten, sowie statische Variablen.
Line 99: Line 99:
</syntaxhighlight>
</syntaxhighlight>


Somit wäre es also leicht möglich, dem Kernel andere Handlungsanweisungen zu geben, falls eine App crasht. Aktuell ist der Panic gesetzt, da es sich hierbei um ein Development Board handelt. Es wäre aber auch einach möglich, das falls eine App crasht, der Prozess, in dem die App läuft, zu terminieren und den Kernel unbeschadet weiterlaufen zu lassen. Diese Einstellungen im Kernel sind spezifisch für jedes Board in einer Konfigurationsdatei editierbar.
Somit wäre es also leicht möglich, dem Kernel andere Handlungsanweisungen zu geben, falls eine App crasht. Aktuell ist der Panic gesetzt, da es sich hierbei um ein Development Board handelt. Es wäre aber auch einfach möglich, dass falls eine App crasht, der Prozess, in dem die App läuft, zu terminieren und den Kernel unbeschadet weiterlaufen zu lassen. Diese Einstellungen im Kernel sind spezifisch für jedes Board in einer Konfigurationsdatei editierbar.


== Tockloader ==
== Tockloader ==

Revision as of 14:07, 18 October 2022

TockOS

Idee

Bei Tock handelt es sich um ein Open-Source-Betriebssystem für den Embedded-Bereich, welches u.a. Plattformen wie Cortex-M und RISCV unterstützt. Besonders dabei ist, dass dabei der Kernel als auch die Treiber in der Programmiersprache Rust geschrieben sind. Hinzu kommt die Schwierigkeit, dass Tock die limitierten Systemressourcen sinnvoll und effizient verwalten muss, da im Embedded-Bereich die Systemressourcen sehr minimalistisch gehalten werden. Dabei werden die Speicherschutzmechanismen von Rust genutzt, um für Safety und Security innerhalb des Kernels aber auch der Anwendungen zu gewährleisten. Dabei sind 2 Techniken für den Speicherschutz besonders hervorzuheben:

1. Compile-Time Memory-Safety durch den Rust Compiler,

2. Nutzung von Speicherschutzeinheiten (u.a. auf Hardwareebene) um Anwendungen vom Kernel zu isolieren.


Architektur

TockOS architecture.png

Einzigartig an Tock ist auch das Trust-Modell, wie in der Abbildung zu erkennen ist. Grundsätzlich wird nur den Komponenten des Kernels vertraut, jedoch schon nicht mehr den Treibern oder anderer Software, welche mit noch privilegierten Rechten in sog. "Capsules" laufen. Anwendungen wird grundsätzlich nicht vertraut in dieser Architektur. Allgemein kann festgehalten werden, dass das Hail-Board sowie Tock über keine virtuelle Adressierung verfügt, da wir lediglich eine MPU (Memory Proctection Unit) und keine MMU (Memory Management Unit) zur Verfügung haben.

Die Architektur von Tock besteht also mehr oder weniger aus 3 Ebenen, wobei der Kernel die erste Ebene bildet. Im Kernel laufen nur die notwendigsten und es­sen­zi­ellsten Programme, darunter der Scheduler, die boardspezifische Kernel-Konfiguration sowie das HAL (Hardware Abstraction Layer). Jedes Board besitzt seine eigene Kernel-Konfiguration, in der z.B. auch festgelegt wird, wie der Kernel reagieren soll, wenn eine Anwendung oder Capsule abstürzt. Beim Scheduler handelt es sich bei Tock um einen MLFQ-Scheduler (Multi-Level-Feedback-Queue). Außerdem kann der Kernel durch sog. "Upcalls" mit den Anwendungen im Userland kommunizieren.

Die zweite Ebene bilden die Capsules, welche zwischen der Anwendungsebene und dem Kernel leben. Die Capsules an sich unterscheiden sich grundsätzlich von Prozessen, da sie vollkommen anders vom Kernel und Scheduler behandelt werden. Programme in Capsules laufen im privilegierten Modus, dennoch kommt dies mit einigen wichtigen Restriktionen, welche auch der Programmiersprache Rust zugrunde liegen:

  • Kooperatives Scheduling: Capsules sind nicht preemptiv durch den Scheduler verwaltbar, sodass dieser stets warten muss, bis die Capsule die Kontrolle zurück an den Kernel gibt, wenn deren Operationen vollständig abgeschlossen sind. Das impliziert aber auch, dass falls die Capsule in eine unendliche Schleife gerät, diese für immer läuft, da der Scheduler wartet, bis die Operationen der Capsule abgeschlossen sind.
  • Keine dynamische Speicherallokierung: Die Speicherallokation für Capsules ist statisch und wird mit dem Systemstart festgelegt. Es ist also nicht ohne weiteres möglich, Speicher für Capsules zu allokieren. Dafür werden sog. "Grants" benötigt, welche im Grunde spezielle Speicherregionen innerhalb des Prozessspeichers sind. Diese werden herangezogen, falls Capsules zusätzlichen Speicher benötigen. Die Entscheidung, dass Capsules nicht dynamisch Speicher allokieren können, ist darauf zurückzuführen, dass dem gesamten System nur wenige Kilobyte an Hauptspeicher zur Verfügung stehen, wodurch es für den Kernel durch dynamische Allokationen schwer ist vorherzusagen, wenn der Hauptspeicher erschöpft ist.
  • Kein Unsafe-Rust: Um Memory-Safety in Capsules zu gewährleisten, werden sich die Eigenschaften von Safe Rust zunutze gemacht. So kann schon zur Compile-Time schon festgestellt werden, ob das Programm Memory-Safety erfüllt. Da Unsafe-Rust in Capsules nicht erlaubt ist per Konvention, ist es aber auch nicht möglich, bestimme Programme in Capsules zu schreiben, da wir bestimmte Operationen nur mit Unsafe-Rust ausdrücken können. Dennoch benötigen wir durch Safe Rust keine zusätzlichen Mechanismen, um Memory-Safety zu überprüfen, an dieser Stelle.

Zudem kann hier noch erwähnt werden, dass Capsules sich mit dem Kernel einen Stack teilen und auch während der Laufzeit nicht änderbar sind. Das führt aber auch dazu, dass wenn eine Capsule abstürzt, bzw. den Kernel-Stack korrumpiert, das System sich davon nicht ohne Weiteres erholen kann und ein Neustart des Kernels erforderlich ist. Rust liefert hierbei mit seinen eigenen Sicherheitsgarantien die Memory-Safety der Capsules, sodass es keine fehlerhaften Speicherzugriffe während der Laufzeit gibt.


Implementierungen

Insbesondere existieren bisher 2 Implementierungen von Bibliotheken, welche eine Schnittstelle für die Anwendungsentwicklung in Tock zur Verfügung stellen. Zum einen die libtock-c, welche C-Bindings bereitstellt und die libtock-rs, welche direkt Rust-Bindings für die Anwendungsentwicklung bereitstellt. Dabei ist die Entwicklung in der libtock-c deutlich weiter vorangeschritten als in der in Rust geschriebenen Version, da dort deutlich mehr Bindings für die Features der Systeme und die des Boards bereitgestellt werden.

Weitere Informationen zu Tock sind in der Dokumentation des Github Repositories von Tock zu finden.


Bootloader

Zudem ist der Bootloader von Tock ebenfalls in Rust geschrieben, allerdings leider nicht mehr als Open-Source verfügbar für das Hail Board. Als Grund gaben die Entwickler an, dass es sich vom Aufwand her nicht lohnt, diesen Bootloader zu warten und zu aktualisieren, da Hail sich auf dem absteigenden Ast befindet und der Support dafür in Zukunft eingestellt würde. Von daher war es uns nicht möglich, in den Quellcode des Bootloaders zu schauen, der auf diesem Board installiert ist und neue Erkenntnisse dadurch zu gewinnen.


Prozesse und Prozessmanagement

Prozesse erhalten wie in anderen Betriebssystemen auch einen eigenen Bereich im Hauptspeicher, welcher im Fall von Tock in folgende Bereiche eingeteilt ist:

  • Grant-Segment: Der Prozess kann auf dieses Segment standardmäßig weder lesend noch schreibend zugreifen. Dieses Segment dient dem Kernel zur Speicherverwaltung von den Capsules, kann aber auch asl Buffer für Daten verwendet werden, die der Prozess dann nutzen kann. In dem Fall ist es dann möglich, über eine allow_readonly() Funktion diese Bereiche für den Prozess lesbar zu machen.
  • Heap-Segment: Das Heap-Segment wird dynamisch allokiert für den Prozess und die Größe dieses Segments wird in der Kernel-Konfiguration spezifisch für jedes Board festgelegt.
  • Daten-Segment: Hier liegen Konstanten und weitere Daten, sowie statische Variablen.
  • Stack-Segment: Die Größe vom Stack wird beim Erstellen des Prozesses final festgelegt und kann dann auch nicht mehr geändert werden. Dazu ist es in Rust-Anwendungen notwendig, diese Stackgröße manuell zu setzen.

Dazu gibt es noch das Text-Segment, welches die Anwendung selbst als Binary (TAB) enthält, aber nicht mehr im Hauptspeicher liegt, sondern im Flash-Speicher des Boards. Prozesse werden preemptiv vom Scheduler behandelt, können also während ihrer Ausführung durch den Kernel (z.B. aufgrund eines Interrupts) in die Ready-Queue geschoben werden. Also muss nicht erst gewartet werden, bis der Prozess die Kontrolle freiwillig wieder abgibt. Da Anwendungen auch in C geschrieben werden können und die Rust-Anwendungen auch Unsafe-Regionen enthalten können, ist es nicht mehr möglich, Memory Safety zur Compile-Time zu garantieren. Daher wird für die Prozesse zur Laufzeit die MPU genutzt, welche überprüft, ob nur gültige Speicherzugriffe und Referenzen genutzt werden. Im Falle eines ungültigen Zugriffs wird ein Fehler geworfen und der Kernel entscheidet, wie mit der Anwendung, die diesen Fehler verursacht hat, verfahren wird.

Findet in einer Anwendung ein fehlerhafter Speicherzugriff statt, der zur Compile-Zeit nicht erkannt wird, so crasht nicht nur die Anwendung selbst. Zusätzlich wird im Kernel von TockOS ein kritischer Fehler (Rust: panic) ausgelöst, welcher den Kernel ebenfalls zum Absturz bringt, sodass danach ein Neustart erforderlich ist. Der Kernel entscheidet letztendlich, wie er mit der Applikation verfährt.

Dabei wird nach der Art des Fehlers entschieden, wie der Kernel handelt:

    fn set_fault_state(&self) {
        // Use the per-process fault policy to determine what action the kernel
        // should take since the process faulted.
        let action = self.fault_policy.action(self);

        match action {
            FaultAction::Panic => {
                // process faulted. Panic and print status
                self.state.update(State::Faulted);
                panic!("Process {} had a fault", self.process_name);
            }
            FaultAction::Restart => {
                self.try_restart(None);
            }
            FaultAction::Stop => {
                // This looks a lot like restart, except we just leave the app
                // how it faulted and mark it as `Faulted`. By clearing
                // all of the app's todo work it will not be scheduled, and
                // clearing all of the grant regions will cause capsules to drop
                // this app as well.
                self.terminate(None);
                self.state.update(State::Faulted);
            }
        }
    }

Aktuell ist im Kernel festgelegt, dass falls Apps abstürzen, dann auch der Kernel durch ein von selbst ausgelöstes PANIC crasht. Damit soll es für die Entwickler einfacher sein, die Apps zu debuggen. Daher hier ein Ausschnitt aus dem Kernel, welcher genau diese Fehler erzeugt. Dieser Ausschnitt stammt aus der Kernel-Config und kann manuell angepasst werden:


    // Configure application fault policy
    let fault_policy = static_init!(
        kernel::process::ThresholdRestartThenPanicFaultPolicy,
        kernel::process::ThresholdRestartThenPanicFaultPolicy::new(4)
    );

Somit wäre es also leicht möglich, dem Kernel andere Handlungsanweisungen zu geben, falls eine App crasht. Aktuell ist der Panic gesetzt, da es sich hierbei um ein Development Board handelt. Es wäre aber auch einfach möglich, dass falls eine App crasht, der Prozess, in dem die App läuft, zu terminieren und den Kernel unbeschadet weiterlaufen zu lassen. Diese Einstellungen im Kernel sind spezifisch für jedes Board in einer Konfigurationsdatei editierbar.

Tockloader

Der Tockloader dient dazu, kompilierte Binärdateien auf den Flash-Speicher eines Chips zu laden. Außerdem kann darüber weiterhin mit TockOS kommuniziert werden, welches ebenfalls auf einem solchen Chip laufen muss.

Initialisierung (Linux)

Vor der Installation des Tockloaders müssen zuerst einige wichtige Tools installiert werden:

Rust: $ curl https://sh.rustup.rs -sSf | sh

Compiler für Cortex-M-Boards: $ sudo apt install gcc-arm-none-eabi

Nun kann auch der Tockloader installiert werden: $ pip3 install -U --user tockloader


Kernel Kompilieren

Zusätzlich kann man noch den Tock-Kernel installieren und kompilieren, um ggf. den Kernel auf dem Board zu updaten:

$ git clone https://github.com/tock/tock

Anschließend in das Verzeichnis wechseln und kompilieren:

$ make

Letztendlich kann der neu kompilierte Kernel auch auf das Board geflasht werden.

$ tockloader flash


Bedienung

Hier sind ein paar nützliche Befehle aufgelistet, um Apps mithilfe des Tockloaders mit libtock-c zu installieren/deinstallieren und den Status des Systems abzufragen:

  • Apps installieren: $ tockloader install
  • Apps deinstallieren: $ tockloader install [name]
  • Apps aktualisieren: $ tockloader update [name]
  • Apps deinstallieren (löscht alle installierten Apps): $ tockloader erase-apps
  • Von serial output lesen: $ tockloader listen
  • Alle installierten Apps anzeigen: $ tockloader list
  • Alle Befehle anzeigen lassen: $ tockloader help

libtock-c

Die Bibliothek libtock-c ermöglicht es, Anwendungen für TockOS in C zu schreiben, indem dafür die notwendigen C-Bindings zur Verfügung gestellt werden. Dabei werden bereits einige Beispiele für Anwendungen bereitgestellt, welche direkt kompiliert und über den Tockloader installiert werden können. Die libtock-c basiert auf einer eigenen Implementierung der C-Standardbibliothek für Embedded-Systeme, welche auf den Namen 'newlib' getauft wurde und mit der libtock-c mitgeliefert wird. Von daher ist es inherent nicht möglich, den POSIX-Standard zu nutzen bzw. zu gewähren während der Entwicklung von Anwendungen mit dieser Bibliothek. Zusätzlich gibt es noch Erweiterungen für Lua und C++, welche ebenfalls direkt mitgeliefert werden.


Beispiel-Apps installieren

Zunächst benötigt man die libtock-c:

$ git clone https://github.com/tock/libtock-c.git

Anschließend in das Verzeichnis wechseln:

$ cd libtock-c/examples

Nun kann eine Beispiel-App ausgewählt werden (z.B. Blink):

$ cd blink

Die Binärdatei des Beispiels kann nun mit 'make' gebaut werden:

$ make

Nun muss diese nur noch mithilfe des Tockloaders installiert werden:

$ tockloader install

Anschließend sollte die App ausgeführt werden (im Falle von Blink sollte die LED aufleuchten).


Eigene Apps entwickeln

Es ist direkt möglich, neue Apps in der Tock-Umgebung zu programmieren. Dazu kann man selbst direkt Apps im bereitgestellten Ökosystem von libtock-c programmieren und kompilieren, indem man im 'examples' Verzeichnis einen neuen Ordner anlegt und anschließend das folgende Makefile kopiert:

# Makefile for user application

# Specify this directory relative to the current application.
TOCK_USERLAND_BASE_DIR = ../..

# Which files to compile.
C_SRCS := $(wildcard *.c)

# Include userland master makefile. Contains rules and flags for actually
# building the application.
include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk

Um nun Applikationen auch extern entwickeln zu können, also außerhalb des libtock-c Verzeichnisses, ist lediglich eine Änderung der Pfadvariable TOCK_USERLAND_BASE_DIR auf die entsprechende Stelle notwendig, in der die libtock-c liegt. Dort liegen die notwendigen Makefiles, um die Applikation erfolgreich zu komplieren und das TAB (Tock Application Bundle) zu bauen.

libtock-rs

Ähnlich zur libtock-c stellt libtock-rs das Rust-Userland von Tock als Rust-Library dar. Bisher sind die Rust-Analoge der alarm-, buttons- und console-APIs verfügbar, weiterhin gibt es Unterstützung für die Konsole des Tock-Kernels und ein Debug-Interface. Um die libtock-rs zu verwenden, muss man einige Eigenheiten der Build-Umgebung beachten: Es sind einige spezielle Flags an rustc vonnöten, welche die Kompilierung für die entsprechenden ARM-Targets ermöglichen und das Linking kontrollieren. Zu finden sind diese Optionen in libtock-rs/.cargo/config, wo auch der mit der Library mitgelieferte "Runner" konfiguriert wird. Dieser ist dafür verantwortlich, die als ELF im cargo-Buildprozess kompilierte Binary in ein Tock Application Bundle (.tab) umzuwandeln, welches dann ebenso mit dem Runner auf das Board geflasht werden kann. Ein typisches Projekt könnte so aussehen:

 app
 |
 +- .cargo
 |  |
 |  +- config (mit Linker- und Runner-Konfiguration)
 |
 +- runner (aus libtock-rs übernommen)
 |
 +- src (Quellcode der App)
 |
 +- Cargo.toml:
 |   [package]
 |   ...
 |   [dependencies]
 |   libtock = { path = "<relativer Pfad zu libtock-rs>" }
 |   libtock_buttons = { path = "<relativer Pfad zu libtock-rs>/apis/buttons" }                      # Pro benutzter API von libtock
 |   ...
 |   [...]
 |   ...
 |   [workspace]
 |   members = [ "runner" ]                                                                          # Um den Runner sichtbar zu machen
 |
 +- build.sh:
     export LIBTOCK_PLATFORM=hail                                                                    # Plattform setzen
     cargo build --release --target thumbv7em-none-eabi                                              # Projekt kompilieren
     cargo run -p runner --relase target/thumbv7em-none-eabi/release/<App-ELF> --deploy=tockloader   # In .tab konvertieren & flashen

Mit dieser Projektstruktur kann man einfache Rust-Apps für Tock programmieren. Es ist jedoch zu beachten, dass das Rust-Userland von libtock-rs nur mit Tock 2.0 funktioniert, wozu wir einen entsprechend neuen Tock-Kernel kompilieren und flashen mussten. Dies hat die Kompatibilität mit Teilen des libtock-c-Userlands zerstört, viele Apps waren aber weiterhin funktionsfähig.

Für das im Board verfügbare Bluetooth-Radio gab es in libtock-rs bislang keine API, daher haben wir im Zuge des Projektes begonnen, eine solche zu implementieren, wobei wir uns recht eng an die C-Implementierung in libtock-c gehalten haben.

Hail Board

Hail ist ein speziell für Tock im Jahre 2017 entwickeltes Board für IoT-Development. Es operiert mit dem vollständig in Rust geschriebenen Open-Source Betriebssystem TockOS. Zudem exisiert auch ein in Rust geschriebener Bootloader für dieses Board, welcher allerdings augrund eines zu hohen Aufwands für die Entwickler nicht mehr Open-Source verfügbar ist.


Komponenten des IoT-Boards

Das Tock Hail IoT-Development Board besitzt folgende Komponenten mit folgenden Funktionen:

Komponenten des Hail-Boards
Name Spezifikation Beschreibung Technische Details
SAM4L Cortex-M4 48 - 120 Mhz, 32 - 160 kB RAM, 128-2048 kB Flash Memory Microcontroller für sehr energiesparende Embedded-Systeme, ARM CPU Dokumentation SAM4L-Package

Dokumentation Cortex-M4

nRF51822 BLE Radio 2.4 Ghz, 16/32 kB RAM, 128/256 kB Das BLE (Bluetooth Low Energy) Radio ist in der Lage, zu anderen Geräten Bluetooth-Verbindungen aufzubauen. Dokumentation
SI7021 Temperature and Humidity Sensor - Messung von Temperatur und Luftfeuchtigkeit -
ISL29035 Light Sensor 16-Bit ADC, 11s - 105ms RT Messung von Helligkeit in Lumen Dokumentation
FXOS8700CQ 6-axis Accelerometer and Magnetometer 6-axis e-compass (T range), acceleration, ODR 1.5 Hz - 800 Hz Sensor zur Bestimmung von Drehungen, Geschwindigkeit etc. Andwendung für digitale Kompasse, Bewegungserkennung oder Erkennung der Orientierung des Geräts. Dokumentation
RGB LED 3 LEDs (R,G,B) Ansteuerbare LED mit 3 Kanälen (rot, grün, blau) -
User push-button - Programmierbarer Knopf (sehr klein) -


Photon Pinout

TockHail Pinout.png

PINS
Label Beschreibung
A0-A5 (ADC) Analog-to-Digital Pin
DAC Digital-to-Analog Output, kann als digitaler Pin oder DAC genutzt werden
WKP Wakeup-Pin, um Modul aus Sleep-Modus zu reaktivieren
RX Dient als UART (Universal Asynchronous Receiver / Transmitter) RX, kann aber auch als digitaler GPIO genutzt werden
TX Dient als UART (Universal Asynchronous Receiver / Transmitter) TX, kann aber auch als digitaler GPIO genutzt werden
GND Ground-Pin, für Spannungmessung
VIN Input/output Pin mit 3.6-4.8 V Grundspannung
D0-D7 Digitale GPIO Input Pins, alternativ auch digitale GPIO Pins
3V3 3V Spannung anliegend, Stromversorgung
RST Reset-Pin
VBAT Batterie zum sichern von Registern und SRAM und RTC, wenn Strom von 3V3 nicht verfügbar


Ansteuerbare Pins


Über GPIO (General Purpose Input/Output) lassen sich die einzelnen Pins im Programm ansteuern. Dabei scheint es mehrere Modis zu geben, in denen die Konfiguration der Pins laufen kann:

  • JTAG (Joint Test Action Group): Debug- und Testmodus für Hardware und integrierte Schaltungen. Damit können einzelne Komponenten auf Funktionalität getestet werden, wobei spezielle Teile des ICs (Integrated Circuits) erst dann aktiviert werden.
  • SPI1 (Serial Peripheral Interface): Hierbei handelt es sich um ein Bus-System, mit dem digitale Schaltungen nach dem Master-Slave-System miteinander verbunden werden können. Dabei können viele Teilnehmer gleichzeitig über diesen Bus verbunden werden und der Master legt fest, mit welchem Slave er dabei kommunizieren will.
  • TIM3: General Purpose Timer

Dabei ist es uns gelungen, in der aktuellen Konfiguration die Pins D0, D1, D6 und D7 anzusteuern. Die Pins D2 und D5 waren immer aktiv dabei mit einer konstanten Spannung von 3.3V.

Die Pins lassen sich folgendermaßen über das GPIO-Interface ansteuern:

#include <stdio.h>
#include 'ble.h'
#include 'led.h'
#include 'timer.h'

// Set to test a certain pin
#define PIN_NUM 0 

int main (void) {

  // LED will light up if a certain pin is activated
   gpio_set(PIN_NUM);
   gpio_enable_output(PIN_NUM);
   led_on(DRIVER_NUM_LEDS);
   delay_ms(5000);
   gpio_disable(PIN_NUM);
   gpio_clear(PIN_NUM);
   led_off(DRIVER_NUM_LEDS);

  return 0;
}

Dabei haben sich folgende Pins mit einem bestimmten Index ansteuern lassen, welche der Funktion in GPIO übergeben werden müssen:

Indexe ansteuerbarer Pins
Index ID
0 D0
1 D1
2 D6
3 D7

BLE API für Rust

In der libtock-c gibt es eine API für das Bluetooth-Radio, die es in der libtock-rs nicht gibt. Um das Bluetoothradio angemessen zu verwenden, benötigt man jedoch einen vollständigen Bluetooth-Stack, welcher in der libtock-c durch libnrf-serialization mitgeliefert wird -- einen entsprechenden Bluetooth-Stack in Rust gibt es in dieser Form nicht. Da das Bluetoothradio nicht in den Prozessor integriert ist, kommuniziert dieser über SPI mit dem Radio, was nur mit einer Aufspaltung in Pakete funktioniert. Dies ist im Serialisierungsteil der Library implementiert, welcher als "Backend" fungiert und auf Tock ausgelegt ist. Aufgrund der Spezifizität der Anwendung für diese Bibliothek ist es naheliegend, dass ein Port in Rust nicht existiert, also die libtock-rs auf eine Implementierung des Bluetooth-Stacks derzeit verzichten muss. Darüber noch in Unkenntnis befindlich haben wir uns jedoch der Aufgabe angenommen, wenigstens eine Implementierung der Bluetoothradio-Syscalls in die libtock-rs einzufügen. Diese entsprechen in etwa der C-API, jedoch mit für Rust idiomatischerem Handling von Buffern und Callbacks:

pub struct Ble<S: Syscalls, C: Config = DefaultConfig>(S, C);
// andere Syscalls ausgelassen
impl<S: Syscalls, C: Config> Ble<S, C> {

    pub fn start_passive_scan<'share, F: Fn(Result<(), ErrorCode>, u32)>(
        scan_data: &mut [u8],
        listener: &'share PassiveScanListener<F>,
    ) -> Result<(), ErrorCode> {
        share::scope::<
            (
                AllowRw<_, DRIVER_NUM, { allow_rw::SCAN_DATA }>,
                Subscribe<_, DRIVER_NUM, { subscribe::SCAN_SUBSCRIBE }>,
            ),
            _,
            _,
        >(|handle| {
            let (allow_rw, subscribe) = handle.split();

            S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, BLE_SCAN_UPCALL>(subscribe, listener)?;

            S::allow_rw::<C, DRIVER_NUM, { allow_rw::SCAN_DATA }>(allow_rw, scan_data)?;

            S::command(DRIVER_NUM, BLE_SCAN, 1, 0).to_result()
        })
    }
}

Um das Bluetoothradio in C zu verwenden, genügt es, den simple_ble.h Header zu verwenden, der einfache Funktionalität wie das Advertisen von Sensordaten ermöglicht, ein Beispiel hierzu findet man in libtock-c/examples/services/ble-env-sensing.

References

TockOS Introduction

TockOS Kernel

Libtock-c

Libtock-rs

Pin Layout

Tock Hail IoT Development Board

BLE with Tock

Tock Bootloader