MP-WS15-02

Aus Verteilte Systeme - Wiki
Wechseln zu: Navigation, Suche

Einleitung

Moderne Autos bilden ein hochgradig verteiltes System aus verschieden Steuergeräten. Dabei ist der Austausch von Sensor- und Aktordaten zwischen den Komponenten erforderlich. Bisher reichen hierfür statische Kommunikationsbeziehungen von gängigen Bussystemen wie FlexRay oder CAN aus. Neue Technologien wie Car2X oder Car2Car benötigen allerdings flexiblere Kommunikationsbeziehungen.

Dabei müssen weiterhin die Anforderungen im Automotive-Bereich erfüllt werden. Diese sind unter anderem:

  • Mehrere echtzeitkritische Anwendungen werden auf einem Steuergerät ausgeführt. Neue Kommunikationsarten müssen daher echtzeitfähig sein.
  • Gängige Prozessoren für Steuergeräte im Auto besitzen geringe Ressourcen. Daher dürfen neue Kommunikationsarten nur einen geringen Ressourcenverbrauch aufweisen.
  • Der Speicher verschiedener Anwendungen auf einem Steuergerät muss voneinander isoliert sein.

Hierfür wird in diesem Masterprojekt die Middleware sDDS auf das Betriebssystem AUTOBEST (HSRM, easycore GmbH) portiert. sDDS ist hierbei eine von dem Labor für Verteilte Systeme entwickelte Implementierung von Teilen des OMG Standards DDS, welcher eine datenzentrierte und echtzeitfähige Middleware (Publish-Subscribe Architektur) beschreibt. Das Besondere an sDDS ist, das es für ressourcenarme Sensorknoten entwickelt wurde und so auch für den Einsatz im Automotive-Bereich geeignet ist. AUTOBEST ist ein echtzeitfähiger Mikrokernel, der für den Einsatz auf Steuergeräten im Auto konzipiert wurde. Dieser Kernel unterstützt die rückwirkungsfreie Isolation von Anwendungen in Speicher- und Zeitoperationen.

Das Masterprojekt wird von Andreas Zoor (B. Sc.) durchgeführt und mitbetreut durch Dipl. Ing. (FH) Alexander Züpke.

Aufgabenstellung

Zunächst ist sDDS auf den AUTOBEST-Kernel zu portieren. Hiernach sind die Leistungskenngrößen dieser Portierung zu bestimmen. Um zu gewährleisten, dass mehrere sDDS-Anwendungen auf einem Knoten ausgeführt werden, ist eine Architektur zu implementieren, die sDDS von seinen Applikation trennt. Die Kosten dieser Trennung sind zu erfassen.

Um dies umzusetzen, sind folgende Schritte durchzuführen:

  1. Portierung von sDDS auf AUTOBEST. Dazu gehört die Auswahl eines Entwicklungsboards sowie die Portierung eines IP-Stacks für AUTOBEST.
  2. Erfassen der Leistungskenngrößen für die Portierung
  3. Entwickeln einer Architektur zur Abgrenzung von sDDS von seinen Applikationen.
  4. Erfassen der Leistungskenngrößen nach der Abgrenzung, sowie die Bestimmung des Overheads einer solchen Abgrenzung

Grundlagen

In diesem Kapitel werden die Grundlagen für das vorgestellte Masterprojekt aufgearbeitet.

Leistungsbewertung

Die Grundlagen für die Leistungsbewertung von Systemen wurden hier von Frau Olga Dedi erfasst: Leistungsbewertung

sDDS

Im folgenden Abschnitt wird auf die relevanten Eigenschaften von sDDS für das Projekt eingegangen. Weitere Information über DDS sowie sDDS können im DDS-Standard oder in der sDDS Dokumentation nachgelesen werden.

sDDS ist eine datenzentrierte Middleware, die den DDS Standard für ressourcenarme Sensorknoten implementiert. DDS basiert dabei auf dem Publish-Subscribe-Konzept. Der Vorteil dieses Konzeptes ist, dass die Kommunikationspartner nur Nachrichten austauschen, die sie auch interessieren. Hierzu werden sogenannte Topics definiert, die Nachrichten typisieren. Die Kommunikationspartner können dann entscheiden, ob sie ein Topic publizieren, abonnieren oder einfach nicht empfangen wollen. Topics, die über sDDS ausgetauscht werden, werden in der Sprache XML beschrieben. Die entsprechende Funktionalität wird für die einzelnen Anwendungen den Anhand ihrer Topics und Einstelllung wie QoS-Eigenschaften etc. generiert. Eine genaue Beschreibung der Codegenerierung ist hier nachzulesen.

Für die jeweilige Rolle einer sDDS-Anwendung werden DataReader (Subscriber) bzw. DataWriter (Publisher) zur Kommunikation bereitgestellt. Folgende Funktionalitäten dieser beiden Entitäten wurden implementiert:

Entität Methode Charakteristik Beschreibung
DataReader DataReader_take_next_sample nicht blockierend Holt das nächste

Sample aus der sDDS-History (Empfangsqueue) und löscht es aus der History

DataReader DataReader_set_listner nicht blockierend Registriert einen

Callback für bestimmte Events auf den DataReader. sDDS unterstützt bisher nur ein Event: on_data_available

DataWriter DataWriter_write blockierend Publiziert ein neues Sample eines

Topics

OSEK: Offene Systeme und deren Schnittstellen für die Elektronik im Kraftfahrzeug

Das Standardisierungsgremium für Offene Systeme und deren Schnittstellen für die Elektronik im Kraftfahrzeug (kurz OSEK) befasst sich mit der Standardisierung von Software Architekturen für Systeme die in Kraftfahrzeugen eingesetzt werden. Einer der Standards ist der OSEK-OS Standard. Dieser beschreibt die Architektur für Betriebssysteme.

Architektur OSEK-OS

OSEK-OS ist ein Standard der OSEK und beschreibt Anforderungen sowie Konzepte für Betriebssysteme im Auto. Hierbei handelt es sich um statisch generierte Systeme. Das heißt, alle Prozesse/Task, ihre Attribute und die benötigten Ressourcen sind zur Kompilationszeit bekannt und können sich zu Laufzeit nicht mehr ändern. Im Folgenden wird die Architektur beschreiben, die im OSEK-OS Standard erfüllt werden muss.

Prozessmodell

Ein OSEK-OS konformes Betriebssystem bietet Echtzeitunterstützung für Prozesse einer Applikation an. Dabei kennt das Betriebssystem folgende Prozessmodelle:

  • Task: Hierbei wird unterschieden zwischen Basis Task und Extended Task (im folgenden weiter erklärt).
  • Interrupt Service Routinen, die vom Betriebssystem verwaltet werden.

Grundsätzlich ist die Prozessorvergabe Prioritäten-basiert allerdings werden hierbei verschiedene Level betrachtet:

  • Interrupt Level.
  • Logische Level (nur für den Scheduler).
  • Task Level.

Hierbei können für Tasks full-, non- und mixed-preemptive als Scheduling Strategien verwendet werden. Folgende Regeln gelten bei der Prozessorvergabe:

  • Interrupts haben Vorrang gegenüber Tasks.
  • Der Vorrang der Interrupts bezieht sich auf interne Interrupt Prioritäten.
  • Die Prioritäten der Interrupt Service Routinen werden statisch vergeben.
  • Die Zuteilung der Interrupt Priorität zu den Prioritäten der Interrupt Service Routinen ist abhängig von der unterliegenden Hardware und der Implementierung des Betriebssystems.
  • Prioritäten von Tasks werden statisch vom Nutzer festgelegt. Hierbei stehen hohe Prioritätsnummern für hohe Prioritäten.

Zum Verwalten der Betriebsmittel werden s.g. Betriebssystem Services bereitgestellt die mittels einer eindeutigen API bereitgestellt werden.

Task & Interrupts Management

In diesem Kapitel wir auf die Arten von Task und Interrupts im OSEK-OS Standard eingegangen.

Interrupts

In OSEK-OS werden generell zwei Kategorien von Interrupts beschrieben. Sie unterscheiden sich in der Art der Interrupt Service Routine.

  • Kategorie 1: Nutzt keinen Service des Betriebssystems und hat keinen Einfluss auf das Taskmanagement. Nach dem Beenden der Routine wird die Instruktion ausgeführt, in welcher der Interrupt auftrat. Daher ist diese Art von ISR diejenige mit dem kleinsten Overhead. Normalerweise werden diese Interrupts nicht vom Betriebssystem behandelt.
  • Kategorie 2: Erlaubt das Nutzen von Services des Betriebssystems und bietet dem Nutzer einen ISR Frame, um eigene Routinen zu implementieren. Nach dem Beenden der Routine wird ein Rescheduling angestoßen.

Task

Taskmodel OSEK-OS Standard

Generell gibt es zwei Arten von Tasks: Basic Tasks und Extended Tasks. Der Unterschied zwischen den zwei Arten liegt darin, dass Basic Tasks nicht in dem Zustand Wartend / Blockierend gesetzt werden können. Daher können Basic Tasks nur folgende Zustände annehmen: Rechenwillig, Rechnend, Ruhend. Von dem Zustand Ruhend können Basic Task aktiviert werden und in dem Zustand Rechenwillig gesetzt werden. Von diesem Zustand können sie in den Zustand Rechnend übergehen, wenn sie vom Scheduler den Prozessor zugeteilt bekommen. Hier können sie terminieren und wieder in den Zustand Ruhend gehen oder wieder in den Zustand Rechenwillig gehen, wenn einem höherprioren Task der Prozessor zugewiesen wird. Die folgende Abbildung veranschaulicht dies.

Extended Task im OSEK-OS Standard

Extended Task können zudem den Zustand Wartend annehmen. In diesem können sie aus dem Zustand rechnend gesetzt werden, wenn sie auf Events warten (siehe Abschnitt Events). Aus dem Zustand wartend können sie mittels eines Events (erklärt im Abschnitt Events) in den Zustand Rechenwillig gesetzt werden. Die folgende Abbildung veranschaulicht dies. Das Warten auf Events ist dabei im OSEK-OS Standard die einzige Möglichkeit zu blockieren.

Events

