summaryrefslogtreecommitdiffstats
path: root/Master/Masterarbeit/thesis/tex/prototype.tex
diff options
context:
space:
mode:
authorSven Eisenhauer <sven@sven-eisenhauer.net>2023-11-10 15:11:48 +0100
committerSven Eisenhauer <sven@sven-eisenhauer.net>2023-11-10 15:11:48 +0100
commit33613a85afc4b1481367fbe92a17ee59c240250b (patch)
tree670b842326116b376b505ec2263878912fca97e2 /Master/Masterarbeit/thesis/tex/prototype.tex
downloadStudium-master.tar.gz
Studium-master.tar.bz2
add new repoHEADmaster
Diffstat (limited to 'Master/Masterarbeit/thesis/tex/prototype.tex')
-rw-r--r--Master/Masterarbeit/thesis/tex/prototype.tex114
1 files changed, 114 insertions, 0 deletions
diff --git a/Master/Masterarbeit/thesis/tex/prototype.tex b/Master/Masterarbeit/thesis/tex/prototype.tex
new file mode 100644
index 0000000..752b976
--- /dev/null
+++ b/Master/Masterarbeit/thesis/tex/prototype.tex
@@ -0,0 +1,114 @@
+\setchapterpreamble[u]{%
+ \dictum[Johann Wolfgang von Goethe]{Es ist nicht genug, zu wissen, man muß auch anwenden; es ist nicht genug zu wollen, man muß auch tun.}\bigskip}
+\chapter{Prototypische Implementierung}
+\label{chp:prototype}
+Das im vorigen Kapitel erstellte Konzept soll nun mittels einer prototypischen Implementierung realisiert werden. Sie soll als Proof Of Concept dienen und Vor- sowie Nachteile des Konzepts in der Realität ermitteln.
+\section{Implementierungsgrundsätze}
+\label{sec:generalimpl}
+Für die Implementierung des Prototyps gelten einige Grundsätze, wie sie schon im Abschnitt \ref{sec:fun:rt_req} formuliert wurden. Besondere Bedeutung fällt dem Verzicht auf die Verwendung von dynamischem Speicher auf dem Target zu. Darüber hinaus wirken sich die technischen Voraussetzungen aus Abschnitt \ref{sec:con:techbase} auf die Implementierung aus. Als Programmiersprachen kommen sowohl für das Target als auch den Host C bzw. C++ zum Einsatz.
+
+Zur Generierung der Quellcode-Dokumentation (siehe \ref{app:cd:doc}) aus den Quellcode-Dateien kommt Doxygen (\cite{Doxygen}) zum Einsatz. Dazu wird der Quellcode an entsprechenden Stellen mit Kommentaren versehen, aus denen die Dokumentation entsteht.
+
+Auf dem Target stehen zwei Alternativen zur Realisierung der \nameref{sec:fund:pip} zur Verfügung. Zum Einen bietet die POSIX-API ein Mutexattribut PTHREAD\_PRIO\_INHERIT, zum Anderen enthält das \nameref{sec:con:X2Esdk} eine Klasse x2e::Mutex, die diese Funktionalität implizit bereit stellt.
+
+Da das \nameref{sec:con:X2Esdk} boost bereits enthält, verwenden die Implementierungen die boost-Bibliotheken für
+\begin{description}
+\item[Netzwerkkommunikation] Die Bibliothek boost.Asio stellt Mechanismen für asynchrone Netzwerkkommunikation über TCP/IP bereit.
+\item[Tasks] Die Bibliothek boost.Thread stellt Mechanismen zur Verwaltung mehrerer Threads in einer Applikation bereit. Die einzelnen Tasks werden als boost-Threads implementiert. Sie kapseln transparent die nativen Thread-Konstrukte der jeweiligen Plattform, POSIX-Threads auf dem Target, Windows-Threads auf dem Host.
+\item[Container] \index{boost.Intrusive}Boost bietet verschiedene Container, die entsprechende Container aus der STL ersetzen und gegenüber diesen bestimmte Vorteile bieten, die im Abschnitt \ref{sec:proto:intru} detailliert beschrieben sind.
+\item[Lock] Boost bietet verschiedene Klassen zur Verwaltung von Locks. So erlaubt die Klasse boost::lock\_guard die Verwendung von Mutexen nach dem Prinzip RAII\index{RAII}. Dies stellt die korrekte Initialisierung einer Ressource sicher. Darüber hinaus erlaubt es diese Klasse, einen Mutex für den Gültigkeitsbereich eines assoziierten Lock-Objekts zu sperren, da der Destruktor des Lock-Objekts den Mutex implizit freigibt.
+\end{description}
+
+Darüber hinaus verlagert die prototypische Implementierung alle Berechnungen, die nicht zwingend auf dem Target ausgeführt werden müssen, auf den Host. Da auf dem Host keine Echtzeitanforderungen bestehen, kann so das Target entlastet werden.
+
+Das Listing \ref{lst:global} zeigt eine globale Header-Datei, die bei allen host-- und targetseitigen Komponenten zum Einsatz kommt. Sie stellt Datentyp-Definitionen, Präprozessormakros und --definitionen bereit, die eventuelle Unterschiede der Entwicklungsumgebungen oder Architekturen kapseln und vor der Anwendungsebene verbergen.
+
+\section{Boost intrusive container}
+\label{sec:proto:intru}
+Der Hauptgrund für die Verwendung der intrusive Container in diesem Prototypen liegt in deren Eigenschaft, keine Kopien der in ihnen gespeicherten Objekte zu erzeugen. Die Container der STL zeigen genau diese Eigenschaft, die in diesem Fall aus zwei Gründen unerwünscht ist. Abhängig vom verwendeten Allokator, allozieren STL-Container dynamischen Speicher, was hier vermieden werden soll. Weiterhin steigt durch die Kopien der gesamte Bedarf an RAM auf dem Target. Ebenso verschlechtert sich durch den Zugriff auf Objektkopien die sog. memory locality, was zu mehr Cache faults führt. Diese beeinträchtigen das Echtzeitverhalten des Systems (siehe \ref{sec:fund:cache}).
+
+Darüber hinaus bieten intrusive Container eine höhere Verarbeitungsgeschwindigkeit als STL-Container, wie in \cite{BoostIntrusivePerformance} gezeigt. In einem zeitkritischen System ist jede Verbesserung der Laufzeit willkommen, besonders wenn sie praktisch ohne Mehraufwand zu erreichen ist.
+
+\section{Asynchrone Netzwerkkommunikation}
+\label{sec:proto:asio}
+Bei asynchroner Netzwerkkommunikation blockieren Lese- und Schreiboperationen nicht. Stattdessen löst das Eintreten eines Ereignisses auf einer Verbindung den Aufruf einer entsprechenden Behandlungsfunktion (engl. Handler) für das jeweilige Ereignis aus. In dieser Arbeit handelt es sich um eine Client-Server-Kommunikation, bei der die hostseitige Implementierung den Client darstellt und die targetseitige den Server.
+
+Die Netzwerkkommunikation für Steuerkommandos läuft nach diesem Schema ab:
+\begin{enumerate}
+\item Mit der Initialisierung des Targets startet die Netzwerktask mit einem halboffenen Verbindungsobjekt zu dem sich ein Client verbinden kann. Für dieses Objekt ist ein sog. AcceptHandler konfiguriert. Sobald eine neue Verbindung zu diesem Objekt aufgebaut wird, wird der AcceptHandler im Kontext der Netzwerktask ausgeführt. Er startet ein weiteres halboffenes Verbindungsobjekt zu dem sich der nächste Client verbinden kann. Auf dem Verbindungsobjekt zu dem die neue Verbindung aufgebaut wurde, findet nun eine asynchrone Leseoperation unter Verwendung eines sog. ReadHandler statt. Diese Operation blockiert nicht und kehrt sofort zurück. Treffen nun Daten auf dieser Verbindung ein, löst dies den Aufruf des ReadHandlers im Kontext der Netzwerktask aus. Clientseitig hält ebenfalls ein asynchrones Lesen mit konfiguriertem ReadHandler die Verbindung offen.
+\item Eine Benutzeraktion im CanEasy-Plugin löst das Versenden einer Anfrage über das Netzwerk aus. Nach dem Versenden der Daten über das Netzwerk wird der sog. WriteHandler aufgerufen.
+\item Der ReadHandler auf dem Server wird aufgerufen, sobald die Daten des Clients empfangen wurden. Er wertet die Daten aus, löst entsprechende Aktionen aus und versendet die Antwort an den Client. Der serverseitige WriteHandler der Antwort versetzt nach Versand der Antwort die Verbindung wieder in den Lesezustand mit konfiguriertem ReadHandler, so dass die nächste Anfrage empfangen werden kann.
+\item Der clientseitige ReadHandler wird bei Empfang der Antwort aufgerufen. Er wertet diese aus, führt entsprechende Aktionen aus und hält die Verbindung mittels eines erneuten asynchronen Lesens mit einem ReadHandler die Verbindung offen.
+\end{enumerate}
+
+Die Übermittlung aufgezeichneter Busnachrichten vom Server zum Client verläuft umgekehrt. Hier initiiert der Server die Datenübertragung, indem er eine Netzwerkbotschaft mit den Daten der empfangenen Busbotschaft an den Client schickt. Der Client antwortet mit einer entsprechenden Netzwerkbotschaft zur Bestätigung des Empfangs.
+
+Diese Abläufe sind durch die Bibliothek boost.Asio vorgegeben. Die Kernkomponente einer auf boost.Asio basierenden Implementierung bildet ein asynchrones IO-Dienst-Objekt. Dieses führt alle tiefer liegenden Operationen der Kommunikation aus und steuert die Aufrufe der Handler. Der IO-Dienst läuft im Kontext der Task, in der die Methode \texttt{run} des Objekts aufgerufen wird. Im nornmalen Betrieb kehrt dieser Aufruf nicht zurück. Alle folgenden Netzwerkoperationen erfolgen nun im Kontext des Threads der Netzwerktask durch Aufrufe der Handler durch das IO-Dienst-Objekt in diesem Thread.
+
+Die Implementierung hat gezeigt, dass bei der asynchronen Übertragung basierend auf boost.Asio mehrere Netzwerknachrichten auf einmal übertragen werden können. Dies bedeutet, dass der Empfangspuffer bei Aufruf des Lesehandlers mehrere Nachrichten enthalten kann. Um dies überprüfen zu können, übergibt boost.Asio beim Aufruf des Lesehandlers die Anzahl der empfangenen Bytes. Der Lesehandler ist nun dafür zuständig, alle Bytes der Übertragung zu konsumieren. Dazu versucht er solange Netzwerknachrichten aus dem Empfangspuffer zu lesen und zu verarbeiten, bis er alle empfangenen Bytes verarbeitet hat. Erst dann startet er eine erneute asynchrone Leseoperation. Ohne diese Schleife könnten Netzwerknachrichten verloren gehen.
+
+Der Zugriff auf die Empfangs- und Sendepuffer der Netzwerkübertragung erfolgt über Adapterklassen, die den jeweiligen Puffer als Datenstrom abbilden. Es existieren zwei Adapterklassen, eine zum Auslesen eines Datenstroms und eine zum Schreiben eines Datenstroms. Der Entwurf dieser Klassen orientiert sich an der in \cite{Wie05} vorgestellten Klasse zur Verarbeitung von MOST-Daten. Diese Klassen verbergen über entsprechende Compilermakros aus der globalen Header-Datei (\ref{lst:global}) die Endianess des jeweiligen Rechners. So können diese Datenstromklassen ohne Änderungen in den target-- und hostseitigen Softwarekomponenten verwendet werden.
+
+Zur Verarbeitung der Netzwerknachrichten kommen konstante Parser-Objekte zum Einsatz. Dieser Ansatz basiert auf der in \cite{Wie05} vorgestellten Methode zur Verarbeitung von MOST-Nachrichten. Zur Compilezeit werden dabei Objekte erzeugt, die eine Netzwerknachricht aus einem Netzwerkdatenstrom in einen Datencontainer lesen bzw. Netzwerknachrichten aus einem Datencontainer schreiben. Die konstanten Objekte beinhalten dabei jeweils Validierungsdaten, wie beispielsweise das zulässige Minimum oder Maximum eines Datums. Ein Datencontainer enthält die Anwendungsdaten. Konstante Parserobjekte prüfen beim Lesen oder Schreiben die Daten des Datencontainers gegen die konstanten Validierungsdaten. Von Seiten der Applikation erfolgt der Zugriff auf Netzwerkdaten über den Datencontainer. Der Datencontainer stellt dazu eine Schnittstelle bereit, die einen indexbasierten Zugriff erlaubt, so dass keine Zeigerarithmetik benötigt wird. Die Adressierung von Strukturelementen mittels Zeigerarithmetik stellt laut \cite{Wie05} immer eine kritische Fehlerquelle dar, da sie abhängig von Compiler und Plattform ist.
+
+\section{Targetseitige Implementierung}
+\label{sec:implxoraya}
+Zum Laden der Targetplugins verwendet der Prototype die vorhandene Bibliothek \textit{dl}. Sie bietet Funktionen zum Öffnen und Schließen von shared objects. Darüber hinaus liefert sie Zeiger auf Symbole in einem geladenen shared object anhand des Symbolnamens. Allerdings alloziert die Bibliothek RAM für das Laden der shared objects. Da dies innerhalb der Bibliothek geschieht, findet an dieser Stelle die einzige Nutzung von dynamischem Speicher innerhalb der Komponenten auf der \myxc~statt. Für den hier zu erstellenden Proof Of Concept wird dieser Umstand in Kauf genommen.
+
+\subsection{Initialisierung}
+Die Initialisierung der Targetimplementierung wird nach dem Programmstart zuerst ausgeführt. Sie stellt den gewünschten Systemzustand her. Dazu zählen
+\begin{description}
+\item[Memory locking] Verhindert das Auslagern der RAM-Bereiche des Systems auf die Festplatte (siehe \ref{sec:fund:swap}). Der entsprechende Systemaufruf auf einem Linux-System benötigt root-Rechte. Schlägt dieser Aufruf fehl, startet das System ohne memory locking.
+\item[Stack pre-faulting] Vorbelegung des Stacks nach \cite{RTPREEMPTHOWTO}.
+\item[Maintask] Über einen Kommandozeilenparameter lässt sich konfigurieren, ob die Maintask mit Echtzeit-Priorität laufen soll oder als Task mit normaler Priorität. Ist Echtzeit-Priorität gewünscht, startet diese Funktion die Maintask unter Verwendung des Echtzeit-FIFO-Schedulers und mit der höchsten Priorität im Gesamtsystem, andernfalls als normalen Thread ohne weitere Parameter.
+\end{description}
+
+\subsection{Steuerung}
+Als Datencontainer für Nachrichten, die in einem Zyklus versendet werden sollen, findet boost::intrusive::multiset Verwendung. Dieser Container basiert auf einem Rot-Schwarz-Baum und bietet ein Laufzeitverhalten wie in \ref{sec:con:rbt} beschrieben. Ein Multiset kann mehrere Einträge unter dem gleichen Schlüssel ablegen. Diese Eigenschaft ist hier notwendig, da in mehreren Bussen die gleiche Nachrichten-ID vorkommen kann, die als Schlüssel dient.
+
+\subsection{Aufzeichnung}
+Die Template-Klasse boost::circular\_buffer stellt einen Ringpuffer bereit, der zum Auslesen der Empfangswarteschlange der \myxc~dient. Die Template-Klasse bounded\_buffer kapselt den Ringpuffer und einen Mutex mit dem Attribut PTHREAD\_PRIO\_INHERIT zur Synchronisation. Der Ringpuffer bietet Platz für 8192 Nachrichten, die Größe des internen Puffers der Logtask beträgt 4096 Byte.
+Das Listing \ref{lst:global} zeigt u. a. den Aufbau der Datenstruktur \texttt{tstLogMessage} zum Speichern empfangener Nachrichten ab Zeile 153. Um auf dem Target möglichst wenig Rechenzeit für das Speichern der Aufzeichnungsdatei zu verbrauchen, findet an dieser Stelle ein binäres Dateiformat Verwendung. Es enthält sequentiell die empfangenen Nachrichten im Format von \texttt{tstLogMessage} in der Bytereihenfolge des Targets. Beim Auslesen auf dem Host muss diese Tatsache Beachtung finden.
+
+\subsection{Netzwerk}
+Es existiert eine separate Task (siehe \ref{sec:con:networktask}) zur Abwicklung der Netzwerkkommunikation. Diese Task führt ein boost::asio::io\_service-Objekt aus. Dieses verwaltet die einzelnen Verbindungen. Die Verbindungen implementieren in Lese- und Schreib-Handlern die Logik dieser Komponente.
+
+Um hier auf dynamischen Speicher verzichten zu können, kommt ein statisches Bytearray zum Einsatz. Mit Hilfe des Placement-new-Operators wird bei einer neuen Verbindung ein Verbindungsobjekt in diesem Speicherbereich entsprechend erzeugt. Dazu muss zuerst ein freier Speicherplatz für das neue Objekt gefunden werden. Ein Datencontainer enthält verwendete Verbindungsobjekte. Entspricht ein Speicherbereich innerhalb des Bytearrays keiner der Adressen der Objekte in diesem Container, kann dieser Speicherbereich für ein neues Verbindungsobjekt verwendet werden. Dieser Speicherbereich wird überschrieben, so dass zuvor überprüft werden muss, dass er von keinem Verbindungsobjekt verwendet wird. Das Einfügen der neuen Verbindung in den Container verhindert die Verwendung ihres Speicherbereichs für eine andere Verbindung. Tritt ein Fehler auf der Verbindung auf, beispielsweise das Trennen der Verbindung durch den Client, wird die Verbindung aus dem Datencontainer gelöscht und der Speicherbereich kann für eine neue Verbindung wiederverwendet werden.
+
+Das Listing \ref{lst:tcpcon} zeigt ab Zeile 51 die statische Fabrikmethode für neue Verbindungsobjekte. Sind alle Speicherslots für Verbindungen belegt, gibt die Methode einen NULL-Zeiger zurück. Aufrufende Module müssen den zurückgegebenen Zeiger also überprüfen. In Zeile 79 findet der Aufruf des Placement-new-Operators statt, um ein neues Objekt an der angegebenen Speicheradresse zu erzeugen.
+
+Dieses Verfahren beschränkt die Anzahl der möglichen Verbindungen entsprechend der festen Größe des Bytearrays. Für diesen Prototyp ist nur die Kommunikation mit einer CanEasy-Instanz vorgesehen, so dass maximal zwei Verbindungen, eine verwendete und eine halboffene für den nächsten Client, benötigt werden. Die zur Compilezeit konfigurierte maximale Anzahl von sechzehn Verbindungen reicht dafür zweifellos aus.
+
+Der Zugriff auf den Datencontainer erfolgt ausschließlich in der Netzwerktask. Dies lässt sich mittels der \texttt{post}-Methode eines asynchronen boost Io-Dienst-Objekts erreichen. Als Parameter erhält \texttt{post} eine Methode, die im Thread des asynchronen boost Io-Dienst-Objekts ausgeführt wird. Soll nun aus der Logtask eine aufgezeichnete Nachricht über des Netzwerk versendet werden, erfolgt der Versand nicht direkt. Stattdessen wird die Nachricht in einen Puffer eingefügt und ein entsprechender \texttt{post}-Aufruf durchgeführt. Die an \texttt{post} übergebene Funktion liest den Puffer im Kontext der Netzwerktask aus und versendet die entsprechende Nachricht über das Netzwerk.
+
+\section{Hostseitige Implementierung}
+\label{sec:impl:host}
+Um sicher zu stellen, dass ein geladenes shared object auf dem Target nicht überschrieben wird, löst jede Start-Aktion für ein Interface zuerst eine Stop-Aktion für dieses Interface aus.
+\subsection{GUI}
+Die Abbildung \ref{fig:proto:dlg} zeigt den GUI-Dialog des CanEasy-Plugins. Die Anordnung der Elemente orientiert sich an den in Abbildung \ref{fig:con:usecases} definierten Abhängigkeiten. Zusätzlich verfügt die prototypische Implementierung über einen Textbereich zur Anzeige von Trace-Meldungen.
+\begin{figure}[hbtp]
+\centering
+\begin{minipage}[b]{\linewidth}
+\includegraphics[width=\linewidth]{caneasy_plugin}
+\end{minipage}
+\caption{CanEasy-Plugin}
+\label{fig:proto:dlg}
+\end{figure}
+
+\subsection{Code-Generierung, Cross-Compiler}
+Das Listing \ref{lst:targetplugin} zeigt den generierten Quellcode für die in Abschnitt \ref{sec:res:wcrttest} beschriebene Busdefinition. Auch an dieser Stelle kommt der Prototyp ohne Allokation zusätzlichen dynamischen Speichers aus. Der C++-Standard C++x0 erlaubt die Generierung der gezeigten Initialisierungsliste des Konstruktors mit den Werten der einzelnen Nachrichten. Darüber hinaus lässt sich eine globale Objektinstanz erkennen. Die Fabrikfunktion des Targetplugins konfiguriert diese Instanz und gibt einen Zeiger darauf zurück.
+
+Aus der generierten Quellcodedatei für ein Targetplugin erzeugt die Toolchain des SDK in ein shared object. Dazu generiert die hostseitige Komponente ein entsprechendes Makefile wie in \ref{lst:targetmakefile} gezeigt. Dieses Makefile steuert Compiler und Linker zur Erzeugung des Targetplugins.
+
+\subsection{Aufzeichnungskonvertierung}
+Bei der Konvertierung der empfangenen Aufzeichnung vom Target muss im ersten Schritt die Bytereihenfolge der empfangenen Nachrichten auf die des Hosts angepasst werden. Danach erfolgt die Umwandlung der empfangenen binären Daten in ein Textformat. Für dieses Textformat besitzt CanEasy Importfunktionen. Diese wandeln die konvertierte Textdatei in eine Datei im CanEasy-Aufzeichnungsformat um. Der Anwender öffnet diese Datei, um Zugriff auf die Aufzeichnungsdaten zu erhalten.
+
+\subsection{Aktualisierung von Nachrichtendaten}
+CanEasy verfügt über ein internes Ereignissystem. Dieses ermöglicht es Plugins, Callbackfunktionen für bestimmte Ereignisse im System zu registrieren. Tritt ein solches Ereignis ein, führt CanEasy die entsprechende Callbackfunktion aus.
+
+Bei der Generierung eines Plugins registriert die hostseitige Integrationskomponente eine Callbackfunktion für die Datenänderungsereignisse aller simulierten Nachrichten. Diese Callbackfunktion versendet eine entsprechende Nachricht über das Netzwerk mit den geänderten Nachrichtendaten.
+
+\section{Implementierung des Verifikationswerkzeug}
+\label{sec:implverification}
+Die Implementierung auf dem \nameref{sec:con:at32uc3c} basiert auf einem Grundsystem der Firma Schleißheimer für diesen Controller und setzt das in Abschnitt \ref{sec:con:verification} entworfene Konzept um.
+