Events sind Objekte, die von einem konformen Betriebssystem zur Synchronisation genutzt werden, allerdings werden sie nur von Extended Tasks unterstützt. Jedem Task werden Events zugeordnet und werden durch eine 32-Bit Maske maskiert. Wird ein Task gestartet, wird diese Maske vom Betriebssystem genullt. Die Art der Events sind abhängig von der Applikation und können individuell für Tasks konfiguriert werden. Sie können u. a. genutzt werden, um binäre Kommunikation zwischen Tasks zu realisieren (Nachrichtenaustausch), Verfügbarkeit von Betriebsmitteln zu regeln oder auch als Timer Mechanismus genutzt werden.

Jeder Task (also auch Basic Tasks) und ISR der Kategorie zwei können Events auslösen. Events können nur vom besitzendem Task wieder in der Maske zurückgesetzt werden. Das Betriebssystem muss folgende Services für Events bereitstellen:

  • Absetzen von Events.
  • Rücksetzen von Events.
  • Abfragen, ob ein Event ausgelöst wurde.
  • Warten, bis ein Event eingetroffen ist.

Ressourcenmanagement

Ressourcen bedeuten im Betriebssystem und auch im OSEK-OS Kontext Betriebsmittel wie: Rechenzeit auf dem Prozessor, Speicher, gemeinsam genutzter Speicher von verschiedenen Anwendungen (shared memory), etc. Damit Betriebsmittel verschiedenen Tasks zu geordnet werden können, muss ein Betriebssystem Mechanismen hierfür bereitstellen. Dies wird unter dem Begriff Ressourcenmanagement zusammen gefasst. Um das Ressourcenmanagement durchzuführen, definiert der OSEK Standard das sog. OSEK Priority Ceiling Protocol. Mit diesem Protokoll werden Deadlocks und dir Priority Inversionverhindert. Über das Protokol kann weiterhin der Wechselseitige Ausschluss sichergestellt werden. Zudem können verschiedene Scheduling Strategien zur Prozessorvergabe genutzt werden. Im folgenden Abschnitt wird näher auf das Priority Ceiling Protocol und die Scheduling Strategien eingegangen.

OSEK Priority Ceiling Protocol

Das OSEK Priority Ceiling Protocol basiert auf folgenden Anforderungen:

  • Bei der Systemgenerierung muss jeder Ressource eine eigene Prioritätsgrenze (Priority ceils) statisch zugeteilt werden. Diese dienen als Obergrenze für Tasks, die auf diese Ressource zugreifen dürfen.
  • Wenn die Priorität eines Tasks kleiner ist als die Grenze der Ressource, so bekommt der Task die Prioritätsgrenze als Priorität, solange er auf diese zugreift.

So ist sichergestellt das alle Tasks, die auf die Ressource zugreifen, nicht gleichzeitig auf die Ressource zugreifen können, da sie eine kleinere oder die gleiche Priorität besitzen, wie der Task der auf die Ressource zugreift. Tasks, die nicht auf Ressourcen zugreifen können, werden nicht in den Zustand laufend gesetzt und müssen warten, bis der aktuelle Besitzer die Ressource wieder freigibt.

OSEK-OS bietet die Möglichkeit, das Ressourcenmanagement für ISRs anzuwenden. Dies funktioniert äquivalent zu dem Ressourcenmanagement für Tasks, nur das hierfür virtuelle Prioritäten genutzt werden, die nur an die ISR vergeben werden. Virtuelle Prioritäten sind stets höher als Task-Prioritäten.

Folgende Abbildung zeigt das Verhalten des Priority Ceiling Protocol:

Priority Ceiling Protocol

Dabei sind die Tasks wie folgt priorisiert: Task T0 ist der höchstpriore Task, er besitzt eine höhere Priorität als die Prioritätsgrenze. Die Tasks T1 bis T3 haben absteigende Prioritäten, die niedriger sind als die Prioritätengrenze.

Das Priority Ceiling Protocol ermöglicht dann die in OSEK-OS definierten Scheduling Mechanismen umzusetzen. Diese werden hier weiter erläutert.

Interne Resourcen

Interne Ressourcen können nicht vom Benutzer gesehen werden und somit nicht über Betriebssystem Services angesprochen, zugewiesen oder freigegeben werden. Sie werden strikt intern in einer definierten Menge von Systemfunktionen verwendet. Das Managen von internen Ressourcen wird, wie bei Standard Ressourcen, über das OSEK Priority Ceiling Protocol vollzogen.

Interne Ressourcen werden in der Systemgenerierung einem Task zugeordnet. Wird dieser Task gestartet und wurde ihm die interne Ressource noch nicht zugewiesen, wird ihm diese sofort zugewiesen und bekommt die Prioritätsgrenze der Ressource als Priorität gesetzt. Wird ein Rescheduling eingeleitet so verliert der Task die interne Ressource wieder.

Scheduling Strategien

Generell können im OSEK-OS Standard drei Scheduling-Strategien verwendet werden: Full-preemptive, Non-preemptive und mixed preemptive. Unter den Strategien Full-preemptive bzw. Non-preemptive versteht man die klassischen Strategien von unterbrechbaren bzw. nicht unterbrechbaren Aktivitätsträgern. Mixed Scheduling hingegen ist eine Kombination aus beiden Strategien. Hierzu werden Tasks in Gruppen eingeteilt. Tasks die zu einer Gruppe gehören, können nur von Tasks unterbrochen werden die eine höhere oder gleiche Priorität besitzen als der höchst priore Task.

Alarms und Counter

Zunächst können im OSEK Standard so genannte Counter konfiguriert werden. Diese sind einfache Hardware- oder Software-Zähler, die ein Register hochzählen. An diesen Zählern ist es möglich, sogenannte Alarme zu registrieren. Feuert ein Alarm, kann je nach Konfiguration ein Event an eine Task gesendet werden oder eine Callback Routine ausgeführt werden. Counter müssen statisch vor der Systemgenerierung konfiguriert werden.

Error Handling

Für das Error Handling werden u. a. sogenannte Hook Funktionen genutzt. Hook Funktionen sind Callback Funktionen die vom Betriebssystem zu bestimmten Ereignissen wie startup, shutdown oder Fehlerbehandlung aufgerufen werden. Der Unterschied zu normalen Callbacks ist, dass sie nur ein Subset der API Funktionen nutzen können und nicht von Kategorie 2 Interrupts unterbrochen werden können. Zudem besitzen sie eine höhere Priorität als alle anderen Tasks.

Generell werden im OSEK Standard zwischen Application Errors und Fatal Errors unterschieden. Applikation Error werden ausgelöst, wenn ein Service vom Betriebssystem nicht korrekt ausgeführt werden konnte. Hier wird ein Error Code zurückgegeben. Der Nutzer kann entscheiden, wie er mit dem jeweiligen Fehler umgeht. Bei Fatal Errors kann das System nicht mehr die Korrektheit der internen Daten sicherstellen. Wird diese Fehlerart ausgelöst, wird die Hook zum Herunterfahren aufgerufen.

Konformitätsklassen

Um zu gewährleisten, dass ein Betriebssystem für seinen Anwendungsbereich minimal gehalten werden kann, definiert OSEK-OS vier verschiedene Konformitätsklassen. Diese beschreiben die Eigenschaften, die Tasks erfüllen müssen, wenn das Betriebssystem eine der Klassen umsetzen soll. Die Klassen sind dabei aufwärtskompatibel.

  • BCC1: Diese Klasse erlaubt nur Basic Tasks, wobei diese nur einmal aktiviert werden können und eine Priorität nur einem Task zugewiesen werden kann.
  • BCC2: Hier gibt es alles aus der Klasse BCC1 nur das mehrere Tasks die gleiche Priorität haben und auch mehrfach aktiviert werden können
  • ECC1: Genau wie BCC1, nur das auch Extended Task erlaubt sind.
  • ECC2: Genau wie BCC2, nur das auch Extended Task erlaubt sind (Nur Basic Task sind mehrfach Aktivierbar).

Automotive Open System Architecture

Die Automotive Open System Architecture (AUTOSAR) ist eine Erweiterung des OSEK-OS Standard. Die Erweiterungen von AUTOSAR in Bezug auf OSEK-OS sind u. a. folgende Konzepte:

  • Unterstützung von Speicherschutz, OS-Applikationen.
  • Zeitüberwachung (z. B. Deadline detection).
  • Schedule tables.

AUTOBEST

Überblick AUTOBEST

AUTOBEST ist ein statisch konfigurierbarer Mikrokernel. Es ist dabei möglich ihn so zu Konfigurieren, dass er den AUTOSAR oder ARINC 653 Standard erfüllt. Daher bietet AUTOBEST strikte Partitionierung von Speicher- und Zeitbereichen, wie es von ARINC 653 gefordert wird. Das Taskmodell von AUTOBEST ist eine Vereinigung der Modelle von AUTOSAR und ARINC 653 und bietet volle Unterstützung der AUTOSAR oder ARINC 653 API. Dabei befinden sich die Unterschiede zwischen beiden Standards nur außerhalb des Mikrokernel. Die verschiedenen API-Unterstützungen werden Personalities genannt. Die folgende Abbildung veranschaulicht den Aufbau von AUTOBEST.


Partionierungskonzept ARINC 653

Beispiel: Partitionierungskonzept im ARINC 653 Standard

Der ARINC 653 Standard definiert ein Konzept zur Speicherabgrenzung des Userspaces, sowie eins zur Einteilung von Rechenzeit in Zeitscheiben. Zur Speicherabgrenzung werden verschiedene Partitionen definiert. Einer einzelnen Partition werden dann Tasks zugeordnet, die nur auf den Speicher ihrer Partition zugreifen können. Ein Beispiel hierfür zeigt folgende Abbildung:

Hier wird der Userspace in vier definierte Partitionen P1 bis P4 aufgeteilt. Auf diesen könnten die verschiedenen Tasks, die das System ausführt, je nachdem wie Sicherheitskritisch sie sind, aufgeteilt werden. In der Partition P1 befinden sich dann die Tasks, die am kritischsten sind. Die restlichen Tasks werden absteigend in die übrigen Partitionen aufgeteilt.

Zeitpartitionierung im ARINC 653 Standard

Bei dem Aufteilen der Rechenzeit in Zeitscheiben (Timepartitioning), werden so genannte Zeipartitionen (Timepartitions) definiert. Diese Zeipartitionen definieren eine Zeitscheibe mit einer Ausführungsdauer. Die Menge an Timepartitions ermöglichen dann die Erstellung einer Periode indem eine Reihenfolge für die Zeitscheiben definiert wird. In den jeweiligen Ausführungsdauern der Zeitpartitionen werden nur Tasks aus den zugeordneten Speicherpartitionen ausgeführt. Die einzelnen Tasks der aktiven Partition werden dann wieder nach Prioritäten geplant. Zudem ist es möglich, einen Zeitbereich für die Idle Task des Betriebssystems zu reservieren. Die folgende Abbildung zeigt eine mögliche Aufteilung einer solchen Periode (auch Major Time Frame genannt).

Taskmodell

AUTOBEST erweitert das AUTOSAR Taskmodell um die Task Eigenschaften von ARINC 653. Daher gilt folgendes Zustandsdiagramm für Tasks von AUTOBEST.

Zustände der Tasks von AUTOBEST

Die roten Elemente des Diagramms zeigen die neuen Elemente im Bezug auf das OSEK Taskmodell. Für die neuen Zustandswechsel anhalten und unterbrechen bietet AUTOBEST API Funktionen an. Des Weiteren nutzt AUTOBEST Futex Waiting Queues als Synchronisationsmechanismus. Futex Wait Queues sind statisch konfigurierbare Queues, auf denen Tasks warten können, wenn sie leer bzw. voll sind. Sie werden zur Konstruktion von höheren Synchronisationsobjekten wie Semaphore genutzt. Zudem wird das OSEK Taskmodel um periodische Tasks erweitert. Besonders an den periodischen Tasks ist, dass die Perioden zur Laufzeit gesetzt werden und es möglich ist zu erfassen, wie oft die Deadline der Periode überschritten wurde.

Kofiguration von AUTOBEST

Für die statische Generierung von AUTOBEST ist eine Konfigurationsdatei zu erstellen. Hier werden die gewünschten Partitionen, Scheduling Pläne (Timepartioning), Tasks usw. über XML definiert. Der Folgende Codeausschnitt zeigt ein Beispiel einer solchen Konfigurationsdatei:

<system timeparts="3" period="100000000" ipi_actions="0" kern_stack_size="1536"
kern_contexts="12" nmi_stack_size="256" nmi_contexts="8" idle_stack_size="256"
idle_contexts="4">
	<!-- Counters -->
	<!-- a hardware counter with in-kernel callbacks (board system timer)
-->
	<counter name="SystemTimer" type="hw" maxallowedvalue="4294967295"
ticksperbase="1" mincycle="1"
			 cpu="0"
			 register="system_timer_register"
			 query="system_timer_query"
			 change="system_timer_change"
	 />

	<!-- Time partition schedules. Last window defines schedule duration.
Duration must match system period. -->
	<!-- No holes allowed, use a dummy timepart instead -->
	<!-- NOTE: first table is taken as default at boot time -->
	<schedule name="mysched1">
		<!-- must be sorted by offset -->
		<window offset="0" duration="100000000" release="1"
timepart="0"/>
	</schedule>

	<!-- System level HM tables. The first table is taken as default at boot
time -->
	<hm_table name="default">
	</hm_table>

	<partition name="sdds" max_prio="200" cpu="0" timepart="0"
mode="COLD_START"
            flags="PART_FLAG_RESTARTABLE" init_hook="_start"
sched_state="__sys_sched_state" sda1_base="__sda1_base" sda2_base="__sda2_base">
		<layout ldh="demo_stm32f4/app.ld.h"
dummy_elf="demo_stm32f4/app.dummy.elf" final_elf="demo_stm32f4/app.elf"
bin="demo_stm32f4/app.bin">
			<range start="__data_start" end="__bss_end"/>

			<section name="text" type="rom" start="__text_start"
end="__rom_data_end"/>
			<section name="data" type="ram" start="__data_start"
end="__bss_end"/>
		</layout>

		<!-- give access to network chip -->
		<rq name="ETH MAC" resource="ETH MAC" size="0x2000" read="1"
write="1" exec="0" cached="0"/>

		<!-- initial task -->
		<hook name="_start" prio="100" regs="yes" fpu="no" contexts="5">
			<invoke entry="__sys_startup" arg="_start"
stack="__stack_main" stack_size="1024"/>
		</hook>
		<task name="main" prio="100" regs="yes" fpu="no" contexts="8"
blocking="yes">
			<invoke entry="main_entry" arg="" stack="__stack_main"
stack_size="1024"/>
		</task>
		<!-- ISR -->
		<isr name="ETH_IRQ" prio="103" vector="61" unmask="yes"
regs="yes" fpu="no" contexts="5">
			<invoke entry="eth_irq_handler" arg=""
stack="__stack_isr" stack_size="1024"/>
		</isr>
	</partition>

</system>

In dem Beispiel wird AUTOBEST für ein STM32F4 Board generiert, mit einer Partition für eine Applikation. Weiterhin wird nur die Timepartition 0 definiert, diese muss immer für AUTOBEST definiert werden. Wie man an dem Beispiel sieht, werden zwei ELF-Dateien erzeugt, eine app.dummy.elf und die app.elf. Die dummy elf-Datei dient zur Erfassung der Speichergröße der Partition, indem die Segmentinformationen ausgewertet werden. So könne über diese Informationen die Startadressen der Partitionen bestimmt werden. Sind diese bekannt wird die Applikation mit den bekannten Linking Informationen, für das Binary neu kompiliert werden. Weiterhin existiert ein Hook der zum startup genutzt wird. Hiernach wird der Task main ausgeführt, dies ist der einzige Task in der Partition sdds. Des Weiteren wurde noch Lese- und Schreibzugriff auf den Speicher des Ethernet Controllers des Boards konfiguriert. Ebenfalls wird eine ISR zum Empfangen von Ethernet Paketen konfiguriert. Eine weitere Interrupt-Quelle in diesem Beispiel ist der konfigurierte System-Timer.

Kommunikation zwischen den Partitionen

In AUTOBEST gibt es generell zwei Möglichkeiten des Nachrichtenaustausches zwischen zwei Partitionen. Einmal die asynchronen Interpartition Events (IPEV) sowie die synchronen Remote procedure calls (RPC). Die Interpartition Events sind prinzipiell zu benutzen wie die Event Kommunikation zwischen Tasks in der gleichen Partition. Die einzigen Unterschiede sind, dass sie in der Konfiguration für AUTOBEST definiert werden müssen und sie über einen Systemaufruf sys_ipev_set abgesetzt werden müssen. Das folgende Beispiel zeigt, wie ein IPEV zu konfigurieren ist:

...
    <ipev name="reader_callback_sdds_app2" partition="app2"
task="cb_mng_sdds_app2" bit="0"/>
...

Durch die Angabe der Partition und des Task-Namen, wird dem Kernel bekannt gegeben an welche Task das Event gesendet wird. Das Attribut bit gibt an, welches bit der 32 Bit Maske (für OSEK-Events) gesetzt wird. So ist es durch den IPEV Mechanismus möglich, eine asynchrone Kommunikationsbeziehung zwischen zwei Task aus verschieden Partitionen abzubilden. Dies kann z.B- für die Implementierung einer Callback Funktionalität zwischen zwei Applikationen genutzt werden.

Die RPC hingegen bieten die Möglichkeit, Client-Server-Architekturen umzusetzen. Hierfür werden im AUTOBEST Kernel RPC Calls auf sogenannte Invokable Tasks abgebildet. Führt eine Task einen RPC aus, wird der Invokable Task rechenwillig gesetzt und der aufrufende Task blockiert, solange bis er eine Antwort (RPC reply) bekommt. Der Invokable Task muss, wie auch der RPC, in der Konfiguration von AUTOBEST angeben werden. Hierbei können jeweils Prioritäten für den Call selber, sowie für den Invokable vergeben werden. Die Priorität des ausgeführten RPCs ist dann das Maximum aus Priorität des Calls, des Invokables und des aufrufenden Tasks.

Der folgende Auszug aus der Konfigurationsdatei für AUTOBEST zeigt, wie ein RPC zwischen zwei Partitionen konfiguriert wird.

...
<partition name="sdds" max_prio="200" cpu="0" timepart="0" mode="COLD_START"
flags="PART_FLAG_RESTARTABLE"
   init_hook="_start" sched_state="__sys_sched_state" sda1_base="__sda1_base"
sda2_base="__sda2_base">
   ...
   <invokable name="sdds_rpc_sdds_app1" prio="102" regs="yes" fpu="no"
contexts="5" blocking="yes">
       <invoke entry="sdds_rpc_sdds_app1" arg="" stack="__stack_rpc_sdds_app1"
stack_size="1024"/>
   </invokable>
   ...
</partition>
<partition name="app1" max_prio="200" cpu="0" timepart="0" mode="COLD_START"
flags="PART_FLAG_RESTARTABLE"
   init_hook="_start" sched_state="__sys_sched_state" sda1_base="__sda1_base"
sda2_base="__sda2_base">
   ...
   <rpc name="rpc_sdds_sdds_app1" partition="sdds"
invokable="sdds_rpc_sdds_app1" prio="102" arg=""/>
   ...
</partition>
...

Im Beispiel können nun Tasks aus der Partition app1 den RPC rpc_sdds_sdds_app1 absetzten. Dies hat zur Folge, dass der Invokable Task sdds_rpc_sdds_app1 aus der Partition sdds rechenwillig gesetzt wird und ausgeführt wird. Der aufrufende Task des RPC blockiert so lange, bis der Invokable Task ein RPC reply ausgeführt hat und ihm so mitteilt, dass die Bearbeitung des RPC beendet ist.

Da AUTOBEST-Tasks bei einem RPC maximal einen long Wert austauschen können (einen als Parameter für den RPC, einen als Rückgabewert des RPCs), bzw. bei einem IPEV sogar gar keine Daten ausgetauscht werden können, bietet AUTOBEST zudem die Möglichkeit, Shared Memories (SHM) zu definieren. So können mehr Daten bzw. überhaupt Daten ausgetauscht werden. Ein SHM besitzt eine feste Größe, die in der Konfiguration des Systems definiert wird. Des Weiteren müssen für die beteiligten Partitionen die Zugriffsrechte entsprechend gesetzt werden. Das folgende Beispiel zeigt wie ein SHM für AUTOBEST konfiguriert wird.

...
<shm name="sdds_shm_sdds_app1" type="ram" size="0x20" description="SHM for the
application: sdds_app1" cpu="0"/>
...
<partition name="sdds" max_prio="200" cpu="0" timepart="0" mode="COLD_START"
flags="PART_FLAG_RESTARTABLE"
   init_hook="_start" sched_state="__sys_sched_state" sda1_base="__sda1_base"
sda2_base="__sda2_base">
    ...
    <shm_access shm="sdds_shm_sdds_app1" read="1" write="1" exec="0"
cached="1"/>
    ...
</partition>
...

Im Beispiel wird das SHM sdds_shm_sdds_app1 definiert, wobei die Task der Partition sdds Lese- und Schreibzugriff auf das SHM bekommt.

Zur Kommunikation zwischen Tasks in verschiedenen Partitionen bietet AUTOBEST also die Möglichkeit einer synchronen und asynchronen Kommunikationsbeziehung. Für die synchrone Kommunikation wird ein RPC-Mechanismus bereitgestellt, der es ermöglicht, Client-Server-Architekturen umzusetzen. Für eine asynchrone Kommunikation bietet AUTOBEST den IPEV Mechanismus an, so können z. B. Callbacks zwischen Partitionen implementiert werden. Zum allgemeinen Datenaustausch können zusätzlich noch Shared Memories definiert werden.

Analyse

In diesem Kapitel werden die Anforderungen der jeweiligen Teilaufgaben vorgestellt.

Auswahl Netzwerk Stack

Da AUTOBEST nativ keinen Netzwerk-Stack besitzt, muss für die Portierung von sDDS ein Netzwerk-Stack hinzugefügt werden. Hierzu wurde im Vorfeld der lwIP-Stack ausgewählt und für AUTOBEST portiert. Dieser wurde gewählt, da er leicht zu portieren ist und zudem sehr leicht zu konfigurieren ist. Um IPv6 zu unterstützen, wurde die Version 1.4.1 (zu der Zeit die neuste Version) aus dem Git-Repository ausgecheckt und portiert. Um die Code-Größe zu minimieren, ist zu überprüfen, ob die TCP-Funktionalität ausgeschaltet werden kann und welche Änderungen hierfür am Stack gemacht werden müssen.

Die Portierung des lwIP-Stacks wird dabei im Vorfeld von Herr Züpke übernommen. Die Konfigurationen am Stack werden dann von Herr Zoor durchgeführt.

Auswahl Hardware-Plattform

Für die Auswahl der Hardware-Plattform sind die Anforderungen von sDDS sowie der eventuelle Aufwand, AUTOBEST auf die Plattform zu portieren, zu beachten. Das Einsatzgebiet von sDDS sind kleine Sensornetze im Bereich Internet der Dinge (IoT), die über Funk kommunizieren. Um alle Funktionen von sDDS, wie das Discovery (Gegenseitiges Auffinden von Knoten) nutzen zu können, wird zusätzlich IPv6 vorausgesetzt. Folgende Plattformen die im Bereich IoT eingesetzt werden, sind vorhanden und stehen zur Auswahl:

  • Spark Core
  • Spark Photon
  • ESP826

Der Nachteil an diesen Plattformen ist, dass AUTOBEST noch auf keine dieser Plattformen portiert ist. Zudem müsste ein Treiber für die WLAN-Schnittstellen implementiert sowie der lwIP-Stack portiert werden.

Die vorhandenen Plattformen, auf die AUTOBEST bereits portiert wurde, sind:

  • STM32F4 Discovery Board.
  • TMS570LS3137 Board.

Der Vorteil dieser Boards ist, dass bereits Portierungen eines älteren lwIP-Stacks existiere, die nur noch für AUTOBEST und den lwIP-Stack in Version 1.4.1 angepasst werden müssen. Ein Nachteil beider Plattformen ist, dass sie keine WLAN- oder eine andere Funkschnittstelle bereitstellen. Da allerdings Ansätze im Automotive-Bereich existieren, auch Ethernet im Auto zu nutzen, kann hier auf die Ethernet-Schnittstelle der Boards zurückgegriffen werden. Für beide Plattformen müsste aber ein Ethernet-Treiber für AUTOBEST implementiert werden.

Da eine Ethernet-Treiber im Repository des lwIP-Stacks für den Ethernet Controller des STM32F4 Discovery Boards existiert, der als Basis für den Treiber für AUTOBEST genutzt werden kann, wird ein STM32F4 Discovery Board als Hardware-Plattform gewählt. Dies minimiert den Aufwand, eine IPv6-fähige Plattform, auf der AUTOBEST portiert ist, bereitzustellen.

Portierung

Um die erste Teilaufgabe des Projektes umzusetzen, ist es nötig, sDDS auf AUTOBEST zu portieren, und die Leistungskenngrößen anhand dieser Portierung zu bestimmen. Diese können dann als Grundlage genutzt werden, um den Overhead der Speicherisolation von sDDS und seinen Anwendungen zu bestimmen. Für die Portierung von sDDS auf AUTOBEST liegen foglende Komponenten von AUTOBEST vor:

  • Ein BSP (Boad Suport Package) für das STM32F4 Discovery Board.
  • Eine Portierung des lwIP-Stacks mit einer TCP Echo Anwendung auf IPv4 (portiert von Herrn Züpke).
  • Eine malloc() und free() Portierung für AUTOBEST (portiert von Herrn Züpke).

Hierauf aufbauent, wird für die Portierung, eine sDDS-Applikation zusammen mit dem lwIP-Stack, dem Ethernet-Treiber sowie die sDDS-Middleware in eine Partition gepackt. Die Analyse von sDDS zeigt, dass sDDS bereits für folgende Plattformen portiert wurde:

Nach der weiteren Analyse sind für eine Portierung des Ist-Zustandes von sDDS die folgenden Interfaces zu implementieren:

  • Memory. Hier wird die dynamische Allokation von Speicher für die jeweiligen Plattformen implementiert.
  • Task. Hier werden zeitverzögerte Aufgaben implementiert, diese können periodisch sein oder nicht.
  • Thread. Eine Thread API, die im sDDS benutzt wird.
  • NodeConfig. Diese Schnittstelle bietet knotenspezifische Konfigurationsmöglichkeiten (bisher nur Berechnung der Node-ID) an.
  • Log. Hier werden die Funktionalitäten für den sDDS Logging Mechanismus bereitgestellt.
  • UDP. Hier werden die Funktionalitäten für die Netzwerkkommunikation zwischen den Konten auf UDP Basis bereitgestellt.

Da AUTOBEST ein statisches Betriebssystem ist, gibt es keine Möglichkeit, Speicher dynamisch zu allokieren. Um also das Memory Interface zu implementieren, muss für AUTOBEST ein Mechanismus implementiert werden, der die dynamische Speicherallokation auf einem statischen Speicherblock imitiert. Hierfür wird auf die Malloc-Porteriung zurückgegriffen werden. Diese erwartet in ihrer Initialisierung eine Adresse auf einen Speicherblock und teilt dann Segmente aus diesem Speicherblock dynamisch zu.

Um den Tasking-Mechanismus des sDDS zu nutzen, muss eine Architektur entwickelt werden, um das zeitliche verzögerte Ausführen von Funktionen zu unterstützen. Dabei können die Funktionen periodisch oder auch nur einmal ausgeführt werden.

Die sDDS-Threads müssen auf AUTOBEST-Tasks abgewandelt werden. Hierfür kann im AUTOBEST ein Pool an Dummy Tasks definiert werden, die dann über das Thread Interface von sDDS initialisiert und gestartet werden. Wenn der Pool an Dummy Tasks aufgebraucht ist, muss das dem Aufrufer mitgeteilt werden. Hierbei ist auf die sDDS Return Codes zurück zugreifen.

Bei der Berechnung der Node-ID muss eine Eindeutige 16 Bit ID berechnet werden. In den meisten Portierungen werden hierzu die letzten zwei Byte der MAC-Adresse zurückgegeben. Dies kann für AUTOBEST adaptiert werden.

Um das UDP Interface zu implementieren, muss die Netzwerk Funktionalität von sDDS auf den lwIP-Stack umgesetzt werden. Hierfür kann das Netconn Interface des Stacks genutzt werden.

Damit der Logging-Mechanismus des sDDS unterstützt werden kann, wird die Funktionalität eines vprintf() benötigt. AUTOBEST liefert ein vprinft() mit, daher kann dies für die Portierung genutzt werden.

Da zeitgleich das sDDS in einem weiteren Masterprojekt weiterentwickelt wird, sind noch folgende Interfaces hinzugekommen:

  • TimeMng. Dieses Interface wird für die Zeiterfassung im sDDS genutzt.
  • Mutex. Dieses Interface stellt Funktionen für einfache Mutexe bereit.

Das TimeMng Interface definiert Funktionen zum Erfassen von relativen Zeitpunkten. Zudem bietet es Funktionen zur Bestimmung der Differenz eines bereits gespeicherten Zeitpunktes zur aktuellen Systemzeit. Hierbei können 16- oder 32-Bit Zeitstempel genutzt werden. Da das Interface nur die relative Zeiterfassung bereitstellt, können alle Funktionen mittels des Systemaufrufs sys_gettime() implementiert werden.

AUTOBEST liefert keine Mutexe mit. Um Mutexe auf AUTOBEST zu implementieren, kann das Priority Ceiling Protocol ausgenutzt werden. Dies ist deshalb möglich, da alle Tasks einer Partition auf einem zugewiesenen Prozessorkern ausgeführt werden. So muss einfach die Priorität eines Task auf die maximale Priorität gesetzt werden, wenn das Mutex gelockt wird, und beim Wiederfreigeben muss die alte Priorität wiederhergestellt werden. So ist der wechselseitige Ausschluss gewährleistet.

Trennung von sDDS und seinen Applikationen

Nachdem die Leistungskenngrößen der Portierung von sDDS bestimmt wurden, ist der Overhead einer Speicherisolation von sDDS und seinen Applikationen zu bestimmen. Als Grundlage dienen hierfür die Leistungskenngrößen, die für die Portierung von sDDS in einer einzigen Partition erfasst wurden.

Um eine Speicherisolation durchzuführen, bieten sich folgende Stellen an:

  • Zwischen der Interrupt-Service-Routine des Ethernet-Interrupts und dem lwIP-Stack.
  • Zwischen dem lwIP-Stack und der sDDS Middleware.
  • Zwischen der sDDS Middleware und seinen Anwendungen.
Architektur des Projektes

Die Isolierungen bieten hierbei verschiedene Vor- und Nachteile. Wenn man z.B. die ISR von dem lwIP-Stack trennt, müsste jede Anwendung ein eigener IP-Stack mitgegeben werden. Dies hätte zur Folge, dass die Codegröße der Anwendungen groß wird und der Speicherplatz auf dem Board knapp werden könnte. Ein weiterer Nachteil wäre, dass so auch für jeden Stack mindestens eine weitere IP-Adresse benötigt wird, um die Netzwerkpakete an den richtigen Stack weiterzugeben. Zusätzlich kommt zu dieser Lösung also größerer Speicherverbrauch und Mehraufwand zum Verteilen der Pakete hinzu. Somit scheint der Overhead dieser Lösung zu groß zu sein. Wenn man den lwIP-Stack von der sDDS Middleware abgrenzt, können verschiedene Netzwerk-Anwendungen den Stack nutzen. Ein Nachteil dieser Lösung ist, dass der lwIP-Stack nicht optimal an die sDDS Middleware angepasst werden kann, da andere Anwendungen evtl. andere Anforderungen an einen IP-Stack haben. Dies hat zur Folge, dass Codegöße und Speicherverbrauch höher sind als nötig. Zudem wird in dem Kontext des Projektes bewusst, dass nur sDDS zur Kommunikation mit anderen Steuergeräten bzw. Knoten ausgewählt wird. Das macht die Trennung des Stacks von der Middleware zunächst unnötig. Die Trennung von sDDS von seinen Applikationen ermöglicht, dass eine Netzwerkpartition existiert, die sDDS und alle weiteren Netzwerk Ebenen (lwIP, Ethernet) enthält. So können verschiedene Anwendungen auf einem Knoten unabhängig von Fehlern in den Netzwerk Modulen in ihren Partitionen ausgeführt werden und z.B. Sensordaten zwischenspeichern, bis die Netzwerkpartition neu gestartet wird.

Um den Speicher von sDDS von seinen Anwendungen abzutrennen, ist eine Architektur zu entwickeln, die folgenden Anforderungen erfüllt:

  • Die Trennung soll zwischen sDDS und den Anwendungen vorgenommen werden.
  • Ein Absturz von sDDS darf die Anwendungen nicht zum Absturz bringen.
  • Ein Absturz einer Anwendung darf keine andere Anwendung oder sDDS zum Absturz bringen.
  • Der Speicher von sDDS und seinen Anwendungen soll voneinander isoliert sein.
  • Die Trennung soll keine Auswirkungen auf die API von sDDS haben.
  • Der Performanceverlust durch die Trennung soll erfasst und ausgewertet werden.

Aufgrund der Anforderungen ergibt sich folgendes Architekturbild:

Es existiert eine Netzwerkpartition, die die ISR des Ethernet Controllers, den lwIP-Stack sowie das sDDS enthält. sDDS besitzt Schnittstellen, um mit den Anwendungen zu kommunizieren, so dass alle Funktionalitäten von sDDS den Anwendungen bereitgestellt werden. Die Schnittstellen dürfen sich von der sDDS-API nicht unterscheiden. Das sDDS Modul soll dabei als Server implementiert werden, der die Client-Anfragen der Anwendungen bearbeitet.

Um sDDS als Server für diverse Applikationen bereitzustellen, wird eine RPC-Architektur für folgende Funktionen des sDDS umgesetzt:

  • DataReader_take_next_sample, welche das Abholen eines Samples vom DataReader umsetzt.
  • DataWrite_write schreibt ein Sample eines Topis und kann blockieren
  • DDS_DataReader_set_listener registriert einen Callback für verschiedene DDS-Events. sDDS erlaubt es dabei nur, Callbacks für das on_data_available Event zu registrieren.

Für die Funktion DDS_DataReader_set_listener ist neben dem Registrieren von Callbacks auch das Ausführen des Callbacks zu implementieren. Zudem soll bei der Implementierung dafür gesorgt werden, dass wenn weitere Funktionen des DDS Standards im sDDS umgesetzt werden, die Architektur erweitert werden kann.

Messaufbau zum Erfassen der Leistungskenngrößen

Zunächst ist zu analysieren, welche Leistungskenngrößen für die Performance-Messung vor und nach der Trennung von sDDS und seinen Applikationen aussagekräftig sind. Um die Auswirkungen der Trennung zu messen, sind die Laufzeiten zwischen verschiedenen Messpunkte für einen Sende- bzw. Empfangsvorgang am aussagekräftigsten. Messpunkte hierfür sind der Übergang von Komponenten bzw. Modulen und der Übergang in eine andere Partition.

Damit die Ausführungszeiten zwischen diesen Übergängen erfasst werden können, wird eine Tracing-Architektur benötigt. Diese soll das Einleiten der Messpunkte markieren, so dass die Zeit zwischen zwei Messpunkte als Maß für die Ausführungszeit dienen soll. Desweiteren ist der Fehleranteil durch diese Instrumentierung festzuhalten.

Um diese Messungen durchzuführen, gibt es generell zwei Ansätze: Interne Messungen oder externe Messung. Bei internen Messungen nutzt man Mechanismen des Systems aus, das man untersuchen will. Hier können Zeitmessungen z. B. durch Hardware-Timer durchgeführt werden. Bei externen Messungen wird externe Logik genutzt. Dabei können z.B. Events über diverse Schnittstellen nach außen kommuniziert werden, wenn Messpunkte erreicht wurden. Dadurch stellen diese Events dann Messpunkte für die externe Logik da. Die externe Logik errechnet dann die jeweiligen Zeiten aus indem sie das Auftreten der Events analysiert und interpretiert. Hierbei ist es möglich für die externe Logik Offline- oder Online-Analysen zu betreiben.

Da der STM32F4 keinen Hardware-Timer besitzt und der System Call sys_gettime() von AUTOBEST zu teuer ist, eignet sich eine interne Messung nicht. Außerdem kommt hinzu, dass der Aufwand der Datenhaltung für eine interne Messung zu groß ist. Daher wird für die Tracing-Architektur eine externe Messung über die GPIO-Pins des Boards vorgesehen.

Umsetzung

Im folgenden Kapitel wird erläutert, wie die verschiedenen Anforderungen der Portierung sowie der Isolation von sDDS umgesetzt wurden.

Buildsystem

Der Code zum Projekt kann im Mecurial Repository des Projektes angesehen werden. Der Zugriff auf dies Repository erfolgt durch Alexander Züpke. Mehr Informationen zu AUTOBEST stehen hier. Weitere Informationen zu sDDS sowie den sDDS Code sind im sDDS Github Repository zu finden. Der Zugriff auf dies Repository erfolgt durch Kai Beckmann. Um sDDS-Applikationen auf AUTOBEST zu bauen, wurde ein Buildsystem entwickelt, welches die Projektstrukturen von sDDS und AUTOBEST verbindet. Das Projekt wird aus dem AUTOBEST Buildsystem gebaut. Beim Bauen der sDDS-Applikation wird über einen Softlink in das sDDS Buildsystem gesprungen und sDDS wird als Bibliothek gebaut und gelinkt. Will man sDDS abgegrenzt von seinen Applikationen betreiben, wird vor diesem Schritt die Codegenerierung zum Abgrenzen aufgerufen.

Codegenerierung bei der Isolation

Die Implementierung der Architektur wurde vorwiegend mittels Codegenerierung umgesetzt. Hierfür wurde die Skriptsprache gsl genutzt, da diese auch bereits bei der Generierung von sDDS genutzt wird. Durch die Codegenerierung wurden folgende Anforderungen umgesetzt:

  • Konfiguriern eines sDDS-Server, der über RPC Client Anfragen bearbeiten kann.
  • Sammeln aller konfigurierten Eigenschaften der Applikationen.
  • Generieren der Header Datei für die IDs für die DataReader / DataWriter.
  • Erstellen der RPC-Stubs, und der Funktionen für die Invokable Tasks der RPCs.
  • Generierenen der Struktur für die jeweiligen SHMs, sowie das Erfassen der Größe der SHMs.
  • Erstellen einer Konfigurationsdatei für die Generierung von AUTOBEST.
  • Generieren der Makefiles für die verschiedenen Applikationen.

Anpassung lwIP-Stack und Ethernet-Treiber

Zunächst musste IPv6 für den Stack aktiviert werden. Hierfür reichte es, die Präprozessorkonstanten LWIP_IPV6, LWIP_ICMP6, LWIP_IPV6_DHCP6 und LWIP_IPV6_FORWARD von Null auf Eins zusetzen. Um die Code-Größe des lwIP-Stacks zu verringern, musste die TCP-Funktionalität des Stacks ausgeschaltet werden. Hierzu muss laut Doku lediglich die Präprozessorkonstante LWIP_TCP auf Null definiert werden. Allerdings kompilierte der Stack so nicht, da Codeausschnitte in dem Modul api_lib.c nicht richtig durch Präprozessordirektiven deaktiviert wurden. Daher wurde dies für die betroffenen Funktionen netconn_close_shutdown(), netconn_close() und netconn_shutdown nachgezogen. Um den RAM-Verbrauch zu verkleinern, wurde weiterhin der Speicherbereich, den lwIP für seine eigene Speicherallokation (LWIP_MALLOC()) nutzt, von 10 KB auf 2 KB reduziert. Um weiteres Feintuning am Stack vorzunehmen, müssen die Präprozessorkonstanten in der Header-Datei lwipopt.h angepasst werden.

Da Multicast-Adressen auf Ethernet abgebildet werden, muss dem Ethernet Controller mitgeteilt werden, welche Ethernet Multicast Adressen vom Controller empfangen werden sollen. Der Ethernet Controller auf dem STM32F4 Discovery Board setzt dafür eine Hashtable ein. Hierzu wird der Hash der Ethernet Multicast-Adresse in Hardware berechnet und getestet. ob im MACHTR (64 Bit-)Register das Bit an Stelle des Hashwertes gesetzt ist. Ist das Bit gesetzt, wird das Paket angenommen, ansonsten verworfen. Um also ein Paket zu empfangen, das eine Mulitcast-Adresse besitzt, muss das entsprechende Bit im MACHTR Register gesetzt werden. Der lwIP-Stack bietet hierfür die Möglichkeit, Multicast-Adressen zu registrieren, um dem Netzwerkmodul mitzuteilen, dass Pakete von dieser Adresse angenommen werden sollen.

Der portierte Treiber lieferte allerdings keine Unterstützung, um die Hashes der Adressen zu berechnen und die Bits im MACHTR zu setzen. Daher wurde der Treiber um diese Funktionalität erweitert. Hierzu wurde die Hash Funktion CRC32 portiert und die Schnittstelle für den lwIP-Stack implementiert. Ein Workaround hierzu wäre gewesen, den Controller im Promiskuitätsmodus zu betreiben. In diesem Modus werden alle Netzwerkpakete empfangen. Dies würde aber zu viele Interrupts auslösen und so die Messwerte der Overhead Bestimmung zu sehr verfälschen. Daher war dies keine akzeptable Lösung.

Portierung

Um zunächst die Portierung von sDDS in einer Partition vorzunehmen, um den Overhead einer Abgrenzung zu erfassen, werden für sDDS zunächst alle benötigten Interfaces implementiert. Nach der Portierung werden dann die Ausführungszeiten der Phasen der Empfangs- und Sendevorgänge erfasst. Diese dienen dann als Grundlage zur Bestimmung des Overheads.

Aktivtätsdiagramm sDDS-Taskmanager


Für das Log Interface und das Memory Interface kann die Linux Implementierung genutzt werden, da für AUTOBEST eine vprintf-Implementierung existiert und malloc() und free() auf AUTOBEST portiert sind. Das Interface Network wurde mittels der LWIP Netconn API umgesetzt.

Für das Interface NodeConfig wurde die Funktion getNodeID()(die einzige Funktion) wie folgt umgesetzt. Die Funktion soll eindeutige 16 Bit ID für einen Knoten zurückgeben. Hierfür werden die letzten 16 Bit der MAC-Adresse genutzt.

Für das Interface Task wurde ein AUTBEST-Task angelegt, der sich alle Tasks, die ausgeführt werden, in einer Liste hält und nachschaut, ob es Zeit ist, sie auszuführen. Dies wird anhand der Systemzeit berechnet. Ist der Task periodisch, wird er nicht aus der Liste gelöscht, sondern sein Zeitpunkt zum Ausführen neu gesetzt. Die folgende Abbildung zeigt eine Iteration über die Liste.

Da AUTOBEST vorsieht, dass Tasks statisch konfiguriert werden, kann das Thread-Interface nicht einfach auf System Calls bzw. API Calls gepatcht werden. Daher werden in der Konfiguration von AUTOBEST zwei Tasks als Dummies konfiguriert. Diese können dann zur Laufzeit mit dem System Call sys_create_task() initialisiert und gestartet werden. Das Modul vergibt dann eine noch freie Task, wenn ein neuer sDDS Thread erstellt werden soll. Will man mehr als zwei Threads anlegen müssen in der Konfiguration von AUTOBEST weitere Dummy Tasks angelegt werden und die Direktive SDDS_AUTOBEST_NUM_TASKS in der Datei Thread.c angepasst werden. Die erste freie Task für die sDDS Threads wird mit der Direktive SDDS_AUTOBEST_FIRST_FREE_TASK_ID bestimmt und standardmäßig auf ID 4 gesetzt. Im folgenden Ausschnitt der Konfiguration von AUTOBEST wird gezeigt, wie die Dummy Task für sDDS angelegt werden.

<partition name="part1" max_prio="200" cpu="0" timepart="0" mode="COLD_START"
flags="PART_FLAG_RESTARTABLE" init_hook="_start"
    sched_state="__sys_sched_state" sda1_base="__sda1_base"
sda2_base="__sda2_base">
...
		<task name="sdds_task1" prio="101" regs="yes" fpu="no"
contexts="8" blocking="yes" timeout="yes">
			<!-- Created dynamically by sdds -->
		</task>
		<task name="sdds_task2" prio="101" regs="yes" fpu="no"
contexts="8" blocking="yes" timeout="yes">
			<!-- Created dynamically by sdds -->
		</task>
		<task name="sdds_task3" prio="101" regs="yes" fpu="no"
contexts="8" blocking="yes" timeout="yes">
			<!-- Created dynamically by sdds -->
		</task>
		<task name="sdds_task4" prio="101" regs="yes" fpu="no"
contexts="8" blocking="yes" timeout="yes">
			<!-- Created dynamically by sdds -->
		</task>
		<task name="sdds_task5" prio="101" regs="yes" fpu="no"
contexts="8" blocking="yes" timeout="yes">
			<!-- Created dynamically by sdds -->
		</task>
		<task name="sdds_task6" prio="101" regs="yes" fpu="no"
contexts="8" blocking="yes" timeout="yes">
			<!-- Created dynamically by sdds -->
		</task>
		<task name="sdds_task7" prio="101" regs="yes" fpu="no"
contexts="8" blocking="yes" timeout="yes">
			<!-- Created dynamically by sdds -->
		</task>
		<task name="sdds_task8" prio="101" regs="yes" fpu="no"
contexts="8" blocking="yes" timeout="yes">
			<!-- Created dynamically by sdds -->
		</task>
...

Das Interface für TimeMng wurde über den System Call sys_gettime() realisiert. Dieser gibt die relative Systemzeit in Nanosekunden an. Für die jeweiligen Funktionen wurden dann die Zeit entsprechend um um gerechnet. Allerdings existiert hier eine gewisse Ungenauigkeit. Die Zeit wird in einem 64-Bit Wert vom System Call zurückgegeben, da aber keine der ARM-Prozessor keine Division von 64-Bit Werten unterstützt und auch keine Software Implementierung hierfür existiert, wird eine Näherung durch Bit shifting umgesetzt.

Das Interface für Mutexe wurde über das Priority Ceiling Protocol implementiert. Ein Lock eines Mutexes entspricht dabei des setzten auf die Maximale Priorität und und unlock das setzten auf die vorherige Priorität des Task. Dies kann nur deswegen so umgesetzt werden, da bei AUTOBEST Tasks einer Partition, immer auf dem selben Prozessorkern laufen. Dieser Kern wird dabei statisch konfiguriert.

Trennung von sDDS und seinen Applikationen

Architektur der Trennung von sDDS und seiner Anwendungen

Nachdem sDDS in einer Partition mit Applikation portiert wurde und die Leistungsmerkmale erfasst wurden, wurde die Abgrenzung durch Speicherisolierung von sDDS und seinen Applikationen umgesetzt. Nachdem dies erfolgt ist, wird die Erfassung der Leistungsgrößen erneut durchgeführt. Allerdings werden auch die neu hinzugekommen Phasen durch die Trennung mit erfasst, um zu sehen, wo genau der Overhead ensteht. Nach diesem Schritt kann der Overhead dann bestimmt werden.

Für die Trennung von sDDS von seine Applikationen wurde folgende Architektur umgesetzt:

Der sDDS-Server kennt alle sDDS-Eigenschaften (Rollen, QoS, etc.) der Applikationen und generiert sein sDDS Modul so, als würde er selber diese Eigenschaften als ein Knoten umsetzen wollen. Das Sammeln der Eigenschaften erfolgt über die Codegenerierung. Über die Codegenerierung werden auch für alle Rollen der Applikationen die entsprechenden DataReader und DataWriter erstellt. Diese werden für die Kommunikation mit anderen Knoten genutzt.

Pro Applikation existiert ein RPC zum sDDS Server. Dieser RPC stellt die Schnittstelle aller individuellen Funktionalitäten der Applikation, für ihre Topics, zum Server da. Um Daten austauschen zu können, gibt es für jede Applikation ein SHM. Ein SHM beinhalten ein Segment pro Topic seiner zugehörigen Applikation. Diese Segmente werden in der Codegenerierung individuell für die Topics generiert. Folgender Codeausschnitt zeigt den Aufbau eines SHMs:

struct shm_seg_sdds_app1_ipc{
    uint8_t type;
    Ipc data;
} __aligned(8);
typedef struct shm_seg_sdds_app1_ipc shm_seg_sdds_app1_ipc_t;

struct cb_buff{
    uint8_t events[PLATFORM_AUTOBEST_CALLBACK_EVENT_BUF_SIZE];
    uint8_t read;
    uint8_t write;
    uint8_t status;
} __aligned(8);

typedef struct cb_buff cb_buff_t;

struct shm_sdds_app1{
    shm_seg_s$(app.name:c)dds_app1_ipc_t ipc_seg;
    cb_buff_t cbs;
} __aligned(8);

typedef struct shm_sdds_app1 shm_sdds_app1_t;

Im Fall, wenn der RPC für ein DataReader_take_next_sample und DataWrite_write ausgeführt wird, liest der invokable Task die Daten aus dem SHM und führt einfach die entsprechende Funktion aus und schreibt ggf. Samples wieder ins SHM. Zum Auswählen, auf welchem DataReader / DataWriter die jeweiligen Funktionen aufgerufen werden, übertragen die Applikationen eine ID via RPC-Parameter. Diese IDs werden durch eine generierte Header-Datei dem Server und den Applikationen bekanntgegeben.

Der folgende Codeausschnitt zeigt den Stub für die Funktion DataReader_take_next_sample für das Topic Ipc:

...
DDS_ReturnCode_t DDS_IpcDataReader_take_next_sample(
    DDS_DataReader self,
    Ipc** data_values,
    DDS_SampleInfo* sample_info
){
    unsigned int err;
    unsigned long recv_arg;
    // setting up the shm...
    g_shm_sdds_app1->ipc_seg.type = 0;
    err = sys_rpc_call(CFG_RPC_rpc_sdds_sdds_app1,DDS_IPC_READER_ID,-1,
&recv_arg);
    assert(err ==  E_OK);
    Ipc* addr = *data_values;
    *addr = g_shm_sdds_app1->ipc_seg.data;
    return (DDS_ReturnCode_t) recv_arg;
}
...

Hier werden der Typ des RPC (0 für die Funktion DataReader_take_next_sample) ins SHM geschrieben. Hiernach wird der RPC aufgerufen und mittels des Parameters DDS_IPC_READER_ID wird dem Server mitgeteilt, welches DDS-Objekt genutzt werden soll. Die Struktur des SHM wurde bereits bei der Erklärung der Architektur gezeigt.

Es beinhaltet einmal ein Segment für den RPC sowie den Buffer für den Callback-Mng. Das Segment für den RPC beinhaltet den Typ, welcher spezifiziert, welche Funktionalität (bisher write , take_next_sample oder set_listner) im RPC umgesetzt werden soll, sowie einen Slot zum Zwischenspeichern eines Samples (Hier Ipc).

Die Gegenstelle im Server sieht wie folgt aus:

...
switch(dds_obj_id){
        case DDS_IPC_READER_ID:
            // check if we want to register a listenr or want to take the next
sample from the history.
            if(g_shm_sdds_app1->ipc_seg.type == 0){
                Ipc* data_used_ptr = &g_shm_sdds_app1->ipc_seg.data;
                DDS_ReturnCode_t ret =
DDS_IpcDataReader_take_next_sample(g_Ipc_reader, &data_used_ptr, NULL);
                sys_rpc_reply(reply_id, (unsigned long)ret, 1);
                sys_task_terminate();
            	assert(0);
            }else if(g_shm_sdds_app1->ipc_seg.type == 2){
                //TODO: check which listener should be registered
                struct DDS_DataReaderListener listStruct = { .on_data_available
=
            			&sdds_callback_sdds_app1_ipc};
                DDS_ReturnCode_t ret = DDS_DataReader_set_listener(g_Ipc_reader,
&listStruct, NULL);
                sys_rpc_reply(reply_id, (unsigned long)ret, 1);
                sys_task_terminate();
            	assert(0);
            }
            //TODO: if in the future sdds implements more functions of the dds
standard we need to check more types for the DataReader.
            break;
...

Im Server werden für einen DataReader die Funktionalitäten für DataReader_take_next_sample (Typ 0) und DDS_DataReader_set_listener (Typ 2) bereitgestellt. Hier werden jeweils die Daten im SHM ausgewertet und die jeweilige Funktion, für den der ID zugehörigen DataReader ausgeführt. Da sDDS bisher nur eine Art von Callback (bzw. Listner) unterstützt, wird standardmäßig ein on_data_available Callback registriert, wenn der RPC zum Setzen des Listeners aufgerufen wird. Zudem wird eine Funktion als Callback gesetzt, die die Kommunikation mit dem Callback-Mng Task übernimmt. Folgender Codeabschnitt zeigt die aktuelle Umsetzung dieser "Internen-" Callback-Funktion:

...
void
sdds_callback_sdds_sdds_ipc(DDS_DataReader reader){
    // reset status
    g_shm_sdds_sdds->cbs.status = 0;
    // prepare shm.
    g_shm_sdds_sdds->cbs.events[g_shm_sdds_sdds->cbs.write] = DDS_IPC_READER_ID;
    g_shm_sdds_sdds->cbs.write = (g_shm_sdds_sdds->cbs.write + 1) %
PLATFORM_AUTOBEST_CALLBACK_EVENT_BUF_SIZE;
    // check if we have an overflow and signal it
    if(g_shm_sdds_sdds->cbs.write == g_shm_sdds_sdds->cbs.read){
        g_shm_sdds_sdds->cbs.status |= EVENT_OVERFLOW_FLAG;
    }
    unsigned int err = sys_ipev_set(CFG_IPEV_reader_callback_sdds_sdds);
    assert (err == E_OK);
}
...

Hierbei wird ein Callback Event, welche der ID des DataReaders entspricht, in den Buffer im SHM gespeichert und das IPEV zum Callback-Mng Task gesendet.

Erweiterbarkeit

Um zu einen späteren Zeitpunkt weitere DDS Funktionen zu unterstützen, muss in die Codegenerierung der Stubs und der Server Funktionalität eingegriffen werden. Hierfür sind folgende gsl Skripte anzupassen:

  • shm.gsl: Hier sind die Strukturen für die SHMs anzupassen
  • sdds_c.gsl: In diesem Skript wird die Funktionalität des Servers generiert. Hier muss für die DDS-Funktion Daten aus dem SHM ausgelesen werden und dann für den DataReader bzw. DataWriter die entsprechende sDDS-Funktion aufgerufen werden. .
  • app_files.gsl: Hier ist der Stub zum Registrieren von Callbacks des sDDS anzupassen. Zudem ist die Funktion des Callback-Mng um neue Callbacks zu erweitern.
  • sdds_topic.gsl. Hier werden die übrigen Stubs für DataWriter_write oder DataReader_take_next_sample generiert. In diesem Skript sollten weiterhin Stubs für neue DDS-Funktionen wie DataReader_read etc. erstellt werden. Hierzu muss im Stub das SHM entsprechend beschrieben werden.

Das Skript sdds_topic.gsl ist im sdds Repository unter ./sdds/scripts zu finden. Die restlichen Skripte sind im AUTOBEST Repository unter ./demos/sdds/gen_scripts/

Messaufbau zum Erfassen der Leistungskenngrößen

Um die Ausführungszeiten erfassen zu können, wurde sDDS um ein Tracing Modul erweitert. Diese Module wurde als betriebssystemabhängige Module entwickelt, da je nach System die Trace-Events anders verarbeitet werden können bzw. müssen.

Im Rahmen des Master-Projektes wurde für AUTOBEST das Tracing so implementiert, dass für die jeweiligen Tracepoints definierte Werte auf den GPIO Port D des STM32F4 Boards gesetzt werden. Um im Userspace Zugriff auf den GPIO Port zu haben wurde das AUTOBEST Board Support Package (kurz BSP) für das STM32F4 Board angepasst und die benötigten Treiber implementiert.

Um zu messen, welche Fehler diese Instrumentierung mit sich bringt, wurde der fünfte Pin des GPIO Ports D in einer Endlosschleife Ge- und Rücksetzt. Die Zeit, in der eine 1 bzw. eine 0 anliegt, ist die Zeit, die eine Task braucht, um ein Signal auf dem Port zu setzten. Die Pegel wurden mit dem Logikanalysator DigiView3100 aufgenommen und die aufgenommenen Signale mit einem Python Skript ausgewertet.

Um nun die Ausführungszeiten zu messen, wurden die Tracepoint an den entsprechenden stellen im sDDS, lwIP-Stack sowie Ethernet Treiber platziert.

Um maximale Flexibilität zu gewährleisteten, kann jeder Tracepoint dazugeschaltet werden oder nicht. Zudem können alle Sende- bzw Empfangs-Tracepoints ausgeschaltet werden.

Um die Messung für einen Empfangsvorgang durchzuführen wird der Knoten STM32F4 (mit AUTOBEST + sDDS), als Subscriber des Topic Ipc konfiguriert. Um eine Aussage treffen zu können, wann die Daten in der Applikation sichtbar sind, ohne Fehler durch ein sleep etc. zu bekommen, wurde ein on_data_available Listener registriert, der DataReader_take_next_sample aufruft und anschließend durch ein Trace-Event signalisiert, dass die Daten nun in der Anwendung sichtbar sind. Das Topic Ipc wurde ausgewählt, da es ein vorhandenes kleines (2 Byte) Topic ist und so der Anteil der Ausführungszeit zur Verarbeitung der Netzwerkdaten gering ist.

Um die Messung für einen Sendevorgang des Knoten umzusetzen, wurde eine sDDS-Applikation als Publicher des Ipc Topics konfiguriert. Diese Applikation publizierte im Zeitabstand von 20 ms ein Sample des Topic.

Für beide Vorgänge wird mit einem Linux Konten kommuniziert, der im jeweiligen Fall den Gegenpart einnimmt. Die Trace-Events werden mit dem Logikanalysator DigiView3100 aufgenommen und die aufgenommenen Signale mit einem Python Skript ausgewertet, um so die Ausführungszeiten der jeweiligen Phasen zwischen den Trace-Ponits zu ermitteln. Für die einfache Portierung von sDDS und der Version mit Abgrenzung zwischen Applikationen und sDDS sind die Messungen zu wiederholen.

Die Phasen eines Sendevorgangs sind folgende:

  • Aufruf der Funktion DataReader_write.
  • Aufbereiten (marshalling, etc) der Daten zum Versenden.
  • Ankunft und Verarbeiten der Daten im Netzwerk Modul.
  • Aufbereiten der Daten im IP-Stack.
  • Versenden der Daten im Ethernet Treiber.

Für einen Empfangsvorgang lauten die Phasen wie folgt:

  • Eintreffen des Empfangs-Interrupts im Kernel.
  • Ausführen der ISR im Userspace.
  • Verarbeiten der Daten im IP-Stack.
  • Vorbereiten der Daten für die Verarbeitung im sDDS Modul.
  • Verarbeiten der Daten im sDDS (unmarshalling, etc).
  • Speichern der Daten in der History.
  • Sichtbarkeit der Daten in der Applikation.

Um den möglichen Overhead genau nachzuvollziehen, werden nach der Trennung von sDDS und seinen Applikationen folgende weitere Zwischen-Phasen betrachtet:

  • Aufruf des RPC's
  • Auslesen der Daten aus dem SHM
  • Mitteilen des Ergebnisses an die Anwendung.

Evaluation

In diesem Kapitel werden die Auswertung der geplanten Ziele sowie die aufgetretenen Probleme aufgezeigt.

Erfasste Leistungskenngrößen

Im diesem Abschnitt werden die erfassten Leistungskenngrößen des Projektes aufgeführt.

Codegröße

Die Codegröße des Binaries, das auf das STM32F4 Boards vor der Abgrenzung geflasht wurde, betrug 54KB und das nach der Abgrenzung 61KB. Die generell hohen Zahlen für die Codegröße sind durch die Größe des lwIP-Stacks zu erklären. Hier müsste weiteres Feintuning am Stack durchgeführt werden. Der Overhead der Trennung der Speicherisolation beträgt also 7KB. Dies ist durch das Hinzukommen weiterer Funktionalität, Tasks und einer Partition zu erklären. Die Codegröße kann aber keine genaueren Aussagen treffen, wo der Code Overhead entsteht.

Ausführungszeiten

Folgende Ausführungszeiten wurden für einen Empfangsvorgang der reinen Portierung von sDDS erfasst (alle Zeitangaben sind in µs):

IRQ im Kernel ISR im Userspace lwIP-Stack UDP-Modul sDDS DataSink_processFrame Aufruf Data available Callback DataReader_take_next_sample
AVG 6,762 8,276 36,457 17,505 9,001 5,639 3,352
MIN 6,040 8,240 29,310 17,490 8,080 5,630 3,340
MAX 12,120 18,380 41,890 23,380 25,710 10,940 8,820
STD 0,074 0,428 0,178 0,125 1,194 0,081 0,075

Die Gesamtausführungszeit der Messung beträgt: 86,992 µs.

Folgende Ausführungszeiten wurden für einen Empfangsvorgang der Portierung von sDDS mit Trennung der Applikationen vom sDDS erfasst (alle Zeitangaben sind in µs):

IRQ im Kernel ISR im Userspace lwIP-Stack UDP-Modul sDDS DataSink_processFrame Aufruf Data available Callback Absetzten IPEV zum Callback-Mng Ankunft im Callback-Mng Aufruf Callback in der Applikation Absetzen RPC Ankunft Invokable Task Reply RPC Daten Lesbar in der Anwendung
AVG 6,707 8,291 36,501 17,426 8,680 5,471 1,147 20,415 1,941 1,167 8,252 3,396 7,593
MIN 6,010 8,240 31,220 17,410 7,750 5,460 1,140 20,380 1,930 1,160 8,240 3,390 7,24
MAX 12,140 18,38 43,670 20,320 25,580 8,49 3,070 27,990 5,330 4,490 13,570 6,960 10,530
STD 0,097 0,573 0,195 0,073 1,204 0,054 0,023 0,367 0,069 0,044 0.107 0,048 0,063

Die Gesamtausführungszeit der Messung beträgt: 126,987 µs.

Folgende Ausführungszeiten wurden für einen Sendevorgang der reinen Portierung von sDDS erfasst (alle Zeitangaben sind in µs):

Aufruf DataWriter_write Verpacken in SNPS Pakete UDP-Modul lwIP-Stack Ethernet-Treiber
AVG 13,909 5,482 7,145 23,175 25,567
MIN 6,010 4,090 7,130 23,140 25,550
MAX 14,030 18,060 7,390 23,570 25,580
STD 0,864 0,736 0,020 0,031 0,005

Die Gesamtausführungszeit der Messung beträgt: 75,278 µs.

Folgende Ausführungszeiten wurden für einen Sendevorgang der Portierung von sDDS mit Trennung der Applikationen vom sDDS erfasst (alle Zeitangaben sind in µs):

RPC Aufruf Ankunft Invokable Task Aufruf DataWriter_write Verpacken in SNPS Pakete UDP-Modul lwIP-Stack Ethernet-Treiber Zurück in der Applikation
AVG 8,272 1,390 13,957 5,409 7,142 23,240 16,284 13,178
MIN 6,630 1,180 8,910 4,130 7,130 23,210 16,270 13,170
MAX 8,300 18,07 14,000 6,230 7,390 23,610 16,300 13,190
STD 0,168 1,093 0,392 0,103 0,020 0,030 0,005 0,004

Die Gesamtausführungszeit der Messung beträgt: 88,872 µs.

Folgende weitere Leistungsmerkmale von AUTOBEST auf dem STM32F4 Discovery Board sind durch Benchmarks von Herrn Züpke bekannt:

Beschreibung Benötigte Durchschnittszeit in µs
Setzen eines Trace-Event auf die GPIO Pins 0,920
Aufrufen eines Null System Calls 0,457
Scheduling Aufwand eigene Partition 2,380
Scheduling Aufwand andere Partition 3,784
RPC in eigener Partition 10,955
RPC in eine andere Partition 13,290
IPEV in eigener Partition 9,368
IPEV in eine andere Partition 11,734
Kosten Partitionswechsel 1,175

Die Messwerte zeigen, dass beim Empfangen sowie beim Senden von Daten die Phasen, die nicht von der Trennung betroffen sind, keine signifikante Abweichung zu der einfachen Portierung von sDDS aufweisen. Lediglich die Phasen, die durch die Trennung von sDDS und seinen Applikationen hinzugekommen sind, beschreiben den eingebüßten Overhead. Für das Empfangen sind dies die Phasen: Absetzen IPEV zum Callback-Mng, Ankunft im Callback-Mng, Aufruf Callback in der Applikation, Absetzen RPC, Ankunft Invokable Task und Reply RPC. Der Overhead dieser Phasen beträgt 38,411 µs.

Für einen Sendevorgang beschreiben die Phasen RPC Aufruf, Ankunft Invokable Task, Aufruf DataWriter_write, Verpacken in SNPS Pakete und Zurück in der Applikation den Overhead. Der Overhead dieser Phasen beträgt 13,594 µs.

Anhand der Leistungsmerkmale von AUTOBEST auf dem Board sieht man, dass auf dieser Plattform das Scheduling und die Partitionswechsel recht teuer sind. Dadurch sind die großen Ausführungszeiten von IPEVs und RPCs zu erklären. Diese Ausführungszeiten passen wiederum zu jeweiligen Overheads der Sende- bzw. Empfangsvorgänge. Weiterhin sieht man, dass die erfassen Werte zum Setzen eines Wertes auf GPIO-Pins zu passen schein da schon die Ausführung eines Null System Calls 0,457 µs beträgt. Rechnet man nun den Overhead der Instrumentierung aus den Messergebnissen heraus, erhält man folgende Werte (in µs):

Messvorgang Gesamtzeit mit Instrumentierung Gesamtzeit ohne Instrumentierung Overhead Abgrenzung in µs Prozentanteil
Empfangsvorgang ohne Abgrenzung 86,992 80,552 0 0
Empfangsvorgang mit Abgrenzung 126,987 115,027 34,475 29,971
Sendevorgang ohne Abgrenzung 75,278 70,678 0 0
Sendevorgang mit Abgrenzung 88,872 81,512 10,834 13,291

Der Overhead-Anteil von 13,291% für Sendevorgänge und 29,971% für Empfangsvorgänge, sind bezogen auf die Leistungsmerkmale von AUTOBEST auf dem STM32F4 Discovery Board akzeptable Werte.

Probleme

In diesem Abschnitt werden die Probleme aufgezeigt, die während des Master-Projektes auftraten.

Bug im BSP

Nach dem erfolgreichen Testen der Portierung auf dem Qemu Target wurde im nächsten Schritt die Portierung auf echter Hardware getestet. Hier zeigte sich, dass beim Auslesen der Messageboxen des lwIP-Stacks ein Fehler auftrat. Die Messageboxen wurden bei der Portierung des lwIP-Stacks auf Waiting Queues im Kernel abgebildet. Der System Call sys_wq_wait() lieferte dabei den Error Code 7, welcher dafür steht, dass die übergeben Waiting Queue nicht initialisiert ist. Beim Analysieren des Fehlers fiel auf, dass dieser Fehler immer bei der Queue mit der ID 8 auftrat. Diese ist die erste Queue, die für den Stack genutzt wird und wurde definitiv initialisiert. Daher wurde der System Call näher untersucht. Hier zeigte sich, dass nach dem Eintritt in den Kernel die ID der Queue nicht mit der übergebenen ID übereinstimmte, dies deutete auf Seiteneffekte beim Interrupt Handling hin. Nach der weiteren Analyse zeigte sich, dass diese Seiteneffekte auftraten, da die Interrupts im Interruptcontroller des ARM Cortex M4 falsch priorisiert wurden, und so die Register im Kernel überschrieben wurden.

Die Analyse und das Beheben dieses Bugs, wurde gemeinsam mit Herrn Züpke durchgeführt.

Bug im Ethernet Treiber

Bei Tests im längeren Betrieb der Portierung auf der echten Hardware (STM32F4) zeigte sich, dass der Controller nach einiger Zeit keine Pakete mehr empfing. Bei der Analyse dieses Fehlers fiel auf, dass nach einiger Zeit der Interrupt, der auf einen empfangenen Frame reagiert, nicht mehr triggert. Zunächst wurde überprüft, ob die Priorisierung der Interrupts immer noch fehlerhaft war. Allerdings konnte hier kein weiterer Fehler gefunden werden. Die weitere Analyse zeigte, dass der Treiber einen Fehler im DMA Protokoll des Controllers besitzt und so irgendwann alle Receive Buffer dem LwIP Stack gehörten und so keine Buffer zum Empfangen mehr vorhanden waren.

Als Lösung wurde ein Hack implementiert, indem die Receive Buffer nach Auftreten des Fehlers neu initialisiert wurden, hierbei werden allerdings die Daten, die noch nicht vom Stack verarbeitet worden sind, verworfen.

Die Analyse und das Beheben dieses Bugs, wurde gemeinsam mit Herrn Züpke und Herrn Bommert durchgeführt.

Probleme bei der Instrumentierung

Bei den ersten Messungen der Leistungskenngrößen eines Empfangsvorganges nach der Trennung von sDDS und seinen Applikationen zeigte sich, dass die Messwerte fehlerhaft sind.

In der Phase vom Absetzen des RPC bis zur Sichtbarkeit in der Applikation waren zeitweise Messwerte über 3 ms, sowie eine Standardabweichung von 2,2 ms messbar. Beim genaueren Auswerten der Messwerte zeigte sich, dass, wenn diese Phase eine größere Ausführungszeit aufwies, zuvor Störsignale 10 ns lang auf den GPIO Pins anlagen. Dies konnten keine Signale von dem Testaufbau sein, da eine Applikation im Userspace mindestens 900 ns braucht, um ein Signal auf GPIO Pins zu setzen.

Um eine Fehlfunktion des Logikanalysators auszuschließen, wurde die Messung mit einem baugleichen Logikanalysator wiederholt, welcher das gleiche Ergebnis zeigte.

Die Störsignale lassen darauf vermuten, dass die GPIO Pins einen neuen Wert zu langsam annehmen. Um den Mehraufwand zu verringern, wurden die verschiedenen Events als Gray-Code codiert. Dies führt dazu, dass das Setzen eines neuen Events lediglich das Ändern eines Bits zur Folge hat. Desweiteren wurden Dummy-Events definiert, welche am Ende einer Messung gesetzt werden müssen, damit sich weiterhin per Trace-Event lediglich ein Bit an den GPIOs ändert.

Nachdem die Events als Gray-Code definiert wurden, sind die Messfehler nicht mehr aufgetaucht.