Fachbereich Elektro- und Informationstechnik. Diplomarbeit

Größe: px
Ab Seite anzeigen:

Download "Fachbereich Elektro- und Informationstechnik. Diplomarbeit"

Transkript

1 Fachbereich Elektro- und Informationstechnik Diplomarbeit Entwicklung einer echt-parallelen Software in C++ zum Empfangen und Verarbeiten eines USB-Datenstroms in Echtzeit Semester: Sommersemester 2010 Beginn: Ende: Referent: Prof. Dr.-Ing. Michael Münke Korreferent: Prof. Dr.-Ing. Werner Bonath Firma: FH Gießen Friedberg Betreuer (firmenseitig): Dipl.-Ing. (FH) Marcel Beuler Vorgelegt von: Jan Philipp Kießling Schmiedegasse Schwalmstadt Matrikel-Nr.: Studiengang: Automatisierungstechnik

2 Per aspera ad astra (Durch Mühsal zu den Sternen)

3 Eidesstattliche Erklärung Hiermit versichere ich, Jan Philipp Kießling, geboren am , dass ich die vorliegende Diplomarbeit selbstständig verfasst und keine anderen als die angegebenen Hilfsmittel verwendet habe. Textstellen, die anderen Werken im Wortlaut oder sinngemäß entnommen wurden, sind mit Quellenangaben kenntlich gemacht. Ich versichere weiterhin, dass diese Diplomarbeit keinem anderen Prüfungsgremium vorgelegen hat. Schwalmstadt, den Jan P. Kießling

4 Vorwort und Danksagungen Diese Diplomarbeit wurde im Rahmen des Nepteron-Projekts im Zeitraum im Labor G115 der FH Gießen Friedberg angefertigt und wurde aus verschiedenen Labormitteln finanziert. Ich möchte nicht versäumen, Herrn Dipl.-Ing.(FH) Beuler für die Zuteilung dieser Arbeit und das mir entgegen gebrachte Vertrauen, zu danken. Ich habe sehr gern mit ihm zusammen gearbeitet und seine ruhige und äußerst gewissenhafte Art zu arbeiten hat mich beeindruckt. Außerdem hat er meine Texte Korrekturen gelesen und konnte mir bei einigen fachlichen Probleme weiterhelfen. Herrn Prof. Dr.-Ing. Münke danke ich für die fachkundige und stets engagierte Betreuung und für die unkomplizierte Bereitstellung aller benötigten Geräte und Bibliotheken. Herr Prof. Dr.-Ing. Bonath gab mir die Gelegenheit im Projekt Nepteron mit zu arbeiten und meldete mich beim MPC-Workshop an. Die Teilnahme war sehr interessant und es hat mir Freude bereitet vor dem Publikum zu sprechen. Die Herren Carle, Dipl.-Ing. (FH) Diehl und Dipl.-Ing. Pointeau haben mich tatkräftig bei allerlei Problemen mit meinem Arbeits-PC, Einkäufen und Lizenzen unterstützt. Ihnen allen sei an dieser Stelle herzlich gedankt. Unserem Dekan, Herrn Prof. Dr.-Ing. Probst, möchte ich für die außergewöhnlich guten Vorlesungen danken. Vor allem für die gründliche Einführung in objektorientierte Programmierung, auf die ich einige Jahre warten musste und ohne die diese Arbeit nicht möglich gewesen wäre. Durch die zahlreichen, angeregten Diskussionen mit meinen guten Freunden B.sc. Sebastian Hesse und Michael Wagner, über ihr Fachgebiet, die Informatik, war es mir u. A. möglich NepteronSoftware von Anfang für die asynchrone Datenverarbeitung auszulegen. Ohne sie wäre meine Arbeit nicht das, was sie heute ist. Außerdem bin ich ihnen natürlich wegen der beherzten und durchweg kompetenten Korrektur meiner Texte zu Dank verpflichtet. Ebenfalls für Korrekturen und Kritik an meinen Vorträgen danke ich meinem Bruder Andre Kießling und meinen Freunden Nora Beckmann, Daniela Euler BBA, Hannes Walz und B.A. Sozw. Manuel Fleischner. Nicht unerwähnt lassen, möchte ich meinem ehemaligen Kommilitonen und guten Freund Dipl.- Ing. Andrè Wanzel, ohne den ich mein Studium wohl nicht abgeschlossen hätte. Die Zeit mit dir hat mir hat mir viel Freude bereitet, wir haben unglaublich viel gelacht und ich hoffe in Zukunft Kollegen wie dich zu finden! Schließlich, aber nicht zu Letzt, danke ich meiner Familie, allen voran meiner Mutter, die mir meine geliebte Ausbildung überhaupt erst ermöglichten. Dass ihr immer an mich geglaubt habt und mich jahrelang, auch und vor allem in schweren Zeiten, unterstützt habt, ist mir durch nichts zu ersetzen. Vielen Dank, euch allen! Schwalmstadt, im September 2010 Jan Philipp Kießling

5 Zusammenfassung In der Biologie und Pharmazie werden täglich neuronale Netze simuliert, etwa um die Wirkung von Medikamenten zu erforschen. Die Berechnung dieser Netze ist allerdings so aufwändig, dass entweder nur kleinste Netze berechnet werden, teure Großrechner gemietet werden müssen, oder zig Stunden auf Ergebnisse gewartet wird. Das Nepteron Projekt entwickelt einen hoch spezialisierten Prozessor, der diese Probleme lösen soll. Die Simulationsergebnisse werden vom Nepteron-Prozessor, in Form eines konstanten Datenstroms per USB. an die Außenwelt abgegeben. Gegenstand dieser Arbeit ist die Entwicklung eines Programms, das die Annahme und Verarbeitung dieses Datenstroms übernimmt. Vor der eigentlichen Planung und Implementierung dieser Software, wurde eine Machbarkeitsstudie durchgeführt. Ihre Ergebnisse bildeten die Grundlage für die folgenden Entwicklungen. Als Programmiersprache wurde C++ eingesetzt. Um den Datenstrom schnell genug zu verarbeiten, wurde das Programm so implementiert, dass es auf mehren Prozessor-Kerne nutzen kann. Zu diesem Zweck wurde die Bibliothek Threading Building Blocks von Intel verwendet. Zur Kommunikation mit dem USB wurde die Bibliothek CyAPI von Cypress eingesetzt und mit der Boost-Bibliothek wurden größere Datenmengen gehandhabt. Dabei kamen Datencontainer und intelligente Zeiger zum Einsatz. Um die Entwicklung zu modularisieren, wurde die Modell-View- Controller Architektur gewählt und um eine Datenquelle/-senke erweitert. Als alle Programmteile einzeln getestet waren, konnten Hard- und Software zusammengefügt werden. Die Verbindung zwischen Software und Nepteron-Prozessor wurde hergestellt, und der Prozessor wurde mit den benötigten Initialisierungsdaten versorgt. Danach konnte nachgewiesen werden, dass der Prozessor die Simulation wie erwartet durchführt und, dass das angefertigte Programm korrekt und schnell genug arbeitet. Abstract In the field of biology and pharmacy neuronal networks are simulated day in and day out, for example to research the impact of drugs. The calculation of those networks is very extensive and thus the scientists either simulate smallest networks, rent expensive supercomputers or wait several hours for results. The Nepteron project develops a highly dedicated processor, to solve those problems. The Nepteron-processor sends his results as a constant stream of data via USB to the outside world. The purpose of this work is to provide a software, which receives and processes this stream. Before planning and implementing this software, a proof-of-concept had been done. It's results built the groundwork for further development. C++ was used to write the software. To process the stream fast enough, it was necessary to use multiple processor cores. For this reason the Threading Building Blocks library by Intel was used. To communicate with USB the CyAPI library by Cypress had been installed and to handle big amounts of data the Boost-library was used. This way data containers and intelligent pointers found their way into the software. The model-viewcontroller architecture was used to modularize the whole development and expanded by a datasource/-sink. After every part had been tested on it's own, the hard- and software had been put together. The connection between Nepteron-processor and the software were established and the processor got supplied with the needed initialisation data. Thereafter it was possible to show, that the Nepteronprocessor simulates as expected and that the software works correct and fast enough.

6 1 Inhaltsverzeichnis 1 Einleitung Das Nepteron Projekt Motivation und Positionierung dieser Arbeit im Nepteron Projekt Programmiertechnische Grundlagen Kunde Grundlagen der objektorientierten Programmierung (OOP) Klassen und Objekte Konstruktion und Destruktion Fabrikfunktionen Überladung und Initialisierungsliste Vererbung und Namensräume Kapselung private und public Templates und STL TR1 und Boost Allgemeines über Datencontainer und Iteratoren Vector Map concurrent_queue Intelligente Zeiger shared_ptr und shared_array USB 2.0 Universal Serial Bus Grundsätzliches Diskussion der Betriebsarten Interrupt-Transfer Isochronous-Transfer Bulk-Transfer Der verwendete USB-Chip Die CyAPI Verbindungsaufbau Identifikation und Parametrierung Senden und Empfangen Optimierung der Datenrate XferSize und HLpktSize Die Datenstrecke eine Machbarkeitsstudie Die Datenstrecke Anforderungen Datenrate und Fehlerfreiheit Untersuchung der Strecke Maximale Bruttodatenrate Fehlerrate Maximale Nettodatenrate GPIF Totzeit Verarbeitungsdauer im PC Ergebnisse und Anforderungen an Benutzer Parallele Datenverarbeitung mit PCs Grundlagen der parallelen Datenverarbeitung Synchronisation Zugriff auf gemeinsame Ressourcen und Daten Das Amdahlsche Gesetz Petri-Netze Intel Threading Building Blocks (TBB)...33

7 Einführung TBB parallel_for Tasks NepteronSoftware Flussdiagramm und interner Datenfluss der NepteronSoftware Betriebsarten, Pakettypen und Paketaufbau MVC-D Architektur Grundbegriffe Modell View Controller Data Schnittstellen innerhalb von MVC-D Data Controller Controller Data Controller Modell Modell Controller Modell View und View Modell Controller View View Controller Die Implementierung von NepteronSoftware Klassenübersicht Initialisierung des Nepteron-Prozessors Aufbau der Initialisierungsdateien Versenden der Initialisierungsdateien Die NepteronInitDatafactory Die NeuronTransferList Datenverarbeitung Funktionsnachweis Fazit und persönliches Resümee Literaturverzeichnis Gedruckter Anhang CD...68

8 1 Einleitung 1 3 Einleitung Dieses Kapitel bietet einen kurzen Einblick in Motivation und Umfeld des Nepteron Projekts, außerdem wird die Positionierung dieser Diplomarbeit im Rahmen des Projekt erläutert. 1.1 Das Nepteron Projekt In der Biologie und Pharmazie werden täglich neuronale Netze berechnet, etwa um psychische Erkrankungen oder Medikamente zu erforschen. Da Simulationen mit aktuellen PC-Systemen sehr zeitaufwändig sind, wurde das Projekt Nepteron gegründet. Es handelt sich dabei um eine Kooperation der FH Gießen Friedberg und der Philipps-Universität Marburg. Genauere Informationen, z. B. über Projektfortschritt und Mitarbeiter, finden sich unter [1]. Der Anspruch dieser Projektgruppe ist es, neuronale Netze mittlerer Größe in Echtzeit zu berechnen und darzustellen. Unter Echtzeit wird hier verstanden, dass z.b. eine Simulation von 20 Sekunden Zellverhalten auch 20 Sekunden dauert. Mittlere Größe heißt, dass mittelfristig Netze in der Größenordnung Zellen angestrebt werden. Dies soll durch das Vernetzen mehrerer ASICs realisiert werden, die jeweils mehrere Neuronenrechenkerne beinhalten. Die Zellen werden nach dem Huber-Braun Modell berechnet, was einen sehr guten Kompromiss zwischen Rechenaufwand und Realitätsnähe darstellt [2]. Es sei an dieser Stelle darauf hingewiesen, dass es nicht darum geht, Berechnungen von dem Netz ausführen zu lassen. In der Informatik werden neuronale Netze beispielsweise zur Gesichtserkennung eingesetzt. Dabei werden sie so angelernt, dass sie auf bestimmte Eingabedaten bestimmte Ausgabedaten erzeugen; dies ist hier nicht der Fall. Vielmehr wird das Verhalten von menschlichen Zellen simuliert. Die Zustände der einzelnen Zellen ihr sog. Neuronenpotential, eine elektrische Spannung im mv-bereich sind von Interesse, um beispielsweise die Wirkung von Medikamenten zu untersuchen oder die Gründe für Schlafstörung zu erforschen. Je größer die Netze, desto vielfältiger und aussagekräftiger sind die Simulationen. Es geht im Grunde um die Frage: Was passiert mit einer Gruppe Zellen der Größe G, die sich im Zustand Z befinden, wenn man X tut und eine Zeit t wartet? Wobei G, Z, X und t vom Benutzer frei wählbar sein sollen. 1.2 Motivation und Positionierung dieser Arbeit im Nepteron Projekt Um diese Fragen zu beantworten wird ein digitaler Prozessor benötigt, der die Berechnungen in der vorgegebenen Zeit durchführen kann. Um ihn zu entwickeln und zu testen, werden an der FH Gießen Friedberg verschiedene FPGAs1 eingesetzt. Auf diesen ist zur Zeit dieser Diplomarbeit ein Neuronenkern implementiert, der 400 Neuronen in Echtzeit berechnet [3]. Um die Ergebnisse der Außenwelt mitzuteilen nutzt der Neuronenkern eine USB-Schnittstelle[4]. Dazu wird ein Programm benötigt, das die ankommenden Daten entgegen nimmt. Außerdem muss es die Daten aufbereiten, sie in einer graphischen Benutzeroberfläche darstellen und alte Daten in einer Datenbank ablegen. Des Weiteren muss das Programm den Neuronenkern mit Initialisierungsdaten, Start-Paketen und Ähnlichem versorgen. Dieses Programm soll den Namen NepteronSoftware tragen. Gegenstand dieser Diplomarbeit ist eine Machbarkeitsstudie für den gesamten Datentransfer, sowie Entwurf und Implementierung der NepteronSoftware. Dabei wird die gesamte Datenstrecke auf ihre Tauglichkeit untersucht. Für diesen Zweck wurde das C++ Programm SpeedTest2.7 geschrieben, auf das in Kapitel 4 genauer eingegangen wird. Im gedruckten Anhang, Abb. 28, findet sich ein Flussdiagramm zu diesem Programm. Erste Ergebnisse wurden auch als Paper veröffentlicht[5]. 1 Virtex-4 und Virtex-6

9 1 Einleitung 4 Ebenfalls Gegenstand dieser Arbeit sind die USB Verbindung - worauf Kapitel 3 eingeht - und die Datenverarbeitung. Um die Berechnungen schnell genug durchzuführen ist echt-paralleles Rechnen nötig, wozu Kapitel 4 die Begründung liefert. Kapitel 5 gibt eine Einführung in diese Technologie. Außerdem stellt dieses Kapitel die verwendete C++ Erweiterung namens Threading Building Blocks vor. Damit ist es möglich in C++ parallele Strukturen, wie z. B. parallel_for, zu verwenden. Kapitel 6 beschreibt anschließend die erarbeiteten und implementierten Konzepte der NepteronSoftware. In Kapitel 7 wird erläutert, wie der Funktionsnachweis erbracht wurde und Kapitel 8 ist für Fazit, persönliches Resümee und Ausblick reserviert. Auf dieses folgt schließlich das Literaturverzeichnis, ein gekürzter, gedruckter Anhang und ein vollständiger auf CD. Diese Diplomarbeit beschreibt also das Proof of Concept und die Entwicklung der NepteronSoftware, für die Arbeitsbereiche Datenübertragung und Datenverarbeitung. Dabei wird vor allem darauf eingegangen, wie in C++ eine USB Verbindung herstellt werden kann und wie mittels TBB echt-paralleler Code geschrieben wird.

10 2 Programmiertechnische Grundlagen 2 5 Programmiertechnische Grundlagen Die folgenden Abschnitte stellen einen Crashkurs der objektorientierten Programmierung dar. Sie sollen kurz die wichtigsten Begriffe und Schreibweisen erklären. Zuerst wird allerdings ein wichtiger Begriff aus [6] erläutert werden, der Kunde. 2.1 Kunde Aus programmiertechnischer Sicht ist ein Kunde, eine Person oder ein Stück Code, das fertigen Code verwendet. Wenn ein Programmierer eine Funktion schreibt und die Hauptfunktion main() diese aufruft, dann ist main() Kunde des Entwicklers und Kunde seiner Funktion. Menschen, die eine graphische Benutzeroberfläche bedienen, sind deren Kunde und Programmierer in einem Team sind häufig gegenseitige Kunden, weil sie Code von ihren Mitarbeitern aufrufen. Genauso häufig ist ein Programmierer auch sein eigener Kunde, meist einige Wochen später. Bei der Gestaltung von Programmteilen sollte stets im Blick behalten werden, wer der Kunde ist und was er vom Code erwartet. 2.2 Grundlagen der objektorientierten Programmierung (OOP) Spätestens seit 1972 Bjarne Stroustrup C++ eingeführt hat, ist Objektorientierung aus der Informatik nicht mehr wegzudenken. Dieses Kapitel gibt eine kurze Einführung in die Thematik und beschränkt sich dabei auf die wichtigsten, in dieser Diplomarbeit verwendeten, OOP-Konzepte Klassen und Objekte Grundsätzlich sind Objekte zusammenhängende Bereiche im Speicher, die Variablen und Befehle enthalten. Variablen werden hier Eigenschaften oder Attribute genannt und Funktionen, die zu einem Objekt gehören heißen Methoden. Die englischen Begriffe sind member variable und member function. Listing 1: Konto.h Mit Objekten können Teile der realen Welt wesentlich besser und schlüssiger modelliert werden, als mit vereinzelten Variablen und herrenlosen Funktionen. Code und Speicher werden zu eigenständigen, nach außen abgegrenzten Einheiten. Dadurch wird eine neue Abstraktionsebene erreicht, die die Gestaltung von Software wesentlich vereinfacht. Hinzu kommen sehr nützliche Techniken wie Vererbung und Templates, die außerhalb der OOP-Welt nicht denkbar sind. Wenn beispielsweise ein Girokonto realisiert werden soll, so kann man eine Klasse namens Konto erstellen. Um sie mit Leben zu füllen bekommt sie die Attribute Kontostand und Kontonummer und die Methoden Einzahlen() und Auszahlen(). Listing 1 zeigt die Deklaration dieser Klasse. Alle vier Dinge sind Teil der Klasse und treten immer gemeinsam auf.

11 2 Programmiertechnische Grundlagen 6 An dieser Stelle sollen die Begriffe Klasse und Objekt deutlich unterschieden werden: Eine Klasse beschreibt eine Sorte von Objekten. Sie ist der Bauplan für Objekte. Genauso wie es Pläne für Häuser aller Art, z. B. Hausboote, gibt. Wenn der Bauplan ordnungsgemäß erstellt wurde, können Arbeiter danach Häuser bauen. Es können damit beliebig viele Häuser gebaut werden. Die einzelnen Hausboote nennt man dann auch Instanzen der Klasse Hausboot und das Erzeugen heißt auch instanziieren. Man sollte darauf achten, ob man über die Klasse spricht oder über individuelle Repräsentanten dieser Klasse, also Objekte. Man sagt: Jedes dieser Häuser ist ein Objekt vom Typ (der Klasse) Hausboot. Typ ist hier übrigens wörtlich zu nehmen, denn jede Klasse ist ein eigener, neuer Datentyp und unterliegt dem C++ Typensystem Konstruktion und Destruktion Wenn eine Instanz eines primitiven Datentyps angelegt wird, beispielsweise eine Integervariable, muss dazu im Stack ein Bereich reserviert werden, in dem die Variable dann tatsächlich liegt, und im Heap wird eingetragen wo sie liegt.2 Das Erzeugen von Objektdatentypen ist aufwändiger und erfolgt mit Hilfe von Konstruktoren. Ein Konstruktor ist ein Stück Code, dass alle nötigen Operationen durchführt, um ein Objekt zu erzeugen. Jede Klasse hat einen eigenen Konstruktor. Wenn er aufgerufen wird, reserviert er den nötigen Speicher, stattet ihn mit Initialwerten aus und schreibt den Code der Methoden dazu. Wenn der Konstruktor erfolgreich abgelaufen ist, steht ein neues Objekt zur Nutzung bereit. Da dies recht aufwändig, aber im Grunde immer gleich ist, gibt es sogenannte Standardkonstruktoren. Der Compiler erzeugt den Konstruktor völlig automatisch und verbirgt ihn vor dem Programmierer. Dies wird dadurch deutlich, dass die Konstruktor-Methode augenscheinlich keinen Code enthält. Konstruktoren heißen immer wie ihre Klasse. Listing 2 zeigt die Datei Konto.cpp. Sie enthält den eigentlichen Quellcode der Klasse. Die Angabe Konto:: bedeutet, dass das, was nach dem zweiten Doppelpunkt kommt, zur Klasse Konto gehört. Die in dieser Datei aufgeführten Methoden sind also alle Mitglieder der Klasse Konto. Konstruktoren werden immer automatisch generiert, allerdings können sie bei Bedarf erweitert werden, indem den leeren, geschweiften Klammern Code hinzufügt wird. Dann heißen sie allerdings nicht mehr Standardkonstruktor. Listing 2: Konto.cpp 2 Heap und Stack sind bei Windows-Maschinen Teil des Arbeitsspeichers und wachsen aufeinander zu.

12 2 Programmiertechnische Grundlagen 7 Objekte werden häufig zur Laufzeit erzeugt. Um den reservierten Speicher wieder frei zu geben werden Destruktoren verwendet. Sie verhalten sich analog zu Konstruktoren und heißen wie ihre Klasse, allerdings mit einer ~ vor dem Methodennamen. Wenn ein Objekt lokal erzeugt wurde wird der Destruktor automatisch aufgerufen, wenn das Programm den Gültigkeitsbereich des lokalen Objekts verlässt. Globale Objekte werden nur dann automatisch zerstört, wenn das Programm verlassen wird. Listing 3 zeigt die Verwendung von Konto-Objekten in einer main() Funktion. Listing 3: Erzeugung, Zerstörung und Methodenaufruf bei Objekten Vorsicht! Das zweite Konto wird über den Operator new erzeugt und mit einem Zeiger verwaltet. Beim Verlassen von main() wird nur der Destruktor von EinKonto aufgerufen. Das namenlose Konto bleibt im Speicher, weil nur der Zeiger zerstört wird. Von Operator new erzeugte Daten besitzen keinen Gültigkeitsbereich! Sie müssen immer per Operator delete gelöscht werden. Wird der Zeiger pkonto vorher gelöscht, kennt das Programm den Ort des Objekts nicht mehr und kann es nicht löschen. Es entsteht ein sogenanntes Speicherleck und der Speicher ist verloren. Operator delete löscht zwar das Objekt, lässt aber den Zeiger unangetastet. Es ist daher gute Programmierpraxis einen ungültig gewordenen Zeiger NULL zu setzen, damit er bei versehentlicher Verwendung auf nichts Wichtiges zeigen kann. Listing 4 zeigt die fachgerechte Zerstörung des zweiten, namenlosen Kontos. Listing 4: Zerstörung eines mittels Operator new erzeugten Objekts Fabrikfunktionen Was am Ende des vorherigen Kapitels ein Problem war, kann für sogenannte Fabrikfunktionen genutzt werden. Dies sind Funktionen oder Methoden, die ausschließlich dazu dienen Objekte zu erzeugen. Die Fabrikfunktion wird von einem Kunden aufgerufen und erzeugt mittels Operator new die gewünschten Objekte und gibt einen Zeiger zurück. Da diese Daten keinen Gültigkeitsbereich besitzen können sie nach Belieben per Zeiger weitergereicht werden. Es liegt nun in der Verantwortung des Kunden, den Speicher ordnungsgemäß freizugeben. Wie in [6] beschrieben, sollte sich guter Code allerdings nicht auf so etwas verlassen. Denn alles was erfordert, dass sich jemand erinnert, kann vergessen werden. Aus diesem Grunde sollten intelligente Zeiger verwendet werden, auf die in einem der nächsten Kapitel eingegangen wird. Durch sie werden Aufrufe von Operator delete unnötig.

13 2 Programmiertechnische Grundlagen Überladung und Initialisierungsliste C++ Compiler erkennen Funktionen nicht nur an ihrem Namen, sondern auch an ihren Übergabeparametern. Dadurch ist es z. B. möglich zwei Funktionen namens Multipliziere() zu schreiben, wobei die eine float und die andere integer Variablen multipliziert. Man spricht in diesem Zusammenhang von Überladenen Funktionen/Methoden. Auch Operatoren können überladen werden. Wenn ein Objekt mit bestimmten Initialwerten erzeugt werden soll, kann man einen Konstruktor zur Verfügung stellen, der diese Werte als Parameter entgegen nimmt. Gleichzeitig kann aber noch der Standardkonstruktor bestehen, um nicht initialisierte Objekte zu erzeugen. Hier spricht man von Überladenen Konstruktoren. Falls eine Konstruktion ohne Initialisierung vermieden werden soll, können die Zeilen 8 bis 10 in Listing 5 gelöscht werden, der Compiler erzeugt dann keinen Standardkonstruktor und gibt eine Fehlermeldung, wenn versucht wird ihn aufzurufen. Listing 5: Überladener Konstruktor Die echte Initialisierung von Objekten erfolgt allerdings über Initialisierungslisten. Das initialisieren der Werte im Rumpf eines Konstruktors ist eine Zuweisung die Variablen sind bereits erzeugt, wenn der Konstruktorrumpf bearbeitet wird. Dies ist wichtig, wenn die Variablen ihrerseits aus Objekten bestehen und Zuweisungen nennenswert Zeit kosten. Daher sind Initialisierungslisten grundsätzlich vorzuziehen. Listing 6 zeigt eine gekürzte Klasse der NepteronSoftware, die einen solchen Konstruktor verwendet. Er bekommt drei Zeiger auf selbst deklarierte Klassen übergeben und weist diese seinen privaten Attributen zu. Dabei ist zu beachten, dass die Initialisierung in der Reihenfolge abläuft, in der die Attribute deklariert sind und nicht in der Reihenfolge, wie sie in der Initialisierungsliste stehen. Dies ist wichtig für Initialisierungen, die voneinander abhängig sind. Aus diesem Grund sollten Initialisierungslisten immer die Reihenfolge der Deklaration einhalten.

14 2 Programmiertechnische Grundlagen 9 Listing 6: Konstruktor mit Initialisierungsliste Vererbung und Namensräume Eine zentrale Technik der OOP ist Vererbung. Hiermit können neue Klassen aus alten abgeleitet werden. Die Kindklasse erbt alle Methoden und Attribute ihrer Elternklasse. In diesem Projekt wird vor allem von Intels TBB Bibliothek geerbt. Die Klasse in Listing 6 erbt von einer TBB Klasse namens task. Sie erhält damit alles Nötige, um ein vollwertiges Task Objekt zu sein. Dabei muss der Programmierer die dahinter stehenden Mechanismen nicht kennen. Es muss lediglich angegeben werden, von wem geerbt wird. Dazu steht hinter dem Klassennamen in Zeile 5 ein Doppelpunkt, gefolgt von dem Schlüsselwort public. Dies bedeutet, dass alle öffentlichen Teile der Elternklasse auch beim Kind öffentlich sind. Anschließend folgt der Name der Elternklasse, hier tbb::task. Es ist nun nötig Namensräume zu erläutern. Sie wurden mit C++ eingeführt und sind Codeblöcke, in denen Namen unabhängig vom Rest des Programms benutzt werden können. Nach außen werden sie durch den Namen ihres Namensraumes eindeutig identifiziert. In diesem Fall ist tbb der Namensraum und task der Name, sie werden durch zwei Doppelpunkte getrennt. Dies wurde eingeführt, damit z. B. Hersteller von Bibliotheken nicht darauf achten müssen, wer welchen Namen verwendet. Mit der using Klausel kann auf die wiederholte Angabe des Namensraumes verzichtet werden Kapselung private und public Im Header einer Klasse werden ihre Methoden und Attribute deklariert. Dabei wird zwischen private, public und protected unterschieden. Letzteres wird hier ignoriert. Als public deklarierte Methoden und Attribute sind öffentlich und es darf jeder auf sie zugreifen. Auf Privates darf nur das Objekt selbst zugreifen. Dadurch wird in Listing 1 verhindert, dass andere Programmteile direkt auf den Kontostand zugreifen. Stattdessen müssen sie die Methode Einzahlen() oder Auszahlen() aufrufen. Dieses Konzept wird Kapselung genannt. Die Daten des Objekts sind von der Außenwelt abgekapselt und nur das Nötigste ist zugänglich. Dies hat zum Einen den Vorteil, dass z. B. niemand aus Versehen eine Schiebe-Operation auf den Kontostand anwendet. Er kann auch nicht versehentlich negiert oder dividiert werden. Des Weiteren ist der

15 2 Programmiertechnische Grundlagen 10 Kunde der Methode Einzahlen() davon unabhängig, wie eingezahlt wird. Wenn es beispielsweise nicht um Kontostände, sondern um Berechnungen geht, so ändert sich im Laufe des Projekts vielleicht die Art der Berechnungen. Da diese Berechnungen in einer Methode verborgen sind, braucht nur diese geändert werden und die Kunden werden davon nicht beeinflusst. Dieser Vorteil ist natürlich nicht kostenlos. Eine Methode belegt nicht nur Programmspeicher, sondern sie muss auch gerufen werden, was Zeit kostet. Bei einfachen Methoden wie Einzahlen() übersteigt der Aufwand sie aufzurufen schnell die eigentliche Bearbeitungsdauer. Es liegt daher nahe die Variablen als public zu deklarieren und dem Kunden damit die volle Kontrolle zu übergeben. Davon ist allerdings abzuraten, weil es die Kapselung völlig außer Kraft setzt. In [6] wird sehr detailliert erläutert, warum Kapselung so nützlich ist. In den seltensten Fällen ist der Geschwindigkeitsgewinn so wichtig, dass er diese zahlreichen Gründe außer Kraft setzt Templates und STL Templates sind ebenfalls eine große Neuerung gegenüber C. Wenn Klassen Baupläne für Objekte sind, dann sind Templates Baupläne für Klassen. Sie sind hilfreich, weil häufig die selben Aufgaben für unterschiedliche Datentypen erledigt werden müssen. Allerdings können Klassen grundsätzlich nur die Datentypen verarbeiten, für die sie geschrieben wurde. Templates lösen dieses Problem. Wenn mit Hilfe eines Templates ein Objekt erzeugt werden soll, gibt der Programmierer den gewünschten Datentyp an und der Compiler erstellt und versteckt automatisch den passenden Code. Dieses Konzept wird im Kapitel über intelligente Zeiger und Container deutlicher. Eine sehr nützliche Sammlung von Templates befindet sich in der C++-Standardbibliothek. Die Abkürzung STL bedeutet Standard Template Library. Dieser Begriff ist nicht standardisiert, wird aber häufig als Synonym für die Untermenge der Standardbibliothek benutzt, die Templates enthält. Ihr Namensraum heißt std. 2.3 TR1 und Boost Der Technical Report 1 ist ein Papier, das vom C++-Standardisierungskomitees verabschiedet wurde. Es ist kein offizieller Standard, sondern eine Absichtserklärung die Standardbibliothek um Konstrukte wie z. B. intelligente Zeiger oder Hashtabellen zu erweitern. Offiziell werden diese Dinge erst mit dem nächsten C++ Standard eingeführt, der unter dem Arbeitstitel C++0x gehandelt wird und vermutlich 2012 erscheint. Allerdings muss nicht so lang gewartet werden um etwa shared_ptr zu verwenden, denn Boost hat schon das meiste oder gar alles von TR1 implementiert. Es beschränkt sich allerdings nicht auf TR1, sondern geht noch wesentlich weiter, denn jeder kann eigene Bibliotheken zur Einbindung in Boost einreichen, wodurch eine enorme Bibliothek gebildet wurde und wird. Dies ist nur die halbe Wahrheit und nähere Informationen finden sich unter [6] und [7]. Wichtig für diese Arbeit ist, dass Boost einige wichtige Konstrukte zur Verfügung stellt und das diese unter der Boost-Lizenz stehen. Das bedeutet, dass sie von jedermann, jederzeit, in jeder Form, für kommerzielle oder nicht kommerzielle Zwecke, als open oder closed source, verwendet werden dürfen. Des Weiteren überträgt sich die Boostlizenz nicht automatisch auf Code, der boostlizenzierten Code verwendet, sondern er darf nach Belieben lizenziert werden[8]. Die einzige Einschränkung ist, dass Dokumente, die Boostcode enthalten, eine Kopie der Lizenz mitführen müssen. Sie ist im gedruckten Anhang zu finden.

16 2 Programmiertechnische Grundlagen Allgemeines über Datencontainer und Iteratoren Es gibt viele verschiedene Container. Allen gemein ist, dass sie jeweils einen bestimmten Datentyp aufnehmen. Welcher das ist wird beim Erstellen des Containers festgelegt und kann anschließend nicht mehr geändert werden. Dies ist möglich, weil sie als Templates implementiert sind. Ebenfalls allen gemein ist, dass sie zur Laufzeit schrumpfen und wachsen können. Entweder auf Geheiß des Programmierers oder nach Bedarf, oder beides. Dies ist besonders für Pufferspeicher wichtig, da hier zu manchen Zeiten sehr viel Speicher benötigt wird und dann wieder sehr wenig. Es soll an dieser Stelle nicht weiter auf die zahlreichen Vorteile von Containern eingegangen werden. Es sei jedoch darauf hingewiesen, dass sie natürlich langsamer als Arrays sind. In den folgenden Abschnitten werden die in diesem Projekt eingesetzten Container erläutert. Eine komplette Liste der STL Container findet sich unter [9]. Die Container von TBB sind unter [10] einzusehen. Iteratoren sind zusammen mit Containern eingeführt worden. Um sich in einem Array zu bewegen nutzt man einen Index als Adressoffset, wobei der Index mit der Nummer des Elementes überein stimmt. Bei Containern ist so ein einfaches Adressieren nicht mehr möglich. Verkettete Listen liegen beispielsweise völlig verstreut im Arbeitsspeicher und müssen Schritt für Schritt durchschritten iteriert werden. Um dennoch array-artiges Verhalten herzustellen, wurden Iteratoren geschaffen. Sie verstecken alle Aufgaben vor dem Programmierer, verhalten sich nach außen fast wie Arrayindizes und werden genauso angewendet. Auch sie sind als Templates implementiert und sie werden zusammen mit Containern geliefert. Häufig müssen sie nicht einmal instanziiert werden. Weiterführende Informationen finden sich unter [11] Vector Diese Container sind Arrays nachempfunden. Ihre Iteratoren sind Random-Access-Iteratoren und bieten damit den höchsten Freiheitsgrad. Es kann jedes Element jederzeit angesprochen werden, ohne auf eine Reihenfolge zu achten. Tatsächlich liegen die Inhalte von Vectoren, genau wie bei Arrays, direkt hintereinander im Speicher. Dies macht den Zugriff sehr schnell, ist allerdings beim Wachsen oder Schrumpfen nachteilig. Wenn der reservierte Speicher voll ist und der Vector noch mehr Daten aufnehmen soll, kann es sein, dass er keinen angrenzenden Speicher mehr bekommt. Dann muss er sich komplett in einen anderen, größeren Bereich kopieren. Beim Einfügen und Löschen von Elementen am Anfang oder am Ende ist dieser Datencontainer schneller als alle anderen. Elemente an Zwischenpositionen einzufügen oder zu löschen ist allerdings sehr teuer, weil dann Elemente umkopiert werden müssen. Aus diesem Grund sollte man auch keinen Vector einsetzen, wenn die Daten sortieren werden sollen. Beim Lesen und Schreiben auf zufälligen Positionen, ist ein Vector allerdings weitaus effektiver, als andere Strukturen. Listing 7 zeigt die Deklaration eines Vectors und den Zugriff auf ihn. Eine gute Übersicht zu diesem Container bietet [12]. Listing 7: Deklaration und Zugriff eines integer Vectors

17 2 Programmiertechnische Grundlagen Map Dieser Datencontainer-Typ wurde eingeführt um das sogenannte mapping zu realisieren. Dabei wird einem Objekt ein anderes zugeordnet. Sie sind dann fest miteinander verknüpft und wenn man das Eine kennt, kann man auf das Andere zugreifen. Üblicherweise funktioniert mapping in beide Richtungen, das ist hier allerdings nicht der Fall. Eine Map nimmt zwei Datentypen entgegen. Einer ist der sogenannte Key und der andere ist der Value. Wenn man der Map einen gültigen Key übergibt erhält man den passenden Value zurück. Eine Map kann jeden Key nur einmal enthalten und jeder Key hat exakt einen Value. Für abweichendes Verhalten können Multimap, Set oder Multiset bemüht werden[13]. Im Listing 8 wird eine Map dazu verwendet jeder Person ein Konto zuzuordnen. Um etwas in die Map zu schreiben, müssen immer Key und Value übergeben werden. Dies geschieht über spezielle Template-Objekte vom Typ Pair, die üblicherweise nicht explizit deklariert werden. Zeile 15 im vorliegenden Listing demonstriert dies. Maps sollten eingesetzt werden, wenn typische MappingProbleme auftreten und die Reihenfolge der Elemente unbekannt ist. Dies ist zum Beispiel der Fall, wenn Daten von mehreren Kunden in einen Container gelegt werden. Es liegt in der Natur der Nebenläufigkeit, dass nicht genau vorhergesagt werden kann, wann welcher Teilnehmer fertig ist. Dadurch liegen die Daten in einer unbekannten Reihenfolge im Speicher. Wenn jedes Datum allerdings eine Identifikationsnummer erhält, so kann diese als Key verwendet werden und der Kunde kann auf die Map zugreifen, als wäre sie ein Array. Freilich wird dies mit Geschwindigkeitsverlusten bezahlt. Listing 8: Deklaration, Zuweisung und Zugriff einer Map concurrent_queue Diese Container sind eine Sonderform der Queue. Mit ihr werden FIFO-Speicher gebildet, die je nach Bedarf wachsen und schrumpfen und die das gleichzeitige Hinzufügen und Entfernen durch verschiedene Kunden erlauben. Dabei liegt es natürlich nicht im Verantwortungsbereich der Queue in welcher Reihenfolge die Daten in ihr abgelegt werden. Es ist auch nicht ohne Weiteres möglich diese Reihenfolge anschließend herauszufinden, um dann auf ein bestimmtes Element zuzugreifen. Deshalb sollte eine concurrent_queue nur eingesetzt werden, wenn es darauf ankommt, dass das Element der Reihe nach verarbeitet werden, oder wenn die Reihenfolge egal ist.

18 2 Programmiertechnische Grundlagen 13 Eine concurrent_queue garantiert also, dass gleichzeitiges Lesen und Schreiben durch mehrere Kunden weder die Struktur des Containers, noch die Daten zerstört. Dadurch eignet sie sich besonders gut zum Puffern von single writer / multiple reader Datenströmen. Bei solchen Strömen legt eine Quelle ihre Daten in der Queue ab und eine Gruppe von Datensenken verarbeitet sie. Listing 9 demonstriert die Verwendung der concurrent_queue. Sie ist unter [10] ausführlich dokumentiert. Zu beachten ist, dass pop() und try_pop(), im Gegensatz zur std::queue, Daten liefern und das Original löschen. Listing 9: Deklaration, Zuweisung und Zugriff einer concurrent_queue Intelligente Zeiger shared_ptr und shared_array Im Kapitel über Fabrikfunktionen wurde angesprochen, dass Objekte, die zur Laufzeit mittels Operator new erzeugt wurden, keinem Gültigkeitsbereich angehören. Um den belegten Speicher wieder zur Verfügung zu stellen, müssen solche Objekte explizit per Operator delete gelöscht werden. Dadurch eignen sich diese Daten gut dazu, zwischen verschiedenen Objekten per Zeiger hin und her gereicht zu werden. Allerdings ist gerade dieser Anwendungsfall schwierig. Denn je mehr Kunden solche herrenlosen Objekte entgegen nehmen und weiter reichen, desto schwieriger wird es, die richtige Stelle zur Freigabe zu finden. Es muss nicht nur darauf geachtet werden, die Daten zum richtigen Zeitpunkt zu löschen; sondern auch und vor Allem, dass sie in jedem Fall wieder frei gegeben werden. Bei großen Projekten mit komplizierten Klassen, die zahlreiche if und switch Anweisungen, Unterfunktionen, Ausnahmebehandlung und vielleicht sogar Rekursion enthalten, kann dies schnell alles andere als trivial werden. Abhilfe schaffen die sogenannten intelligenten Zeiger. Boost liefert hier die besonders interessanten shared_ptr und shared_array. Sie verhalten sich völlig analog, der einzige äußerliche Unterschied ist, dass mit shared_array Arrays verwaltet werden und mit shared_ptr einzelne Objekte. Im folgenden Text wird nur von Letzterem gesprochen, ohne shared_arrays diskriminieren zu wollen. Listing 10 zeigt die Deklaration und Verwendung von shared_ptr. Unter [14] und [15] sind die Dokumentationen zu finden. Mit shared_ptr bekommt man ein Template zur Hand, das intelligente Zeiger für beliebige Datentypen erzeugen kann. Das Besondere ist, dass ein shared_ptr beliebig oft kopiert und verteilt werden kann. Dabei unterliegen sie den normalen Regeln des Gültigkeitsbereichs und werden automatisch zerstört. Das Nützliche ist nun folgendes: Sobald die letzte Kopie eines shared_ptr zerstört wurde, wird automatisch das Gezeigte zerstört. Dieses Verhalten führt dazu, dass sich Programmierer keine Gedanken mehr darüber machen müssen, wann mittels Operator new erzeugte

19 2 Programmiertechnische Grundlagen 14 Listing 10: Deklaration und Anwendung von shared_ptr Daten zu löschen sind. Es genügt die Daten mit einem shared_ptr anstatt einem Rohzeiger zu verwalten und sobald kein Programmteil mehr auf die Daten zeigt, werden sie gelöscht. Es müssen allerdings zwei Dinge beachtet werden: Man sollte einen shared_ptr nicht mittels Operator new erzeugen, außer man verwaltet diesen ebenfalls mit einem shared_ptr oder shared_array. Angenommen Objekt A besitzt einen shared_ptr, der auf Objekt B zeigt. Und Objekt B besitzt einen shared_ptr, der auf Objekt A zeigt. Nun ist es egal welche shared_ptr in der Außenwelt existieren, die Objekte halten sich gegenseitig am Leben. Das selbe Problem entsteht, wenn Objekt A einen shared_ptr auf sich selbst besitzt. Wenn auf diese zwei Punkte geachtet wird, dann kommt ein C++-Programm ohne jeden (expliziten) Aufruf von Operator delete aus und erzeugt trotzdem keine Speicherlecks. Um die Beachtung dieser und anderer Punkte zu erzwingen und überhaupt gute Programmierpraxis durchzusetzen, wurden Programmierrichtlinien erfunden. Im gedruckten Anhang findet sich einen Satz solcher Regeln, der in diesem Projekt entwickelt wurde.

20 3 USB 2.0 Universal Serial Bus 3 15 USB 2.0 Universal Serial Bus Um den Datentransport einfach zu halten und das fertige Gerät möglichst einfach zu transportieren, wurde USB als Schnittstelle für das Projekt Nepteron ausgewählt. Im ersten Unterkapitel wird ein sehr kurzer Überblick über dieser Technologie gegeben, um anschließend die Auswahl der Betriebsart diskutieren zu können. Anschließend folgt eine Vorstellung des verwendeten Chips und eine grundlegende Einführung in die dazugehörige C++-Bibliothek. Die Quellen der Wahl für dieses Kapitel sind [16] und [17]. 3.1 Grundsätzliches USB ist eine Master/Slave Architektur, und die Geräte werden über verdrillte, differentielle und geschirmte Zweidrahtleitungen verbunden. Dabei fungiert der PC als Master und alle angeschlossenen Geräte sind Slaves. Slaves können wie üblich untereinander nicht kommunizieren, die einzigen Kommunikationsrichtungen sind (Master Slave) und (Slave Master). Dabei wird die Richtung immer aus Sicht des Masters definiert. Das heißt Eingaben sind Daten, die von einem Slave zum Master gehen und Ausgaben sind Daten, vom Master zu einem Slave. Slaves senden nur Daten, wenn sie dazu aufgefordert wurden. Wenn sie Daten zu versenden haben, legen sie diese in einem Puffer ab und warten, bis sie danach gefragt werden. Wenn der Master Daten zu einem Slave senden will, tut er das meist ohne Vorwarnung. 3.2 Diskussion der Betriebsarten Physikalisch besteht zwischen Master und Slave nur eine einzige Verbindung. Allerdings wurden mehrere logische Verbindungen eingeführt, die man Endpoints nennt. Es gibt drei grundlegende Sorten von Endpoints: Controll In Out Der Controll-Endpoint trägt immer die Nummer 0 und ist als einziger bidirektional. Über ihn melden sich neue Geräte beim PC an und wickeln verwaltungstechnische Aufgaben ab. Dies ist nötig, weil nicht jedes Gerät jede Art von Endpoint unterstützt und weil zu Beginn der PC auch gar nicht weiß, um welches Gerät es sich handelt. Zehn Prozent der Bandbreite sind für diese Transferart reserviert. Außerdem garantiert sie, durch integrierte Fehlerkorrektur, die Korrektheit der Daten. Während des eigentlichen Betriebs wird diese Transferart nicht eingesetzt. Alle anderen Endpoints sind wie gesagt unidirektional und gehören weiterhin zu einer der drei Betriebsarten, die im Folgenden genauer besprochen werden. Dazu sei voraus geschickt, dass der Nepteron-Prozessor einen Datenstrom konstanter Geschwindigkeit produziert, der unbedingt korrekt übertragen werden muss. Korrektheit ist hier wichtiger als konstante Geschwindigkeit. Kapitel 4 wird näher auf diese Umstände eingehen Interrupt-Transfer Da der USB ein Master/Slave System ist, können die Geräte nur Daten senden, wenn sie vom PC dazu aufgefordert werden. Manche Geräte benötigen allerdings Interruptverhalten und deshalb wurde die diese Transferart geschaffen. Tatsächlich handelt es sich hier um einen Polling-Betrieb mit einer garantierten Wartezeit von 1 ms. Er wird von Geräten wie Tastaturen oder Mäusen genutzt, aber es können auch Herzschlag-Signale o. Ä. damit realisiert werden. Interrupt-Transfers unterliegen ebenfalls der integrierten Fehlerkorrektur und bekommen zusammen mit Isochronous-

21 3 USB 2.0 Universal Serial Bus 16 Transfers 90% der Bandbreite zugesichert. Damit ist garantiert, dass ein gerade gepolltes Gerät anliegende Daten auch senden kann. Das Resultat ist eine Art Interruptverhalten, das den Ansprüchen der meisten Geräte genügt. Zum Übertragen von größeren Datenströmen ist es allerdings ganz und gar nicht geeignet Isochronous-Transfer Dies ist der eigentliche Streaming-Modus des USB. Er wird vorwiegend von Audio- und Videogeräten genutzt, die große Datenmengen mit konstanter Datenrate produzieren. Dabei kommt es naturgemäß mehr auf die Aktualität, als auf die Korrektheit der Daten an. Deshalb teilt sich diese Transferart 90% der Datenrate, mit dem zeitkritischen aber datenschwachen Interrupt-Transfer. Ein Gerät, das isochrone Datenübertragung einsetzen möchte, teilt dies bei der Anmeldung am Bus dem PC mit und bittet gleichzeitig um eine gewisse Bandbreite. Da verspätete Daten nutzlos sind, wird auf die interne Fehlerkorrektur verzichtet. Das heißt bei Isochronous-Transfer wird die Bandbreite garantiert, es gibt aber keinen Anspruch auf Korrektheit der Daten. Falsche Daten sind im Rahmen dieser Arbeit allerdings inakzeptabel und deshalb kann diese Betriebsart nicht ohne Weiteres eingesetzt werden Bulk-Transfer Hierbei handelt es sich um das Gegenstück zum Isochronous-Transfer. Er ist dazu gedacht größere, zeitunkritische Datenmengen sicher zu übertragen. Dazu wird dieser Transferart zwar nur 10% der Bandbreite garantiert, falls der Bus frei ist kann sie aber mehr nutzen. Die Daten unterliegen der internen Fehlerkorrektur und sind somit garantiert korrekt. Der Nepteron-Prozessor produziert einen konstanten Datenstrom, der mit sehr hoher Zuverlässigkeit und fester Datenrate übertragen werden soll. Da es keine Übertragungsart gibt, die beides garantiert, müssen die Ansprüche reduziert werden. Ein Absenken der Datenrate ist unerwünscht, lässt das Projekt aber nicht scheitern; eine fehlerhafte Übertragung allerdings schon. Deshalb wurde diese Übertragungsart gewählt und der Nepteron-Prozessor mit einer Wartefunktion versehen. Damit diese nicht aktiv werden muss, werden die Anwender des Systems angewiesen, außer Maus und Tastatur, keine USB-Geräte zu verwenden. Tun sie es doch, ist mit entsprechenden Verzögerungen zu rechnen.

22 3 USB 2.0 Universal Serial Bus Der verwendete USB-Chip Abbildung 1: Der CY7C68013A-128AX USB-Chip Um den Nepteron-Prozessor mit einem PC zu verbinden, ist ein physikalisches USB-Interface nötig. Damit Chipfläche und Entwicklungszeit eingespart wird, wird eine fertige Lösung der Firma Cypress eingesetzt. Abb. 1 zeigt ein Foto dieses Chips. Er wird über 8 oder 16 Datenleitungen und einige Kontrollleitungen an den Nepteron-Prozessor angeschlossen. [4] beschreibt sehr detailliert wie dies realisiert werden kann. Der Chip bietet komplette USB 2.0 Funktionalität und hat einen 8051 Kern integriert, der in den Datenstrom eingreifen kann, aber nicht muss. Im High-Speed Betrieb klinkt er sich völlig aus, da seine Taktrate zu gering ist und er den Datenstrom bremsen würde. Er ist vor Allem während der Anmeldung am PC aktiv und bei Fehlersuchen hilfreich, da er sowohl in Assembler als auch in C programmiert werden kann. Cypress liefert verschiedene Firmwares mit, mit denen man den Chip in Betrieb nehmen oder mit ihm experimentieren kann. Weitere Informationen finden sich unter [18]. 3.4 Die CyAPI Diese C++ Klassenbibliothek wird von Cypress zur Verfügung gestellt [19], um ihre USBController zu unterstützen. Dazu stellt sie verschiedene Methoden zur Verfügung, die mit dem CyUsb.sys Treiber kommunizieren und erspart dem Anwender die Auseinandersetzung mit der sehr umfangreichen WinAPI. Es können alle USB Geräte, die mit diesem Treiber kommunizieren, mit der CyAPI gesteuert werden. Wie eine Verbindung zu solch einem Gerät aufbaut wird, wie es einstellt und wie Daten gesendet und empfangen werden, soll in diesem Kapitel gezeigt werden. Als Beispiel wird das Programm NepteronSpeedTest2.7 verwendet Verbindungsaufbau Im ersten Schritt wird, wie Listing 11 in Zeile 1 zeigt, ein Objekt der Klasse CCyUSBDevice erstellt. Dieses Objekt beinhaltet alle CyAPI-Methoden und wird mit einem Zeiger verwaltet. Da es mit Operator new erzeugt wurde, kann es an beliebige Objekte weitergegeben werden. Dadurch kann der USB-Verbindungsaufbau und die USB-Verwaltung in einem spezialisierten Objekt behandelt werden, das seinen Kunden lediglich einen Zeiger übergibt. Dadurch müssen diese Kunden nur die Sende- und Empfangsmethoden aufrufen.

23 3 USB 2.0 Universal Serial Bus 18 Listing 11: Aufbau der USB Verbindung, inklusive Fehlerbehandlung Nachdem das Objekt in Zeile 1 erzeugt wurde, kann eine Verbindung aufgebaut werden. Der Treiber CyUsb.sys übernimmt automatisch die Behandlung der niedrigen Schichten. Dazu gibt er unter anderem jedem angeschlossen Gerät mit Cypress-Chip eine Identifikationsnummer. Diese ist nur für eine Sitzung gültig und wird aufsteigend verteilt. Da in diesem Projekt nur ein einziges Cypress-USB-Gerät verwendet wird, wird immer das Gerät mit der Nummer 0 geöffnet. Um Fehler zu vermeiden, überprüft das Programm in Zeile 3, ob überhaupt Cypress-USB-Geräte angeschlossen sind und versucht das Gerät mit der Nummer 0 zu öffnen. Die Methode USBDevice->Open(0) wird hier sowohl zum Überprüfen, als auch zum Öffnen verwendet. Falls das Gerät bereits geöffnet war, tut sie nichts und liefert FALSE zurück. Falls es nicht geöffnet war, baut es eine Verbindung auf und liefert bei Erfolg TRUE zurück. Dieser Rückgabewert wird mit der Anzahl der angeschlossenen Cypress-USB-Geräte UND verknüpft, um bibliotheksinterne Fehler auszuschließen. Anschließend wird mit IsOpen() sicher gestellt, ob das Gerät tatsächlich geöffnet wurde. Bei einem positiven Rückgabewert ist die Verbindung aufgebaut und zur Nutzung bereit Identifikation und Parametrierung Nachdem die Verbindung aufgebaut wurde, können Parameter des Gerätes ausgelesen und geändert werden. Listing 12 zeigt das Einlesen der wichtigsten Werte. FriendlyName ist ein Klartext-Name, der einige ASCII-Zeichen lang sein kann. Er wird in der Firmware des USB-Controllers festgelegt und kann dort beliebig geändert werden. Zur Erklärung der Begriffe Endpoint, AlternateInterface und Bulk sei auf [16] verwiesen. Sie sind für das weitere Verständnis dieser Arbeit allerdings nicht nötig. Auf den Parameter XferSize wird in Kapitel näher eingegangen und Listing 13 zeigt die vorgenommenen Änderungen. Listing 12: Einlesen der wichtigsten Parameter Listing 13: Änderung der Parameter

24 3 USB 2.0 Universal Serial Bus Senden und Empfangen Zum Senden und Empfangen wird die Methode XferData() eingesetzt. Je nachdem für welchen Endpoint sie aufgerufen wird, sendet oder empfängt sie Daten. Nach außen verhält sie sich für beide Fälle analog, es wird daher hier nur vom Senden gesprochen. Es sind noch weitere Methoden zum Senden/Empfangen verfügbar, [17] liefert hier geduldige Unterstützung. XferData() erwartet zwei Parameter, zum Einen die Adresse eines Byte-Arrays und zum Andern die Anzahl der zu sendenden Bytes. Letzteres ist eine Übergabe als Referenz und nachdem die Methode abgearbeitet ist, enthält die Variable die Anzahl der tatsächlich gesendeten Bytes. Die Methode kehrt erst zurück, wenn entweder alle Bytes gesendet wurden oder wenn das Gerät einen Timeout auslöst. Dieses Verhalten nennt sich auch blocking-call und es ist der Grund, warum die USB-Datenübertragung in NepteronSoftware als eigener Task ausgeführt werden muss. Listing 14 zeigt einen Code-Ausschnitt, der das hier Gesagte verdeutlichen soll. Listing 14: Daten per USB senden Optimierung der Datenrate XferSize und HLpktSize Bei den ersten Versuchen mit der Cypress Technik wurden Datenraten erreicht, die von jeder RS232 Schnittstelle geschlagen werden können. Nach einiger Recherche wurde ein Assembler Programm von Cypress auf den 8051 Kern überspielt, mit dem die maximale USB Datenrate gemessen werden konnte[21]. Das theoretische Maximum von USB liegt bei 60 MB/s. Mehrere Messungen mit dieser speziellen Firmware ergaben Geschwindigkeiten von ca. 55 MB/s. Damit konnte ein Fehler in der Hardware ausgeschlossen werden. Als Ursache stellten sich die zwei Parameter XferSize und HLpktSize heraus. Mit letzterem kürzt der Autor den Namen High Level Paket Size ab und bezeichnet damit die Anzahl der Bytes die an XferData() übergeben werden. In Listing 14 heißt dieser Parameter len. HLpktSize muss immer ein Vielfaches der USB-Paketgröße sein. Diese ist bei Bulk-Transfer 512 Byte und wird vom Autor entsprechend LLpktSize genannt. HLpktSize ist also die Datenmenge, die mit einem Aufruf von XferData() verarbeitet wird. Ein größerer Wert beschleunigt den Datentransfer, weil XferData() seltener aufgerufen werden muss und somit weniger Code-Overhead entsteht. Im Laufe dieser Arbeit stellte der Autor fest, dass die Kosten für einen Call bei einem C++ Programm, in einer Betriebssystem-Umgebung, wesentlich höher sind, als bei einem C Programm, das direkt auf einem (µ)prozessor abläuft. Bei Letzterem müssen lediglich die CPU-Register und die Rücksprungadresse auf den Return-Stack gelegt werden und anschließend die funktionsspezifischen Dinge, wie Parameterübergabe und Anlegen von lokalen Variablen, erledigt werden. Diese Aktionen müssen in C++ auch getätigt werden, allerdings kommen noch die vergleichsweise enormen Betriebssystemaufgaben hinzu. Betriebssysteme legen Variablen im sogenannten Stack ab und tragen im Heap ein, wo sie liegen.3 Um seine Konsistenz zu garantieren wird bei jedem Call automatisch ein ca. 50-zeiliges Assembler 3 Stack und Heap sind Bereiche im Arbeitsspeicher, die aufeinander zu wachsen.

25 3 USB 2.0 Universal Serial Bus 20 Programm aufgerufen, dass den Stack prüft. Allein der Call dieses Assemblerprogramms kostet bereits so viel, wie der Call einer parameterlosen Funktion in C! Hinzu kommt die Ausführung der 50 Zeilen Assemblercode. Des Weiteren ist XferData() eine Bibliotheksfunktion, die selbst eine ungewisse Anzahl an Funktionen aufruft und eine unbekannte Anzahl an Objekten erzeugt. Jede Objekt-Erzeugung wird von dem Aufruf der entsprechenden Konstruktor-Methode begleitet, die wieder die 50 Zeilen Assembler Code zur Stacküberprüfung erfordern. Diese Betrachtung sollte deutlich machen, dass Aufrufe von C++ Methoden sehr teuer sein können und daher sparsam mit ihnen umgegangen werden sollte. Daher ist es wichtig, dass der HLpktSize entsprechend groß gewählt wird, damit XferData() möglichst selten aufgerufen werden muss. XferData() übernimmt als len-parameter eine 32 Bit Variable, sie könnte also bis zu 2 GiB an Daten auf einmal abarbeiten. Welcher Wert tatsächlich verwendet wird, hängt von der Anwendung ab. Tests haben gezeigt, dass im Bulk-Betrieb bei einer HLpktSize von 127*512 die maximale Datenrate des Chips CY7C68013A-128AX erreicht wird. Abb. 2 zeigt das Messprotokoll eines solchen Tests und gibt Netto-Datenraten an. NepteronSoftware verwendet schließlich eine HLpktSize von 1024*512, da High-Level-Pakete hier Arbeitseinheiten für andere Programmteile darstellen und diese ausreichend groß sein müssen, um die Vorteile von mehreren Prozessoren nutzen zu können. Auf diese Sachverhalte geht Kapitel 5 genauer ein. Der Parameter XferSize gibt an, wie viel Platz der USB-Treiber sich im Arbeitsspeicher reserviert. Er sollte unbedingt auf das Maximum von 0xFFFF, was ca. 65 kb entspricht, gesetzt werden. Abbildung 2: Messprotokoll zur maximalen Datenrate

26 4 Untersuchung der Datenstrecke 4 21 Untersuchung der Datenstrecke Bevor mit der Entwicklung von NepteronSoftware begonnen werden konnte, sollte festgestellt werden, ob sich das Vorhaben überhaupt realisieren lies. Dazu wurden Anforderungen formuliert und ein Blockdiagramm der Strecke erstellt. Anschließend wurde die Strecke mit verschiedenen Mitteln untersucht und ein Fazit gezogen. Eines dieser Mittel ist die eigens geschriebene Software SpeedTest2.7. Ihr Flussdiagramm ist im gedruckten Anhang in Abb. 28 zu finden. 4.1 Die Datenstrecke Abbildung 3: Die Datenstrecke Die eigentliche Simulation läuft zur Zeit dieser Arbeit auf einem Virtex-4 FPGA ab. Wie Abb. 3 zeigt, wird dieser mit acht Datenleitungen an den USB-Chip von Cypress angeschlossen. Dieser Chip enthält das GPIF, einen 8051 Kern und die nötige Hardware zur USB-Kommunikation, hier USB Slave genannt. An der anderen Seite des Chips werden die USB-Datenleitungen angeschlossen und mit dem PC verbunden. Hier werden die Daten zuerst vom USB-Host auf dem Mainboard entgegen genommen, passieren die niedrigen Stufen des Betriebssystems und werden schließlich durch einen Treiber abgeholt. Sobald dies geschehen ist, kann ein C++ Programm die Rohdaten verarbeiten und einem Benutzer zur Verfügung stellen. Die entgegen gesetzte Richtung verhält sich analog. 4.2 Anforderungen Die grundlegende Absicht des Projekts Nepteron ist es, größere Mengen Simulationsdaten innerhalb einer vertretbaren Zeit zu liefern. Dabei wird ein Echtzeitverhalten in der Form 5 Minuten Zellverhalten zu simulieren dauert 5 Minuten angestrebt. Einerseits sollen die Daten einen groben Überblick des gesamten Netzes bieten. Das heißt, es wird eine übersichtliche Darstellung benötigt, die das Verhalten über der Zeit darstellt. Hierbei ist wichtig, dass die Simulation wenig Zeit in Anspruch nimmt. Vollständig müssen die Daten nicht sein, da sie in einer Übersicht sowieso nicht komplett dargestellt werden. Es muss allerdings ausgewählt werden können, welche Daten übertragen werden sollen. Andererseits soll die Simulation zum detaillierten Betrachten einzelner Zellen und Netzbereiche verwendet werden. Hierbei ist wichtig, dass alle Ergebnisse korrekt übertragen werden. Übertragungsfehler sind hier unter keinen Umständen zu tolerieren, da der Anwender sich ansonsten bei jeder Anomalie fragen müsste, ob sein Verständnis der biologischen Vorgänge oder die Simulation fehlerhaft ist. Des Weiteren könnten falsche Simulationsergebnisse versehentlich als richtig angenommen werden und so zu falschen Forschungsergebnissen führen. Aus diesen Gründen ist eine fehlerfreie Übertragung in jedem Fall zu garantieren und es gilt der Leitsatz Keine Information ist besser als eine falsche..

27 4 Untersuchung der Datenstrecke 22 Die Simulationsgeschwindigkeit hängt unter Anderem direkt mit der Übertragungsgeschwindigkeit zusammen. Der Nepteron-Prozessor produziert ca. 15 MB Daten pro Sekunde und kann ca. 1 ms dieses Datenstroms puffern. Sobald seine Puffer voll gelaufen sind müssen alle Berechnungen angehalten werden und dürfen erst fortfahren, wenn wieder genügend Speicher frei ist. Um die maximale Rechengeschwindigkeit zu erhalten ist es notwendig, dass die Datenrate im Mittel 15 MB/s beträgt. Des Weiteren muss die Methode XferData() so regelmäßig aufgerufen werden, dass die Puffer nie überlaufen. Es wurden schließlich folgende Anforderungen an NepteronSoftware und Datenstrecke gestellt: Die Daten müssen korrekt übertragen werden. Die Daten sollen so schnell abgeholt werden, wie sie zum USB-Chip geliefert werden. NepteronSoftware muss also die Daten so schnell und regelmäßig abholen, dass die Puffer des Nepteron-Prozessor nicht überlaufen. Konkret: Die durchschnittliche Datenrate darf für jedes beliebige 1 ms Zeitintervall nicht unter ca. 15 MB/s fallen. Die Daten müssen so schnell verarbeitet werden, wie sie geliefert werden Datenrate und Fehlerfreiheit Nach einer Beschäftigung mit den USB-Übertragungsarten war klar, dass Datenrate und Fehlerfreiheit nicht gleichzeitig garantiert werden konnten. Als Kompromiss wurde, wie in Kap erläutert, Bulk-Transfer gewählt. Diese Übertragungsart ist garantiert fehlerfrei, aber die Übertragungsgeschwindigkeit ist prinzipiell ungewiss. So lang ein Bulk-Transfer-Gerät aber das einzige Gerät am USB ist, steht ihm natürlich die gesamte Bandbreite zur Verfügung. Ein weiteres Problem war, dass Windows XP als Betriebssystem eingesetzt werden sollte. Bei diesem System hat der Anwendungsprogrammierer grundsätzlich keinen Einfluss darauf, wann einem Programm Rechenzeit zur Verfügung gestellt wird. Dadurch ist es möglich, dass dem Programm die CPU so lang entzogen wird, dass es zu Aussetzern kommt, die so lang sind, dass der Nepteron-Prozessor warten muss. Für dieses Problem wurden drei Lösungen entwickelt: 1. Prozess-Prioritäten stehlen 2. Prozessor-Affinitäten manipulieren 3. Ein frisch installiertes Betriebssystem auf einem leistungsstarken Computer Die ersten beiden Lösungen benötigen Eingriffe in das Betriebssystem und werden in [5] genauer erläutert. Hier sollen sie nur erwähnt werden, hat sich doch bei zahlreichen Experimenten gezeigt, dass Lösung 3 einwandfrei funktioniert.

28 4 Untersuchung der Datenstrecke Untersuchung der Strecke In diesem Kapitel wird darauf eingegangen, wie die Strecke untersucht wurde. Außerdem werden die Ergebnisse der Untersuchungen vorgestellt Maximale Bruttodatenrate Nachdem erste Versuche mit der CyAPI zwar eine USB-Verbindung hergestellt hatten, diese allerdings sehr langsam war, wurde eine spezielle Firmware auf den 8051 gespielt [20]. Damit war es möglich die Datenrate zwischen 8051 und dem USB-Host auf dem Mainboard zu messen. Die oberen Schichten haben keinen Einfluss, weil die Zeitmessung im 8051 stattfindet. Sie werden erst aktiv, wenn über ein Cypress Programm die Ergebnisse angefordert werden. Ein solches Messergebnis zeigt Abb. 4. Die letzten vier Zeilen enthalten die Ergebnisse und wurden nach [20] in Datenraten umgerechnet. Dabei ergaben mehrere Messungen Bruttodatenraten von ca. 55 MB/s, wobei 60 MB/s das theoretische Maximum ist. Abbildung 4: Messung der maximalen Datenrate mittels spezieller Firmware Fehlerrate Nachdem gezeigt wurde, dass die USB Verbindung grundsätzlich funktioniert, wurde die C++ Software SpeedTest2.7 geschrieben. Mit ihr wurde die vorliegende Strecke genauer untersucht und die in Kapitel beschriebenen Parameter XferSize und HLpktSize konnten optimiert werden. Dadurch wurden schließlich USB-typische Datenraten erreicht. Um die Fehlerrate zu überprüfen wurde ein FPGA so programmiert, dass dieser Testdaten in einstellbarer Datenrate sendete. Bei den Testdaten hatte jedes USB-Paket eine eigene Nummer und

29 4 Untersuchung der Datenstrecke 24 die Nutzdaten wurden von Byte zu Byte um drei erhöht. SpeedTest2.7 erzeugte nach dem selben Muster einen Vector voller Vergleichsdaten und forderte anschließend die selbe Menge Daten per USB an. Darauf hin wurde eine Zeitmessung gestartet. Nachdem alle Daten empfangen waren, wurde der Empfangs- mit dem Vergleichsvector verglichen. Anschließend wurde die Daten- und Fehlerrate ausgegeben. Abb. 5 zeigt das Protokoll einer Messung, bei der der FPGA auf die Datenrate des Nepteron-Prozessors eingestellt war. Es ist zu erkennen, dass die Daten genauso schnell empfangen, wie gesendet wurden, denn die Gesamtdatenrate entspricht der Senderate. Ebenfalls zu erkennen ist, dass die Fehlerrate bei 0% liegt. Der Unterschied bei den Angaben Expected Data und Received Data rührt daher, dass Letzteres als Float und Ersteres als Integer geführt wird. Während dieser Tests wurden insgesamt etwa 20 GB Daten gelesen und nicht ein einziges Byte war fehlerhaft. Die ersten Tests zum Senden von PC zu FPGA verliefen ebenfalls erfolgreich und fehlerfrei. Abbildung 5: Messprotokoll SpeedTest2.7 zum Daten lesen

30 4 Untersuchung der Datenstrecke Maximale Nettodatenrate Dem Nepteron-Projekt stehen mehrere Platinen mit Cypress-USB-Chips zur Verfügung. Deshalb konnten einige Tests ohne FPGA durchgeführt werden. In diesen Fällen übernahm der 8051 die Rolle als Datenquelle/-senke. Die Firmware blksrc (bulk source) war hier besonders hilfreich. Wenn dieses Programm Daten empfängt, werden sie sofort verworfen und es fordert umgehend neue Daten an. Wenn es senden soll, sendet es den aktuellen, zufälligen Inhalt der Sende Puffer und setzt anschließend das Ich habe neue Daten zu senden -Flag. Dadurch können laufend Datenübertragungen in beide Richtungen ausgelöst werden, ohne, dass eine Datenverarbeitung im Gerät bremsend wirkt. Durch Zeitmessungen im C++-Programm konnten die maximalen netto Datenraten ermittelt werden. Abb. 6 zeigt, dass zwischen dem Cypress-USB-Chip und einem C++Programm sehr gute Datenraten möglich sind. Die Angaben zur Fehlerrate sind hier natürlich falsch, weil zufällige Daten empfangen werden. Abbildung 6: Maximale Nettodatenrate, Lesen und Schreiben (v.l.n.r) General-Programmable-Interface Totzeit Abbildung 7: GPIF Totzeit beim Lesen von Daten aus FPGA Zusammen mit dem FPGA konnten solche Datenraten leider nicht erzielt werden. Die erreichten 15 MB/s erfüllen zwar die Anforderungen völlig, aber es wurden trotzdem Versuche unternommen, die Datenraten aus Abb. 6 zu erreichen. Nach einigen typischen Debug-Anstrengungen mit SignalLEDs und Oszilloskop stellte sich heraus, dass ein Teil des USB-Chips die Schuld trägt. Das sogenannte General-Programmable-Interface (GPIF) es verwaltet den parallelen Datenbus zwischen FPGA und dem Rest des USB-Chips wurde als Flaschenhals identifiziert.

31 4 Untersuchung der Datenstrecke 26 Um zu Überprüfen, ob das GPIF korrekt arbeitet wurde nämlich im FPGA ein Puffer mit vier USBPaketen gefüllt und das GPIF aufgefordert, diese zu lesen. Gleichzeitig wurde die Firmware des 8051 so verändert, dass ein Pin High-Pegel führt, solange das GPIF Daten einliest. Wie in Abb. 7 zu sehen ist, entstehen relativ große Totzeiten zwischen den Lesevorgängen. Vermutlich wird hier von einem Lese-FIFO auf ein anderes umgeschaltet. Um die volle Datenrate zu erreichen, muss das GPIF wohl mit 16 Datenleitungen betrieben werden. Falls in Zukunft höhere Datenraten nötig werden, kann an dieser Stelle angeknüpft werden Verarbeitungsdauer im PC All diese Versuche wurden auf handelsüblichen PC-Systemen mit 2,6 GHz DualCore Prozessoren durchgeführt. Abb. 8 zeigt den CPU-Lastverlauf von SpeedTest2.7. Da das Programm nicht darauf ausgelegt wurde mehrere Kerne zu nutzen, ist immer nur einer der beiden Prozessoren aktiv. Während der Vergleichsvector befüllt wird, ist ein Kern zu 100% ausgelastet und der andere wird nicht genutzt. Während der USB-Leseoperationen ist dies nicht der Fall, weil hier laufend die Methode XferData() aufgerufen wird. Sie leert den Treiberpuffer und wartet anschließend, bis der Treiber wieder genügend Daten zur Verfügung stellt. Diese Zeit ist von der USB-Datenrate abhängig und jeweils so kurz, dass in der Grafik keine Totzeiten zu sehen sind, sondern Auslastungen kleiner 100%. Welcher Kern den nächsten XferData() Call bearbeitet, scheint zufällig zu sein und wird vom Betriebssystem bestimmt. Außerdem ist in dieser Zeit auch der Treiber aktiv, er läuft im rot eingezeichneten Kernel-Mode und erklärt die Belastung auf beiden Kerne. Nachdem der Empfang abgeschlossen ist, werden die Daten ausgewertet, was erneut zu einer völligen Auslastung eines Kerns führt. Abbildung 8: typischer CPU-Lastverlauf von SpeedTest2.7 In Abbildung 5 ist zu erkennen, dass die Verarbeitung der ankommenden Daten drei mal so lang dauert wie ihr Empfang. Dieser Umstand ist für die angestrebte Echtzeitverarbeitung sehr hinderlich und wurde deshalb genauer untersucht. Anfangs wurde vermutet, dass die Datencontainer vom Typ Vector zu langsam seien. Deshalb wurde SpeedTest komplett auf statische Arrays umgestellt. Sie sind der schnellste 'Container' überhaupt, bestehen sie doch nur aus einem zusammenhängenden Speicherbereich mit Zeiger. Das Protokoll in Abb. 9 zeigt, dass dies keinen nennenswerten Vorteil brachte. Damit konnte zwar nicht geklärt werden, warum die Auswertung so lang dauert, aber es zeigte sich, dass die Vectoren von Boost so gut wie keinen Geschwindigkeitsverlust mit sich bringen. Dass die Datenverarbeitung in Abb. 9 vier mal so lang dauert, wie der Empfang, erklärt sich durch die erhöhte Datenrate.

32 4 Untersuchung der Datenstrecke 27 Abbildung 9: SpeedTest mir Arrays statt Vectors Es konnte schließlich nicht geklärt werden, warum die Auswertung um ein Vielfaches länger dauert als der Empfang. Es gab zwar verschiedene Ansätze (Kernel- vs. UserMode, Anzahl Operationen, XferData() in ASM, Treiber in RAM und Vergleichsarray in Auslagerungsdatei), diese haben sich aber entweder als unplausibel herausgestellt oder konnten nicht überprüft werden. Schließlich wurde die Datenverarbeitung auf ein Minimum reduziert, um zu sehen was maximal möglich ist. Listing 15 zeigt den relevanten Ausschnitt. Da dieser Code zu den selben Ergebnissen führte, wurden die Nachforschungen eingestellt. Es war abzusehen, dass die Datenauswertung der NepteronSoftware wesentlich umfangreicher würde, und die weiteren Bemühungen wurden auf eine Lösung gerichtet. Listing 15: Minimalistischer Vergleich von empfangenen und erwarteten Daten Letztlich wurde also mindestens die vierfache Rechenleistung benötigt. Da neben der Datenverarbeitung auch die Benutzeroberfläche, Windows und eine Datenbank auf dem Rechner laufen sollte, erschien eine erhöhte Leistung um den Faktor sechs bis acht realistischer. Der eingesetzte Prozessor hatte eine Taktrate von 2,6 GHz. Da es noch keine Prozessoren mit 20,8 GHz Taktrate gab, war Parallelisierung das Mittel der Wahl. Es wurde ein Server im PC Gehäuse mit zwei Intel Xeon Prozessoren angeschafft, womit acht Kerne verfügbar wurden. Um die Labormittel zu schonen, wurden Modelle mit je 2 GHz Taktfrequenz gewählt. Das Mainboard kann allerdings mit den wesentlich leistungsstärkeren Intel Core i7 Prozessoren nachgerüstet werden. Die vorhandenen Prozessoren sind rein rechnerisch in etwa sechs Mal so schnell wie ein einzelner 2,6 GHz Kern und sollten daher ausreichen. Um diese vielen Kerne nutzen zu können, wurde die C++Erweiterung Threading Building Blocks von Intel angeschafft, auf die in Kapitel 5 genauer eingegangen wird.

33 4 Untersuchung der Datenstrecke Ergebnisse und Anforderungen an die Benutzer Alles in Allem erschien das Vorhaben realisierbar und plausibel. Bei den Anforderungen musste ein Kompromiss geschlossen werden. Die Anforderung Die Übertragung muss in Echtzeit erfolgen wurde in Die Übertragung soll in Echtzeit erfolgen geändert. Dieser Kompromiss ist allerdings eher akademischer Natur, denn so lang die unten stehenden Anweisungen eingehalten werden, stehen dem Nepteron-Prozessor 100% der USB-Bandbreite zur Verfügung, und die angestrebte Datenrate wird problemlos erreicht. Die Übertragung der Daten in Echtzeit kann also trotz BulkTransfer gewährleistet werden. Diese Transferart garantiert gleichzeitig eine korrekte Übertragung der Daten, die in verschiedenen Tests auch nachgewiesen werden konnte. Um die ankommenden Daten schnell genug zu verarbeiten wurde ein 8-Kern Computer und eine passende C++Erweiterung angeschafft. Damit waren alle Vorarbeiten erledigt, und es konnte mit der eigentlichen Aufgabe begonnen werden. Um den Arbeitsaufwand und die Kosten des Projekts im Rahmen zu halten, wurden einige Anforderungen an die zukünftigen Benutzer gestellt: Der Nepteron-Prozessor sollte das einzige breitbandige Gerät am USB sein. Wird während der Simulation beispielsweise eine USB-Festplatte oder ein USB-Mikrofon eingesetzt, ist mit enormen Verzögerungen zu rechnen. NepteronSoftware benötigt Windows XP 32 Bit als Betriebssystem und mindestens 6 Prozessor-Kerne zu je 2 GHz. Intel Threading Building Blocks muss installiert sein. Während NepteronSoftware ausgeführt wird, darf kein anderes rechenintensives Programm ausgeführt werden. Überhaupt sollte außer Windows und den zum Betrieb von NepteronSoftware nötigen Programmen und Treibern nichts installiert werden. Der Einfluss von fremden Programmen kann nicht vorhergesagt werden.

34 5 Parallele Datenverarbeitung mit PCs 5 29 Parallele Datenverarbeitung mit PCs Wie im vorherigen Kapitel beschrieben wurde, musste das Programm NepteronSoftware auf mehrere Prozessoren verteilt werden. Dieses Kapitel beschreibt einige Grundlagen zur parallelen Datenverarbeitung, etwa das Amdahlsche Gesetz, Petri-Netze und Möglichkeiten zur Synchronisierung. Schließlich wird ein Einblick in die Threading Building Blocks Bibliothek (TBB) von Intel gegeben, die den Sprachschatz von C++ um parallele Ausdrücke erweitert. 5.1 Grundlagen der parallelen Datenverarbeitung Ein Programm, in dem verschiedene Programmpfade gleichzeitig ausgeführt werden, verhält sich anders, als ein Programm, welches nur sequenzielle Abläufe besitzt. Es ist gutes Handwerk zuerst eine sequenzielle Version zu implementieren und zu testen. Anschließend wird diese parallelisiert und erneut getestet. Dabei ist zu beachten, dass nicht jeder Programmteil parallel ausgeführt werden kann. Auf diesen Umstand geht das Amdahlsche Gesetz genauer ein. Des Weiteren muss besonderes Augenmerk auf den Datenfluss und die Datenspeicherung gelegt werden. Parallele Datenverarbeitung liegt übrigens auch bei Mikrocontrollern vor, die ein Programm ausführen, das durch Interrupts unterbrochen wird. Diese Programme sind üblicherweise ohne Weiteres beherrschbar, meistens sind sie sogar deterministisch. Dennoch gelten die folgenden Konzepte auch für diese Programme Synchronisation Zugriff auf gemeinsame Ressourcen und Daten Beim verteilten Rechnen entstehen Probleme, die bei sequenzieller Datenverarbeitung nicht existieren. Sie treten auf, sobald sich zwei Prozessoren Daten oder Ressourcen (zum Beispiel einen Drucker) teilen müssen. Moderne Computer sind mit ihren Betriebssystemen, Treibern, zahlreichen Schnittstellen, Interruptquellen und unzähligen Programmen, die im Hintergrund laufen schon seit Jahren so kompliziert geworden, dass sich nicht exakt voraussagen lässt, was ein Prozessor wann tut. Wenn ein solches System nun über mehrere Prozessoren verfügt und ein Programm an diese Prozessoren Arbeitspakete verteilt, dann kann der Bearbeitungszeitpunkt einzelner Pakete nicht vorhergesagt werden. Dieser Umstand wird problematisch, wenn zum Beispiel ein Aufgabenpaket die Ergebnisse eines anderen benötigt. Man kann sich nicht darauf verlassen, dass die Daten zur rechten Zeit an der richtigen Adresse im Speicher liegen. Vielmehr muss davon ausgegangen werden, dass dies nicht der Fall ist. Hier spricht man davon, dass die Prozesse synchronisiert werden müssen. Das heißt sie müssen miteinander kommunizieren, um z. B. zu verhindern das ungültige Daten eingelesen werden. Ebenso müssen die Zugriffe auf Ressourcen synchronisiert werden. Wenn beispielsweise zwei Prozessoren gleichzeitig auf eine ungeschützte Konsole zugreifen, wird der entstehende Text eine Mischung aus den beiden Aufträgen sein. Dieses Verhalten lässt sich gut in Abb. 10 beobachten. Ebenso muss der Arbeitsspeicher verwaltet werden. Hier muss dafür gesorgt werden, dass ein Prozess nicht die Ergebnisse eines andern überschreibt oder in Speicherbereiche schreibt, die gerade gelesen werden. Ein Verfahren zur Synchronisierung ist der sogenannte Mutex. Dieses Kunstwort ist aus dem englischen Begriff mutual exclusion entstanden und bedeutet gegenseitiger/einvernehmlicher Ausschluss. Im einfachsten Fall handelt es sich hierbei um ein Bit mit dem Sinn Ressource ist in Abbildung 10: Drei Prozesse konkurrieren um die Konsole, einer hat Glück

35 5 Parallele Datenverarbeitung mit PCs 30 Benutzung. Dieses wird gesetzt, kurz bevor eine Ressource in Anspruch genommen wird und gelöscht, nachdem sie nicht mehr verwendet wird. Wenn ein Prozess die kritische Ressource in Anspruch nehmen möchte fragt er vorher dieses Bit ab und verzichtet u. U. auf ihre Benutzung. Hierbei entstehen zweierlei Probleme: Erstens ist dieses Bit wiederum eine geteilte Ressource. Es kann passieren, dass es von zwei Prozessoren gleichzeitig beschrieben wird, oder, dass ein Prozessor das Bit abfragt, während der andere es gerade beschreibt. Die Ergebnisse solcher Vorgänge sind unbestimmt und müssen daher unbedingt vermieden werden. Zweitens können sogenannte Verklemmungen entstehen. Angenommen Prozess A hat die Ressource reserviert und wartet nun auf das Ergebnis von Prozess B, wobei dieser die kritische Ressource benötigt, um seine Aufgabe zu erfüllen. Es ist ein sogenanntes Deadlock entstanden. Beide Prozesse warten bis in alle Ewigkeit und das Programm wird nie zu Ende geführt. Aus diesen Gründen sind Mutexe heute nicht mehr einzelne Bits, sondern eher eigene Datentypen. Mutexe können allerdings nicht alle Probleme lösen. Beispielsweise muss auch darauf geachtet werden, wie fair eine Ressource verteilt wird. Zu diesem Thema lohnt sich eine Suche nach dem Stichwort Philosophenproblem. Die Informatik hat zu all diesen Problemen zahlreiche Lösungen entwickelt, wie zum Beispiel Semaphoren und Monitore. Allen synchronen Lösungen ist gemein, dass nicht berechtigte Teilnehmer blockiert werden und warten müssen, bis die Ressource frei ist. Dies führt zu Verzögerungen im Programmablauf und sorgt dafür, dass Prozessoren untätig warten oder einen Kontextwechsel durchführen, um eine andere Aufgabe zu bearbeiten. Eine Alternative ist es, auf Synchronisierung zu verzichten und die Datenverarbeitung asynchron ablaufen zu lassen. Hierbei muss trotzdem gewährleistet werden, dass Ressourcen nicht doppelt verwendet werden und, dass der Speicher konsistent bleibt. Das ist nur dann möglich, wenn es Programmteile gibt, die keine Ergebnisse von anderen Teilen benötigen und eine geeignete Speicherverwaltung für einen konfliktfreien Zugriff sorgt. Des Weiteren muss es Abbruchbedingungen geben, die zur passenden Zeit dafür sorgen, dass alle Teilnehmer sich ordentlich selbst beenden. Dieses Wissen war bereits zu Beginn der Diplomarbeit vorhanden und deshalb wurde von Anfang an versucht, ein Programm zu entwickeln, das ohne blockierende Synchronisation auskommt. Durch den Einsatz von TBB Datencontainern ist dies schließlich gelungen. Wie NepteronSoftware die Probleme der Synchronisation löst wird in den Kapiteln 5. und 6 erörtert Das Amdahlsche Gesetz Wenn ein Programm auf vier statt zwei Prozessoren ausgeführt wird, kann es zwar schneller werden, aber nicht doppelt so schnell. [21] beschreibt das Amdahlsche Gesetzt, das einen Aspekt dieses Verhaltens ausdrückt. Es lautet: s p= f f f p wobei: (1) sp Geschwindigkeitszuwachs, als Vielfaches der sequentiellen Verarbeitungsdauer ( speedup ) f Anteil der nicht parallelisierbaren Berechnungen, 0 f 1 p Anzahl der Prozessoren Es sagt aus, dass der maximale Geschwindigkeitszuwachs davon abhängt, wie viel Prozent eines Programms sich parallelisieren lassen. Wenn beispielsweise 20 % sequentiell ausgeführt werden

36 5 Parallele Datenverarbeitung mit PCs 31 müssen, kann durch zusätzliche Prozessoren das Programm höchstens fünf mal so schnell ablaufen. Wenn diese Grenze erreicht ist, sind weitere Prozessoren nutzlos oder sogar hinderlich. Das Amdahlsche Gesetzt gibt allerdings nur die Obergrenze an, denn Parallelisierung bringt Kosten mit sich. Unter Anderem müssen Prozessoren miteinander kommunizieren, sie müssen auf geteilte Ressourcen warten und außerdem wird Code benötigt, der die Arbeit auf die Prozessoren verteilt. Dieser Code muss natürlich von den Prozessoren selbst ausgeführt werden. All dies ist in sequentiellen Programmen nicht nötig und reduziert die hinzu gewonnene Rechenleistung. Dass der Geschwindigkeitszuwachs mit steigender Prozessorzahl abnimmt, wird z. B. in [22] sehr gut gezeigt Petri-Netze Um nebenläufige Programme übersichtlich darzustellen, sind andere Mittel nötig, als für sequentielle Programme wurden von C.A. Petri die Petri-Netze eingeführt[23]. Mit ihnen können nebenläufige, bedingte Abläufe in einem getakteten Netz dargestellt werden. Petri-Netze bestehen aus drei Grundbausteinen: Stellen, Transitionen und Marken. Stellen werden als Kreise und Transitionen als Balken dargestellt. Marken sind Punkte oder Zahlen. Jede Transition ist mit mindestens einer Stelle verbunden. Marken liegen auf Stellen und werden von Transitionen verarbeitet. Marken sind eine Art Arbeitseinheit, Stellen kann man mit den Zuständen von Zustandsautomaten vergleichen und Transitionen führen Arbeiten aus. Wenn eine Transition aktiv wird, sagt man, dass sie schaltet. Stellen haben Kapazitäten, das heißt sie können nur eine bestimmte Anzahl Marken aufnehmen. Transitionen verarbeiten in jedem Schritt eine gewisse Menge dieser Marken. Die Kapazitäten werden als Zahl neben die Stellen und Transitionen geschrieben. Wenn die Kapazität 1 ist, wird sie nicht notiert. Transitionen benötigen eine gewisse Anzahl Marken, um zu schalten, von jeder Eingangsstelle aber mindestens eine. Dabei verschieben sie die Marken nicht, sondern löschen ankommende Marken und erzeugen in den Folgestellen neue. Dieser Vorgang findet nur statt, wenn die Folgestellen genügend freie Plätze haben. Üblicherweise ist die Kapazitäten jeder Stelle 1 und eine Transition benötigt von jeder Vorstelle exakt eine Marke um zu schalten und erzeugt in jeder Folgestelle auch exakt eine neue Marke. Im einfachsten Fall folgt auf eine Stelle eine Transition und auf diese wieder eine Stelle. Abb. 11 zeigt einen solchen Zusammenhang. Hier könnte beispielsweise eine Autoreparatur dargestellt sein. Stelle A hieße kaputtes Auto steht vor der Werkstatt, die Transition repariere Auto und Stelle B repariertes Auto steht hinter der Werkstatt. Die Kapazitäten der Stellen und der Markenbedarf der Transition sind hier nicht eingetragen. Das heißt, dass Stelle A und B jeweils Platz für eine Marke haben, dass die Transition eine Marke zum Schalten benötigt und, dass sie dabei eine Marke in B erzeugt. Abbildung 11: Folgeprozess mit einer Transition und zwei Stellen

37 5 Parallele Datenverarbeitung mit PCs 32 Die in Abbildung 11 dargestellte Struktur wird Folgeprozess genannt. Laut [23] gibt es fünf Grundstrukturen namens Folgeprozess, Fusionsprozess, Spaltprozess, Zeitprozess, Synchronisationsprozess und zwei logische Verknüpfungen, die Und und Oder genannt werden. Von den Grundstrukturen werden nur die vorgestellt, die für diese Arbeit relevant sind. Abbildung 12 zeigt einen Fusionsprozess. Hierbei benötigt die Transition aus jeder Vorstelle eine Marke um zu schalten und erzeugt anschließend eine neue in C. Allerdings schaltet sie nur, wenn in C Platz für die neue Marke ist. Ein Beispiel für diesen Prozess ist die Legierung von Kupfer und Zinn zu Bronze. Der Fusionsprozess wird auch als Und-Verknüpfung bezeichnet, weil alle Eingangsstellen mit Marken besetzt sein müssen, damit geschaltet wird. Abbildung 12: Fusionsprozess Im Spaltprozess, den Abb. 13 zeigt, findet der umgekehrte Vorgang statt. Die Transition benötigt eine Marke in A um zu schalten und erzeugt dadurch jeweils eine Marke in B und C. Auch hier wird nur geschaltet, wenn in den Folgestellen genug Platz für die Marken ist. Abbildung 13: Spaltprozess Abb. 14 zeigt die Oder-Verknüpfung. Sie wird so genannt, weil entweder Transition T1 oder Transition T2 schaltet. Welche dies tut ist grundsätzlich nicht bestimmt und zufällig. Dadurch können Petri-Netze einen nicht-deterministischen Charakter erhalten. Abbildung 14: Oder-Verknüpfung

38 5 Parallele Datenverarbeitung mit PCs 33 Die Abbildungen 15 bis 18 sollen das gesamte Konzept verdeutlichen. Wie bereits erwähnt laufen Petri-Netze getaktet ab. Im ersten Takt können hier die Transitionen T1 und T4 schalten. Dadurch erhalten die Stellen B, C und D jeweils eine Marke. Transition T5 kann im zweiten Takt noch nicht schalten, weil E keine Marke besitzt. Die Abbildungen 17 und 18 zu analysieren und Takt 5 zu zeichnen, wird dem geneigten Leser überlassen. Takt 6 entspricht dann übrigens Takt 3. Abbildung 15: Komplexeres Petri-Netz Takt 1 Abbildung 16: Komplexeres Petri-Netz Takt 2 Abbildung 17: Komplexeres Petri-Netz Takt 3 Abbildung 18: Komplexeres Petri-Netz Takt 4

39 5 Parallele Datenverarbeitung mit PCs Intel Threading Building Blocks (TBB) In diesem Kapitel soll die TBB Bibliothek vorgestellt werden und ihr Einsatz anhand parallel_for und Tasks demonstriert werden. Auf den in dieser Arbeit verwendeten TBB Container wurde bereits in Kapitel eingegangen. Eine vollständige Dokumentation dieser Bibliothek findet sich unter [10] und unter [24] sind weitere Hilfsmittel verfügbar Einführung TBB TBB erweitert C++ um Ausdrücke zum parallelen Rechnen. Sie unterscheidet sich von anderen Parallel-Bibliotheken vor allem dadurch, dass der Anwender sich keine Gedanken machen muss, wie Parallelität verwaltet wird. Das heißt der Programmierer braucht sich nicht mehr darum kümmern, wie sein Programm auf welche Prozessoren verteilt wird, sondern kann sich ganz auf die Lösung des eigentlichen Problems konzentrieren. Dazu stellt TBB verschiedene Scheduler zur Verfügung. Ein TBB-Scheduler verwaltet die Prozessoren des Zielsystems und sorgt für eine möglichst effektive Auslastung. Es ist möglich Einfluss auf sein Verhalten zu nehmen oder Alternativen zu verwenden. Im Allgemeinen genügt jedoch eine Initialisierung des StandardSchedulers. Wie bereits erwähnt ist im Sprachumfang von C++ keine Nebenläufigkeit vorgesehen. Um sie zu realisieren benötigt TBB Mechanismen, mit dem es Code zur Laufzeit erzeugen kann. Das geschieht mit Hilfe spezieller Klassen. Sie enthalten den Arbeitercode des Anwendungsprogrammierers und werden nach Bedarf vom Scheduler über Konstruktoren instanziiert. Wie diese Objekte erstellt werden, zeigen die folgenden Kapitel parallel_for Listing 16: Header einer Klasse, die Arbeitercode für parallel_for enthält Die einzelnen Iterationen von for-schleifen sind häufig voneinander unabhängig. Daher ist es möglich, zu Beginn der Bearbeitung, die Schleife als sequentiellen Code zu formulieren. Man sagt Die Schleife wird ausgerollt. Der resultierende Code ist dann nur noch von den Parametern der Schleife, also Start-, Stoppbedingungen und Schrittweite abhängig. Dadurch entsteht beispielsweise eine Reihe von Additionen. Diese können nun auf verschiedene Prozessoren verteilt und nebeneinander ausgeführt werden. Allerdings ist zu beachten, dass die Zuteilung eines Arbeitspaketes einen gewissen Aufwand mit sich bringt. Unter anderem muss der Scheduler entscheiden, welcher Prozessor das Paket bekommt, der Prozessor muss das Paket in seine Register laden u.v.m. Daher führt TBB die Größe grainsize ein, die etwa mit Arbeitspaktetgröße übersetzt werden kann. Sie bestimmt, aus wie vielen Schleifen-Durchläufe ein Arbeitspaket besteht. Wenn beispielsweise eine for-schleife nach 1000 Durchläufen abgearbeitet ist und die grainsize 500

40 5 Parallele Datenverarbeitung mit PCs 35 beträgt, dann wird der Scheduler die Schleife auf zwei Prozessoren verteilen, auch wenn mehr zur Verfügung stehen. Laut Intel sollte ein Arbeitspaket zwischen und AssemblerBefehlen groß sein. Im Einzelfall muss sie allerdings durch Experimente optimiert werden, wobei der optimale Bereich meistens sehr groß ist. Listing 16 zeigt den Header einer Klasse, die für den Einsatz in einer parallel_for Schleife entworfen wurde. Dies ist nötig, da der Arbeitercode in einem überladenen Operator () stehen muss. Es muss also für jede parallel_for Schleife eine eigene Klasse, mit eigens überladenem Operator () geschrieben werden. Kunden müssen zum Aufruf der Schleife die Ober- und Untergrenze, grainsize und ein Objekt dieser Klasse übergeben. Einen solchen Aufruf zeigt Listing 17. Der Iterationsbereich wird dabei als Template-Objekt vom Typ blocked_range übergeben und hat die Reihenfolge (Obergrenze, Untergrenze, grainsize). Als zweites muss ein Objekt der Klasse übergeben werden, die den Arbeitercode enthält. In diesem Fall also CParallelDataProcessing. Wie in Listing 16 zu sehen ist, benötigt ihr Konstruktor einen Zeiger auf ein Objekt vom Typ CProcessHLpkt. Dieses Objekt führt die eigentlichen Berechnungen aus. Der gesamte Code aus der Methode CProcessHLpkt::Go() könnte auch an Stelle der Zeile 20 in Listing 16 stehen. Da der gesamte Arbeitercode im Header steht und der Konstruktor nur aus einer Initialisierungsliste besteht kann die Implementierungsdatei leer bleiben. Listing 17: Aufruf einer parallel_for Schleife mit dem Arbeitercode aus Listing Tasks Beim Einsatz von parallel_for muss zu Beginn bekannt sein, wie oft der Arbeitercode ausgeführt werden soll. Ist dies nicht der Fall können verschiedene TBB Konzepte eingesetzt werden. Hier fiel die Wahl auf Tasks. Sie verhalten sich ähnlich wie Threads, allerdings können sie wesentlich einfacher erzeugt werden und werden automatisch vom TBB Scheduler verwaltet. Im Prinzip sind Tasks Funktionen, die unabhängig vom Hauptprogramm auf einem anderen Prozessor ausgeführt werden. Sie verwenden den selben, virtuellen Speicherbereich wie ihr Erzeuger und können daher mit ihm und ihren Geschwister-Tasks problemlos kommunizieren. Wenn mehr Prozessoren als Tasks vorhanden sind, bearbeitet jeder Prozessor einen Task. Gibt es dagegen mehr Tasks als Prozessoren sorgt der TBB-Scheduler für ein gleichmäßiges abarbeiten. Tasks sind das zentrale Konzept von TBB und können auf viele verschiedene Arten eingesetzt werden. Beispielsweise kann Pipeline-artiges Verhalten realisiert werden ein Task gibt seine Ergebnisse an einen Nachfolger weiter, Tasks können recycelt werden, selbst neue Tasks erzeugen, rekursiv Kopien von sich starten u.v.m.. [10] bietet umfangreiche Informationen zu diesen Themen. Um Code als eigenständigen Task laufen zu lassen, muss ähnlich wie für parallel_for eine spezielle Klasse erstellt werden. Sie muss eine Methode namens execute() besitzen, die den Arbeitercode enthält und einen Zeiger auf Task-Objekte zurück gibt. Falls kein Pipeline-Verhalten gewünscht wird, kann NULL zurückgegeben werden. Des Weiteren muss diese Klasse von task erben. Listing 18 zeigt den gekürzten Header einer solchen Klasse.

41 5 Parallele Datenverarbeitung mit PCs 36 Listing 18: gekürzter Header einer Task-Klasse In Zeile 1 wird der Name der Klasse festgelegt und anschließend mittels : bekannt gegeben, dass sie von tbb::task erben soll. Im public Bereich wird die Methode execute() deklariert, die einen Zeiger auf ein Objekt vom Typ tbb::task zurück gibt. Listing 19 zeigt die rudimentäre Implementierung dieser Methode. Listing 19: gekürzte Implementierung eines Tasks Nachdem in Zeile 1 bekannt gegeben wurde, um welche Methode es sich handelt also was sie zurück gibt, zu welcher Klasse sie gehört, wie sie heißt und was sie als Parameter entgegen nimmt kann direkt mit dem Arbeitercode begonnen werden. Dieser Code wird ausgeführt, sobald ein Task dieser Art gestartet wird. Der Rückgabewert ist für das Pipelining reserviert. Sollen Kunden Ergebnisse erhalten, muss dies über Referenzen geschehen. Dabei sind die üblichen Synchronisationsprobleme zu beachten. Um einen Task zu starten, muss mit einem überladenen Operator new ein Objekt der Task-Klasse erzeugt werden und als Referenz gespeichert werden. Die Zeilen 1 bis 3 in Listing 20 zeigen, wie dies geschieht. Bevor die Tasks gestartet werden, muss wie in Zeile 4 die globale Funktion set_ref_count() aufgerufen werden. Ihr muss die Anzahl der zu erzeugenden Tasks plus 1 übergeben werden. Anschließend werden in den Zeilen 5 bis 7 die drei Tasks gestartet. Task a startet, sobald Zeile 5 zurückkehrt. Das Hauptprogramm hält am Ende von Zeile 7 an, bis alle Tasks zurückgekehrt sind. Listing 20: Erzeugen und Starten von drei Tasks

42 6 NepteronSoftware 6 37 NepteronSoftware Dieses Kapitel dokumentiert die Implementierung der NepteronSoftware. Dazu wird im Abschnitt 6.1 eine Übersicht des Programmablaufes geboten Zum Verständnis des Programms müssen die Betriebsarten und Pakettypen des Nepteron-Prozessors bekannt sein. Dieses Wissen soll Abschnitt 6.2 vermitteln. Kapitel 6.3 gibt einen Überblick über die verwendete Software-Architektur, führt die ihre Grundbegriffe ein und zeigt, wie die einzelnen Teile für NepteronSoftware gestaltet wurden. Nachdem auf diese Weise die zu Grunde liegenden Konzepte und Pläne dargelegt wurden, sollen Details der Implementierung besprochen werden. Zur Weiterentwicklung dieser Software, ist ein Studium des vorliegenden Quellcodes unumgänglich und überhaupt dient dieses Kapitel vor allem dazu, dies zu unterstützen. Das Kapitel 6.4 geht daher auf Teile des Quellcodes ein und erläutern komplizierte Strukturen. Es wird nach dem Motto Soviel wie nötig, so wenig wie möglich verfahren, was eventuell dazu führt, dass einige Kapitel abgehackt erscheinen. Um diese Lücken zu füllen, sei an erster Stelle auf den Quellcode verwiesen. Falls ein Studium dessen nicht weiter hilft, steht der Autor dieser Arbeit gern für Nachfragen zur Verfügung. 6.1 Interner Datenfluss und Flussdiagramm der NepteronSoftware Abbildung 19: Datenfluss während der Simulation Zu Beginn einer Simulation muss der Nepteron-Prozessor initialisiert werden. Es sind verschiedene Konstanten-, Variablen- und Codespeichern in diesem Prozessor implementiert, die alle mit Startwerten versehen werden. Sie werden per USB übertragen und stammen größtenteils aus Textdateien. Nachdem die Simulation durch ein Startpaket gestartet wurde, produziert der Nepteron-Prozessor einen konstanten Datenstrom, der von dem Programm entgegen genommen wird. Es zerteilt ihn in Arbeitspakete und legt diese in einem FIFO-Speicher ab. Von dort entnehmen mehrere nebenläufige Tasks einzelne Pakete und sortieren die Daten in das Datenmodell. Abbildung 19 zeigt ein Petri-Netz, das den Datenfluss während der Simulation darstellt. Dabei werden laufend Marken von der Datenquelle erzeugt, von den einzelnen Sortier-Tasks abgeholt, dekodiert und die Nutzdaten im Modell abgelegt. Jede Transition entnimmt eine Marke aus dem FIFO-Speicher und

43 6 NepteronSoftware 38 erzeugt eine neue im Datenmodell. Diese Marken repräsentieren die sogenannten High-LevelPakete. In Abb. 20 wird dieser Ablauf als Flussdiagramm dargestellt. Dabei ist zu beachten, dass NepteronSoftware mehrere Programmteile gleichzeitig bearbeiten kann. Abbildung 20: Flussdiagramm NepteronSoftware

44 6 NepteronSoftware Betriebsarten, Pakettypen und Paketaufbau Nach der Initialisierung kann der Nepteron-Prozessors in einer von zwei Betriebsarten laufen, im Realtime- oder Snapshot-Modus. Im Snapshot-Modus übertragen sogenannte Snapshot-Pakete alle Nutzdaten. Das heißt, der Zustand des gesamten Netzes wird lückenlos übertragen, also jedes Neuronenpotential eines jeden Zeitpunkts. Um die 400 Potentiale eines Zeitpunkts zu übertragen sind vier 512 Byte große Pakete nötig. Da die Datenrate nicht ausreicht, um sie in Echtzeit zu übertragen, läuft der NepteronProzessor in dieser Betriebsart mit halber Geschwindigkeit. Im Realtime-Modus werden alle drei Nutzdaten-Pakettypen übertragen (Realtime, Snapshot Firestatus). Der Prozessor arbeitet mit voller Geschwindigkeit und sendet die Potentiale ausgesuchter Neuronen, lückenlos. Welche Neuronen in diesem Modus beobachtet werden, wird vom Benutzer, mit Hilfe der NeuronTransferList, entschieden. Von den aktuell 400 Neuronen können maximal 124 in Echtzeit auf dieser Liste stehen. Pakete, die diese Daten übertragen, heißen Realtime-Pakete. Um im Realtime-Modus dennoch eine Übersicht des gesamten Netzes zu bieten, werden FirestatusPakete übertragen. Sie enthalten die Information, ob ein Neuron gefeuert hat, oder nicht. Hierbei bleibt noch etwas Bandbreite übrig. Sie wird verwendet, um den Snapshot eines jeden vierten Zeitpunkts zu übertragen. Neben den Nutzdaten-Pakettypen, gibt noch zwei weitere Obergruppen: Pakete zur Initialisierung des Nepteron-Prozessors und solche, die zur Steuerung des Nepteron-Prozessors verwendet werden. Das folgende Kapitel gibt eine Beschreibung des Paketaufbaus und im gedruckten Anhang listet Tabelle 2 alle eingesetzten Pakettypen auf. Abbildung 21 zeigt den allgemeinen Aufbau der Nepteron-USB-Pakete. Sie sind immer 512 Byte groß, die Nummerierung der Bytes beginnt bei Null und der Header besteht immer aus 16 Bytes. Die ersten und die letzten zwei Bytes des Headers werden immer mit 0xAA gefüllt. Byte 2 ist geteilt und überträgt im oberen Halbbyte die Protokoll Nummer und im unteren die Versionsnummer. Das nächste Byte gibt den Pakettyp an, es folgen zwei Byte, die die Länge der Nutzdaten angeben und die Angabe, wie viele, zusammengehörende Pakte übertragen werden. Zur Initialisierung des Codespeichers von ADD_SUB_A sind beispielsweise fünf Pakete nötig. Darauf folgt die laufende Paketnummer, die z. B. angibt, ob es sich um das erste oder letzte Paket für ADD_SUB_A handelt. Die Bedeutung der Bytes C1 bis C4, B1, B2 und die Organisation der Nutzdaten hängen vom Pakettyp ab. Da die Pakete immer 512 Byte groß sein müssen, aber nicht immer so viele Nutzdaten benötigt werden, werden die ungenutzten Bytes mit 0xFF gefüllt.

45 6 NepteronSoftware Abbildung 21: Aufbau der Nepteron-USB-Pakete 40

46 6 NepteronSoftware MVC-D Architektur Dieses Kapitel beschreibt die eingesetzte Software-Architektur und wie sie realisiert wurde. Dabei werden zuerst die Grundbegriffe erklärt. Danach werden die Besonderheiten dieser speziellen Umsetzung erläutert, und anschließend wird gesondert auf die einzelnen Teile eingegangen Grundbegriffe Bei der MVC Architektur wird das zu schreibende Programm in drei Teile unterteilt: Modell, View und Controller. Letzterer führt die eigentliche Rechenarbeit durch. Das heißt der Controller verarbeitet Eingaben und liefert die gewünschten Daten. Diese werden im Modell gespeichert und von der View-Schicht dem Benutzer zugänglich gemacht. Der Vorteil dieser Struktur ist, dass alle drei Teile voneinander unabhängig entwickelt und getestet werden können. Dafür ist besonderer Augenmerk auf die Schnittstellen zu legen. Durch sie wird es möglich, einzelne Teile komplett auszutauschen. Beispielsweise könnte ein View für Windows und eines für Linux geschrieben werden. Sofern beide Versionen die Schnittstellen bedienen, können sie beliebig ausgetauscht werden[25]. Da der Datenaustausch mit dem Nepteron-Prozessor in diesem Projekt eine zentrale Rolle spielt, wurde das MVC Konzept um eine Datenquelle/-senke erweitert. Dadurch wurde es vor allem möglich das Programm unabhängig von der USB-Verbindung zu entwickeln. Abb. 22 zeigt die Zusammenhänge der einzelnen Teile. Vierecke stellen die Schnittstellen dar, Pfeile repräsentieren Daten- und Befehlsflüsse und Kreise stehen für die eigentlichen MVC-D Teile. Abbildung 22: MVC-D Architektur mit Schnittstellen

47 6 NepteronSoftware Modell Der Nepteron-Prozessor arbeitet digital, das heißt er produziert die Simulationsdaten für einzelne, diskrete Zeitpunkte. Diese liegen immer 100 µs auseinander, es fallen also Datensätze pro Sekunde an. Für jeden Simulationszeitpunkt wird ein Objekt der Klasse CCluster erzeugt. Ein Cluster enthält alle Daten, die nötig sind, um einen solchen Zeitpunkt zu speichern. Dazu besitzt er unter anderem ein 400 Floats großes Array, das die Potentiale der 400 Neuronen speichert. Ein weiteres Attribut ist der Zeitstempel, der den Cluster einem Zeitpunkt zuordnet und ihn identifiziert. Cluster werden im RAM abgelegt und sollen, ähnlich wie Karteikarten, durchgeblättert werden können. Das heißt, es muss eine Anlaufstelle im Programm geben, der eine Systemzeit übergeben werden kann und die darauf hin einen Zeiger auf den passenden Cluster liefert. Ein Objekt namens ClusterMaster übernimmt diese Verwaltung. Er verfügt über einen Datencontainer vom Typ Map, der die Zuordnung ( Systemzeitpunkt Zeiger auf Cluster ) übernimmt. Das heißt die Map nimmt einen Zeitpunkt entgegen und liefert einen Zeiger zurück. Des Weiteren stellt der ClusterMaster eine Fabrikfunktion bereit, bei der neue Cluster angefordert werden können. Sie nimmt die gewünschte Anzahl entgegen und erzeugt entsprechend viele Cluster. Diese Cluster sind dann bereits mit den korrekten Zeitstempeln und Initialwerten versehen. Sobald sie erzeugt sind, kann der Kunde, über die erwähnte Map, auf sie zugreifen. Zusammenfassend lässt sich sagen, dass die Simulationsdaten in Clustern gespeichert werden. Sie liegen lose verteilt im Arbeitsspeicher. Ihre wichtigstes Attribut ist ein Array, der die Neuronenpotentiale enthält. Neue Cluster werden zur Laufzeit vom ClusterMaster erzeugt und ihre Adressen über shared_ptr verwaltet. Über diesen hat der Kunde vollen Zugriff, auf den entsprechenden Datensatz. Abb. 23 soll das Datenmodell etwas verbildlichen. Abbildung 23: Simulationszeitpunkte werden Karteikarten-artig gespeichert

48 6 NepteronSoftware 43 Tatsächlich sind die Arrays nicht zwei- sondern eindimensional, und ein Cluster enthält, wie bereits erwähnt, noch etwas mehr als nur die Neuronenpotentiale. Aber dieses Bild soll auch nur einen Eindruck liefern, wie Data hier aufgebaut wurde View Während dieser Arbeit wurde die Windows-Konsole als Schnittstelle zum Benutzer verwendet. NepteronSoftware gibt auf diesem Weg Auskunft, über seine aktuelle Tätigkeit, lässt sich über Menüs steuern und gibt Ergebnisse aus. Abb. 24 zeigt eine dieser Ausgaben. Die Statusmeldungen werden von den jeweiligen Methoden abgegeben und die Menüs sind eigene Methoden. Das heißt, View ist hier noch nicht als isolierter Bereich implementiert, sondern über den Quellcode verteilt. Abbildung 24: NepteronSoftware kommuniziert per Konsole mit dem Benutzer

49 6 NepteronSoftware Controller Dem Controller fallen in diesem Programm zwei große Aufgabenbereiche zu: die Initialisierung des Nepteron-Prozessors und die Verarbeitung des ankommenden Datenstroms. Die Initialisierung läuft zu Beginn der Simulation ab Dazu liest sie die benötigten Daten aus verschiedenen Dateien, dekodiert sie und verpackt die Nutzdaten in Pakete. Diese werden mittels CyAPI per USB versendet. Nachdem alle Speicher des Nepteron-Prozessors initialisiert wurden, kann ein StartPaket versendet werden, das die Simulation startet und angibt, wie lang und in welchem Modus simuliert werden soll. Um den ankommenden Datenstrom schnell genug zu verarbeiten, werden mehrere Tasks erzeugt, die in Abb. 19 Sortier-Task genannt werden4. Diese Programmteile laufen parallel auf verschiedenen Prozessoren. Dabei entnehmen sie einem FIFO-Speicher einen Zeiger auf ein Arbeitspaket. Jedes dieser Pakete enthält 1024 USB-Pakete, zu je 512 Byte. Diese werden Stück für Stück dekodiert und die Nutzdaten in das Modell eingetragen. Anschließend werden die Originaldaten gelöscht und ein neuer Zeiger aus dem FIFO entnommen. Falls kein neuer Zeiger verfügbar ist, warten diese Tasks, bis eines von zwei Ereignissen in Kraft tritt: ein neuer Zeiger liegt im FIFO einer der anderen Tasks hat das Ready-Paket verarbeitet Das Ready-Paket wird vom Nepteron-Prozessor abgeschickt, sobald die Simulation beendet ist. Wenn es entschlüsselt wurde, bearbeiten die Sortier-Tasks ihr aktuelles Arbeitspaket zu Ende und beenden sich dann selbst. Sobald dies geschehen ist, wird ein Stopp-Paket zum Nepteron-Prozessor gesendet, um das Ende der Simulation zu quittieren. An dieser Stelle ist die Arbeit des Controllers beendet. View kann nun die Daten darstellen oder den Controller zu einer erneuten Initialisierung veranlassen, um eine neue Simulation zu starten Data Datenempfang und Datenverarbeitung müssen zur gleichen Zeit ablaufen. Daher muss dieser Teil stets als eigener Tasks implementiert werden. Das Konzept von Data hängt ganz von der verwendeten Quelle ab, daher sind nur die Schnittstellen definiert worden. Im Laufe dieser Arbeit wurde Data für Dateien und USB implementiert. Abb. 24 zeigt eine Version, die mit Textdateien als Datenquelle arbeitet. Welche Schnittstellen dabei eingehalten werden müssen, beschreiben die Kapitel und im Quellcode heißen sie TaskProcessHLpkt

50 6 NepteronSoftware Schnittstellen innerhalb von MVC-D Die Schnittstellen zwischen den vier Programmteilen sind in Abb. 22 dargestellt. Sie wurden größtenteils in der Konzeptionsphase festgelegt und sollen hier erläutert werden Data Controller Die Datenquelle produziert während der Simulation laufend neue Daten. Diese Schnittstelle sorgt dafür, dass Data sie ablegen und Controller sie abholen kann. Sie wird durch das Objekt RawDataStorage realisiert. Die kleinste Informationseinheit ist hier ein 512 Byte großes Paket, entspricht also exakt einem USB-Bulk-Paket. Diese Pakte müssen wie in Kapitel 6.2 beschrieben aufgebaut sein und heißen im Quellcode LLpkt ( Low Level Paket ). Data muss sie zu Bündeln von je 1024 Stück verpacken und im Arbeitsspeicher ablegen. Im Quellcode werden diese Pakete wiederum HLpkt ( High Level Paket ) genannt. Jede diese Arbeitseinheiten wird von jeweils einem intelligenten Zeiger verwaltet. Diese Zeiger sind wiederum in einem Container vom Typ concurrent_queue abgelegt. Dieser verhält sich wie ein FIFO-Speicher und kann ohne Weiteres von mehreren Lesern und Schreibern gleichzeitig bearbeitet werden. Es ist also gleichgültig, woher Data seine Informationen bezieht. Wichtig ist nur, dass sie als gültige Pakete im Sinne von Kap. 6.2 verpackt sind und, dass diese wiederum zu 1024er Gruppen zusammengefasst sind. Jede dieser Gruppen muss per shared_array verwaltet werden und diese Zeiger müssen, wie gesagt, in einer concurrent_queue zur Verfügung stehen. Damit dieses Interface von der Implementierung Datas unabhängig ist, liegt die concurrent_queue in einem Objekt namens RawDataStorage. Es enthält keinerlei aktiven Code, sondern beinhaltet nur den Datencontainer. Abbildung 25 soll diese Zusammenhänge noch einmal verdeutlichen. Abbildung 25: Organisation der ankommenden Daten

51 6 NepteronSoftware Controller Data Der Controller gibt auf zwei Wegen Informationen an Data weiter. Einerseits packt er zu sendende Daten5 als Low-Level-Pakte in einen Array und übergibt sie Data als Zeiger. Andererseits verfügt er über das Steuerbit namens b_forcetoquit. Es teilt TaskUSBReader mit, dass keine neuen Daten mehr zu erwarten sind. Die zu sendenden Pakete müssen wie in Kapitel 6.2 aufgebaut sein. Das erste zu sendende Byte steht dabei an Stelle 0 des Arrays. Wie viele Low-Level-Pakete auf einmal versendet werden, ist nicht festgelegt, sondern hängt von der Anwendung ab. An dieser Stelle wurde die strikte Trennung der Programmteile vernachlässigt. Es hätte ein Interface-Objekt angelegt werden müssen, das exakt dieses Verhalten aufweist und den Aufruf der eigentlichen Übertragungs-Methoden durchführt. Stattdessen ruft der Controller die benötigten Methoden selbst auf. Listing 21 zeigt einen solche Stelle. Diese Abschnitte sind, wie in Zeile , deutlich gekennzeichnet und müssen, bei einem Wechsel der Datenquelle/-senke, entsprechend angepasst oder, am besten, durch ein Interface-Objekt ersetzt werden. Es ist hilfreich alle versendeten Daten zusätzlich in Dateien zu speichern. Diese Dateien erleichtern Fehlersuchen aller Art enorm. Der Einfachheit halber werden Binärdateien verwendet, diese sind mit einem sogenannten Hex-Editor auch für Menschen lesbar und liegen unter C:\Nepteron\USBdump. Listing 21: Controller ruft Data-Methode direkt auf (Zeile 793), aus CNepteronInit::TiePakets() Controller Modell Die Hauptarbeit von NepteronSoftware ist es, ankommende USB-Pakete zu dekodieren und ihre Nutzdaten in das Modell zu sortieren. Hierbei ist nicht nur korrektes, sondern auch schnelles Arbeiten nötig. Deshalb wurde besonderer Augenmerk auf diese Schnittstelle gelegt. Um die nötige Geschwindigkeit zu erreichen, führt der Controller mehrere Sortier-Methoden gleichzeitig aus. Kapitel hat gezeigt, wie diese Methoden ihre Daten nebenläufig und ohne unnötige Wartezeit erhalten. Dieses Kapitel soll zeigen, wie die Daten, nebenläufig und ohne blockierende Synchronisation, in das Modell geschrieben werden. Grundlage der asynchronen Datenverarbeitung ist hier, dass im Snapshot-Betrieb jedes Neuronenpotential eines Zeitpunkts nur ein einziges Mal übertragen wird. Das heißt, wenn z. B. Sortier-Task-1 die Snapshot-Daten für den Zeitpunkt 314 in den entsprechenden Array des Clusters Nr. 314 geschrieben hat, dann wird danach niemand mehr schreibend auf diese Arraystellen zugreifen. Die Snapshot-Daten für Zeitpunkt 314 werden schließlich nur ein einziges Mal versendet. 5 Initialisierungs- und Steuerungspakete

52 6 NepteronSoftware 47 Im Realtime-Modus werden die Potentiale mancher Neuronen allerdings doppelt übertragen. Nämlich einerseits per Realtime-Paket und andererseits durch die Snapshot-Pakete, die für jeden vierten Simulationszeitpunkt übertragen werden. Um einen gleichzeitigen Schreibzugriff zu vermeiden, wurde folgender Mechanismus implementiert: Bevor ein Sortier-Task die Daten eines Snapshot-Pakets in das Modell schreibt, überprüft er, ob das Neuron auf der NeuronTransferList steht. Falls ja, wird es ignoriert, denn sein Potential wird oder wurde auch als Realtime-Paket übertragen und an anderer Stelle eingetragen. Listing 22 zeigt diese Abfrage. Firestatus-Daten kommen ebenfalls ohne Synchronisation aus. Listing 22: if-abfrage synchronisiert verschiedene Pakettypen; aus CProcessHLpkt::Go() Die Sortier-Tasks lesen aus den Headern heraus, zu welchem Zeitpunkt die Nutzdaten gehören. Um die Daten in das passende Cluster zu schreiben, benötigen sie einen Zeiger darauf. Dieser wird vom ClusterMaster geliefert und liegt in einer Map. Listing 23 zeigt die Deklaration dieser Map. Listing 23: Deklaration der Map, die den Cluster-Zugriff nach Systemzeit erlaubt; aus CClusterMaster.h Sie dient, wie bereits erwähnt, dazu, einen Zeitpunkt auf einen Cluster-Zeiger zu mappen. Ein Array kann diese Aufgabe nicht übernehmen, weil vorgesehen ist, dass zur Laufzeit neue Cluster erzeugt werden. Vectoren können zwar zur Laufzeit wachsen, allerdings verrutscht ihr Index dabei. Mit Hilfe eines Offsets könnte man hier zwar entgegen arbeiten, allerdings ist das aufwändig und Maps wurden exakt für solche Aufgaben geschaffen. Listing 24 zeigt wie eine größere Menge Zeiger in die Map gelegt werden und Listing 25 demonstriert das Auslesen derselben. Listing 24: CClusterMaster::AddNewClusters() erzeugt neue Cluster und legt shared_ptr in die Map In Zeile 86 von Listing 24 wird ein temporärer shared_ptr zurückgesetzt, gleichzeitig ein neuer Cluster erzeugt und dem shared_ptr zugewiesen. Zeile 87 ermittelt das Ende der Map, also das Element mit dem höchsten Key. Das ist hilfreich, da es sich um eine geordnete Map handelt. Durch die Angabe der höchsten Stelle geht das Einfügen in die Map schneller von statten. Das eigentliche Einfügen erfolgt in den Zeilen 88 bis 90. Die Methode insert() des Objekt map_clusters wird aufgerufen. Sie erwartet zwei Parameter. Als erstes wird der Iterator it aus Zeile 87 übergeben. Das zweite ist ein Template Objekt, vom Typ pair. Es gehört zum Namensraum std und wird hier für die Datentypen NepteronTime und pcluster erzeugt. Es wird mit dem Zeitstempel des neu erzeugten Clusters und seinem shared_ptr initialisiert. Anschließend wird eine Verwaltungsvariable inkrementiert und es kann von vorn begonnen werden. Listing 25: shared_ptr auf ein Cluster wird aus Map entnommen; aus CProcessHLpkt::Go()

53 6 NepteronSoftware Modell Controller In diese Kategorie fallen Fehlermeldungen. Sie wurden während der Konzeption nicht festgelegt, sondern während der Implementierung nach Bedarf eingeführt. Jede Methode, die keinen speziellen Rückgabewert benötigt, wurde mit int als Rückgabetyp versehen, um das nachträglich Einpflegen von Fehlermeldungen zu erleichtern. Dabei bedeutet 1 immer, dass die Methode erfolgreich ausgeführt wurde. Das Bit b_currentlycreatingnewclusters des ClusterMasters, gibt darüber Auskunft, ob gerade neue Cluster erzeugt werden. Des Weiteren sind vier Bits bei jedem Cluster dafür vorgesehen, Auskunft darüber zu geben, ob sie mit Daten befüllt sind oder nicht. Die richtige Stelle für ihre Anwendung, ist die switch-case Anweisung in der Methode CTaskProcessHLpkt::execute() Modell View und View Modell Die einzige, bisher implementierte Kommunikation zwischen Modell und View besteht darin, dass Neuronen mit ungültigen Potentialen den Wert besitzen. Es wurden verschiedene Ansätze erdacht, um eine Art Videospeicher zu realisieren. Allerdings hängt diese Schnittstelle so sehr von der zukünftigen Implementierung der graphischen Oberfläche ab, dass es nicht sinnvoll erscheint sie hier auszuführen Controller View In dieser Richtung sind ebenfalls nur Status- und Fehlermeldungen möglich. Bisher wurde eine Klasse namens CInterfaceVC angelegt. Sie enthält das Bit namens b_forcetoquit. Es kann von View gesetzt werden und führt dazu, dass Data und Controller ihre Arbeit einstellen. Um genau zu sein: TaskUSBReader() hört auf Daten vom USB zu lesen und beendet sich selbst. Alle Instanzen von CTaskProcessHLpkt::execute() 6 beenden sich selbst, sobald alle noch vorliegenden Rohdaten bearbeitet wurden. Dieses Bit wird ebenfalls von CTaskProcessHLpkt::execute() gesetzt, sobald das Ready-Paket dekodiert wurde. Es kann von View verwendet werden, um das Programm vorzeitig abzubrechen View Controller Da im Rahmen dieser Diplomarbeit keine endgültige View-Schicht implementiert wurde, der Controller aber trotzdem von einem Benutzer gesteuert werden muss, wurden verschiedene Konsolen-Menüs implementiert. Diese sind nicht für die finale Version des Programms vorgesehen, sondern dienen nur der Fehlersuche und dem Funktionsnachweis von Controller, Data und Modell. Der Einfachheit halber stehen diese Menüs in bei den entsprechenden Code-Stellen und nicht in speziellen Menü-Klassen. Das größte dieser Menüs befindet sich in CNepteronInit::Go(), weitere finden sich in CTaskRoot, der die Rolle der Programmeintrittsfunktion main() übernimmt. Grundsätzlich muss das zukünftige View alle Methoden und Funktionen aufrufen, die CTaskRoot aufruft. Um die Entwicklung des zukünftigen Views zu unterstützen, wurden bereits verschiedene Methoden angelegt, mit denen der Controller bedient werden kann, ohne dessen Funktionsweise zu kennen. Dazu gehören vor allem SendInitFile() und PutNeuronOnNTL(). Allerdings waren diese Vorbereitungen nicht erschöpfend. Beispielsweise wird sicherlich noch eine Methode, der Art DeleteNeuronFromNTL(), nötig sein. 6 die Sortier-Tasks

54 6 NepteronSoftware Die Implementierung von NepteronSoftware Es sollte nun ersichtlich sein, dass Eingriffe in den vorliegenden Quellcode nötig sind, um View zu realisieren. Daher soll er in den folgenden Abschnitten möglichst übersichtlich dokumentiert werden. Einen groben Überblick gab bereits Kapitel 6.1 indem es die wichtigsten Abläufe der NepteronSoftware als Flussdiagramm und Petri-Netz darstellt. Kapitel bietet eine detailliertere, tabellarische Übersicht. Die anschließenden Kapitel gehen auf die, für die Weiterentwicklung von NepteronSoftware, wichtigsten Klassen ein Klassenübersicht Die folgende Tabelle soll einen Überblick über die implementierten Klassen geben. Im Quellcode beginnt jeder Klassenname mit einem C, darauf wird an dieser Stelle verzichtet. Die Spalte Z gibt die Zugehörigkeit, im Sinne der MVC-D Architektur, an. Bei einigen Methoden wird auf die Angabe der Parameter verzichtet. Dieser Umstand wird durch ein gekennzeichnet. Wenn Parameter primitive Datentypen sind, wird auf die explizite Angabe des Datentyps verzichtet, enthält der Variablennamen ihn doch implizit7. Klassen, die im Projekt enthalten, aber hier nicht gelistet sind, sind für die aktuelle Funktion nicht relevant Name Notizen Nennenswerte Attribute Methoden Cluster Jedes Objekt dieses Typs float f32_aneuron[cu32_clustersize]; //400 repräsentiert einen Zeitpunkt und bool b_afirestatus[cu32_clustersize]; enthält daher alle für diesen NepteronTime NT_TimeStamp; Zeitpunkt bekannten Daten. Cluster werden per shared_ptr in einer queue gespeichert. und Z M Wird benutzt von:clustermaster Bekommt: Neuronenpotentiale Gibt: Neuronenpotentiale Ist Teil von: ClusterMaster Wird erzeugt von: ClusterMaster Wird zerstört von: Datenbankanbindung (zukünftig) ClusterMaster Eine Instanz pro Neuronenkern. Verwaltet und erzeugt die Cluster im RAM. Hat Map, die Systemzeit nimmt und shared_ptr auf Cluster gibt. Benutzt: InterfaceVC Wird benutzt von: TaskProcessHLpkt, TaskUSBReader Bekommt: Systemzeit Gibt: shared_ptr auf Cluster Erzeugt: Cluster Ist Teil von: Modell Besteht aus/enthält: Cluster Wird erzeugt von: TaskRoot Wird zerstört von: TaskRoot 7 z. B. const unsigned int cu32_name int M AddNewClusters( u32_numberofnewclusters); std::map< NepteronTime, boost::shared_ptr<ccluster> > map_clusters;

55 6 NepteronSoftware InterfaceVC 50 Eine Instanz pro Neuronenkern. Ist Interface für View und Controller. C/V V/C Wird benutzt von: View, Controller Ist Teil von: V/C und C/V Wird erzeugt von: TaskRoot Wird zerstört von: TaskRoot NepteronInit Eine Instanz pro Neuronenkern. Wickelt die gesamte Initialisierung des Nepteron-Prozessors ab. Kopien der gesendeten Daten werden auf der Festplatte unter C:\Nepteron\USBdump abgelegt. Benutzt: NeuronTransferList, InterfaceVC, OpenUSB, Initialisierungsdateien Wird benutzt von: TaskRoot Erzeugt: OpenUSB Zerstört: OpenUSB unsigned char * ReadASCII( ) unsigned char * CookASCIIData( ) int TiePakets( ) int SendInitFile( ) int CookNTL() int SendNTL() int SendFIFO7() int SendStart( ) C Ist Teil von: Controller Wird erzeugt von: TaskRoot Wird zerstört von: TaskRoot NepteronInitDatafactory Eine Instanz pro Neuronenkern. int ManipulateBorders(); Erzeugt eine bestimmte Art int ManipulateValues(); Initialisierungsdaten, die vom int ProduceInitData(); Benutzer manipuliert werden können sollen. Zur Zeit müssen diese Daten für jede Simulation gleich sein und diese Klasse ist nicht in Betrieb. C Wird benutzt von: z.z. niemanden, später z.b. von TaskRoot oder NepteronInit Bekommt: Schranken für die einzelnen Konstanten durch Benutzer oder Quellcode Erzeugt:.snd-Dateien Ist Teil von: Controller Wird erzeugt von: z.z. niemanden Wird zerstört von: z.z. niemanden NeuronTransferList Eine Instanz pro Neuronenkern. unsigned int u32_antl[cu32_sizeofntl]; In diesem Objekt wird gespeichert, bool b_aisonlist[cu32_clustersize]; welche Neuronen im Realtime- int PutNeuronOnNTL(u32_NeuronNr); Modus übertragen werden sollen. Wird benutzt von: View, TaskProcessHLpkt Bekommt: Neuronennummern Ist Teil von: Controller Wird erzeugt von: TaskRoot Wird zerstört von: TaskRoot C

56 6 NepteronSoftware OpenUSB 51 Eine Instanz. int Go(); Öffnet eine USB Verbindung zum bool IsOpen(); Nepteron-Prozessor und führt CCyUSBDevice * USBDevice; grundlegende Einstellungen durch. D Achtung! Trotz C-artiger Fehlerbehandlung stürzt NepteronSoftware ab, wenn COpenUSB::Go() aufgerufen wird und kein Cypress-USB Gerät angeschlossen ist. Vermutlich C++artige Fehlerbehandlung nötig. Benutzt: USB Verbindung Wird benutzt von: TaskUSBReader Ist Teil von: Data Wird erzeugt von: TaskUSBReader Wird zerstört von: TaskUSBReader RawDataStorage Eine Instanz pro Neuronenkern. tbb::deprecated::concurrent_queue< boost::shared_array<unsigned char> Enthält concurrent_queue, die die > HLp_qRawDataStorage; Rohdaten des Nepteron-Prozessors speichert. Sonst nichts. D/C Wird benutzt von: TaskUSBReader, TaskProcessHLpkt Bekommt: shared_arrays Gibt: shared_arrays Ist Teil von: Interface D/C Wird erzeugt von: TaskRoot Wird zerstört von: TaskRoot TaskProcessHLpkt z.z 3 Instanzen. tbb::task* execute(void); C Eine Instanz. tbb::task* execute(void); Dieser Task ersetzt main(). Er erzeugt alle nötigen Objekte und Tasks, um den Nepteron-Prozessor zu bedienen. C Diese Tasks laufen gleichzeitig auf mehreren Prozessoren und sortieren den ankommenden USB Datenstrom in das Modell. Benutzt: ClusterMaster, RawDataStorage, NeuronTransferList, InterfaceVC Bekommt: Rohdaten aus RawDataStorage und Adressen der Cluster von ClusterMaster Zerstört: Rohdaten Ist Teil von: Controller Wird erzeugt von: TaskRoot Wird zerstört von: sich selbst TaskRoot

57 6 NepteronSoftware 52 Benutzt: siehe Quellcode Erzeugt: siehe Quellcode Zerstört: siehe Quellcode Wird erzeugt von: CNepteron2xApp::InitInstance() Wird zerstört von: CNepteron2xApp::InitInstance() TaskTXTReader Zwei Instanzen. tbb::task* execute(void); D tbb::task* execute(void); D Wurde implementiert, um, in früheren Versionen, Testdaten aus Dateien, statt vom USB zu lesen. Verwendet die selbe Schnittstelle wie TaskUSBReader. Wird nicht mehr verwendet. TaskUSBReader Eine Instanz. Nachdem das Start-Paket gesendet wurde, wird dieser Task gestartet. Er fordert Daten am USB an, bis eine der TaskProcessHLpktInstanzen das Ready-Paket dekodiert hat. Üblicherweise entsteht danach noch ein Timeout, weil bereits einmal zu oft angefordert wurde. Benutzt: OpenUSB, RawDataStorage, InterfaceVC Gibt: Rohdaten Erzeugt: Arrays für Rohdaten Ist Teil von: Data Wird erzeugt von: TaskRoot Wird zerstört von: TaskRoot TestDataFactory Eine Instanz. Produzierte die Testdaten für TaskTXTReader. Nicht mehr in Benutzung. Zwei Datensätze zu 256 und 512 MB liegen unter C:\ Nepteron\TestData int GenerateRTPTestData( int GenerateSSPTestData( ); ); C

58 6 NepteronSoftware Initialisierung des Nepteron-Prozessors Dieses Kapitel gibt Informationen darüber, wie die Initialisierungsdaten gewonnen werden und wie sie in Pakete verpackt und verschickt werden. Das erste Unterkapitel beschreibt den Aufbau der verschiedenen Dateien. Darauf folgt eine Beschreibung, wie die Pakete verpackt und versendet werden. Einige Initialisierungsdaten nämlich die Konstanten der Differentialgleichungen des Huber-Braun-Modells müssen in Zukunft vom Benutzer erzeugt werden können. Dafür wurde die sogenannte NepteronInitDataFactory geschrieben. Ihr wird, genauso wie der NeuronTransferList, ein eigenes Unterkapitel gewidmet Aufbau der Initialisierungsdateien Es gibt drei Gruppen von Speichern, die initialisiert werden müssen: Codespeicher Konstantenspeicher RAMs Für die Codespeicher liegen sechs Dateien im ASCII Format vor. Damit sie vom Menschen besser gelesen werden können, ist nach jeweils 16 ASCII-Zeichen ein Zeilenumbruch und Wagenrücklauf (LF, CR) eingefügt. Für die Maschinen sind sie unnötig und müssen beim Einlesen ignoriert werden. Die Datei muss Zeile für Zeile verschickt werden. Jede Zeile muss Zahl für Zahl in umgekehrter Reihenfolge verschickt werden. Dabei ist zu beachten, dass sich eine Zahl aus zwei ASCII-Zeichen zusammensetzt. Die höherwertige Stelle steht links, die niederwertige rechts. Z.B. ergibt die Zeichenfolge F0 die hexadezimale Zahl 0xF0, also 240, dezimal. Abbildung 26: ADD_SUB_A_ASC.txt Abbildung 26 zeigt einen Ausschnitt einer solchen Datei. Um die Zeile zu versenden wird zuerst ein Array angelegt, der die zu sendenden Daten aufnimmt. Anschließend wird die Datei eingelesen und aus den beiden ASCII-Zeichen 1 und 5 wird die dezimale Zahl 21 gebildet. Diese wird im Array an der Stelle 0 abgelegt. Anschließend wird aus den Ziffern 8 und 4 die dezimale 132 gebildet und im Array an Stelle 1 abgelegt. Nachdem die Zeile so verarbeitet wurde, wird mit der nächsten Zeile fortgefahren, bis alle Zeilen verarbeitet wurden. Der Array ist nun gefüllt und kann zum Versenden an die USB Schnittstelle übergeben werden. Die Textdateien für Konstantenspeicher und RAMs unterscheiden sich nur durch die Zeilenlänge. Sie beträgt bei ihnen acht ASCII-Zeichen. Die Dateien, die von NepteronSoftware erzeugt werden, besitzen die Endung snd und enthalten Zahlen im binären Format, in der Reihenfolge, in der sie versendet werden sollen, ohne CR und LF. Das heißt, sie müssen beim Einlesen lediglich in den Sende-Array kopiert werden. Um sie für Menschen lesbar zu machen, können sogenannte HexEditoren eingesetzt werden. Diese Dateien werden von NepteronInitDataFactory produziert und kommen erst zum Einsatz, wenn der Benutzer Einfluss auf die Initialisierung nehmen soll.

59 6 NepteronSoftware Versenden der Initialisierungsdateien Da für das Versenden, je nach Dateityp und Speichertyp, zahlreiche, unterschiedliche Aktionen nötig sind, wurde eine Interface-Methode geschaffen, die all dies verbirgt. Sie kann jede der im vorigen Kapitel erwähnten Textdateien versenden und gehört zu der Klasse CNepteronInit. Listing 26 zeigt ihre Deklaration. Listing 26: Deklaration von CNepteronInit::SendInitFile() Als erstes Parameter muss ein Zeiger auf einen Array übergeben werden, der den Pfad enthält, an dem dem die zu versendende Datei liegt. Als nächstes muss bekannt gegeben werden, wie viele Zeichen die Datei enthält. Hierbei dürfen CR und LF nicht mitgezählt werden. Danach muss ein Zeiger auf ein Feld, das den Header des ersten Pakets enthält, angegeben werden. Listing 27 zeigt als Beispiel die Zuweisung für den NTL-Header. Listing 27: Zuweisung NTL Header; aus CNepteronInit::SendNTL() Die nächsten beiden Parameter gelten dem USB-dump File. Diese Dateien sind üblicherweise unter C:\Nepteron\USBdump zu finden. Sie enthalten eine Kopie der Daten, die per USB versendet wurden. Mit dem Parameter u8_pusbdump wird ein Zeiger auf einen Array, der den Dateipfad enthält übergeben und b_newdumpfile gibt an, ob eine neue Datei erzeugt werden soll, oder ob an das Ende der alten geschrieben werden soll. Die letzte Variable schließlich, dient zur Entscheidung ob eine Konstantendatei versendet werden soll, unterscheiden sich diese doch durch die Zeilenlänge von den anderen. Wenn snd-dateien zu versenden sind, kann direkt die Methode CNepteronInit::TiePakets verwendet werden. Sie wird auch von SendInitFile() gerufen und übernimmt, unter anderem, einen Zeiger auf die gekochten Daten. Mit diesem Begriff soll der Unterschied zu den Rohdaten aus den ASCII-Dateien deutlich gemacht werden. Das heißt, es muss ein Array übergeben werden, der die zu versendenden Daten im endgültigen Format und in korrekter Reihenfolge enthält. Das erste, zu sendende Byte muss also an Stelle 0 eines unsigned char Arrays stehen. TiePakets() teilt diesen Array in 496 Byte große Blöcke, versieht diese mit den passenden Headern und übergibt sie der USB-Schnittstelle.

60 6 NepteronSoftware Die NepteronInitDatafactory Diese Fabrik-Klasse legt zuerst eine dreidimensionale Struktur an und füllt diese anschließend mit den Konstanten. Danach wird die Struktur in Dateien mit der Endung snd abgelegt. Diese Dateien können, wie im vorherigen Abschnitt beschrieben, von NepteronInit versendet werden. Jedes Neuron verfügt im Huber-Braun Modell über 16 Konstanten. Es ist gewünscht, dass diese vom Benutzer manipuliert werden können. Dabei soll nicht nur die Möglichkeit bestehen, jeden Wert von Hand einzustellen, sondern es sollen auch zufällige Werte erzeugt werden können und diese Zufallswerte sollen in bestimmten Grenzen liegen. Deshalb gesellt sich zu jeder Konstanten noch eine Ober- und eine Untergrenze. Die Struktur aus Listing 28 bildet diesen Zusammenhang für eine Konstante ab. Da es 16 Konstanten gibt wurde die Struktur in Listing 29 definiert. Sie enthält insgesamt 16 kleinere Strukturen, die wie die Konstanten der Gleichungen heißen. Listing 28: Drei Werte pro Konstante Listing 29: 16 Konstanten pro Neuron Aus dieser Struktur wird ein dreidimensionaler Array erzeugt. In seiner letzten Dimension enthält er eine NeuronInitData Struktur pro Neuron, also 400. Dieser Array aus Strukturen wird wiederum fünf Mal erzeugt, weil es fünf Konstantenspeicher gibt. Und, weil es zwei Modelle gibt, wird all dies durch die dritte Dimension noch einmal verdoppelt. Listing 30 zeigt die Deklaration dieses Arrays. Listing 30: Deklaration InitData[][][]; aus CNepteronInitDataFactory.h Die Methoden CCNepteronInitDataFactory::ManipulateBorders() und CCNepteronInitDataFactory::ManipulateValues() geben ein Beispiel, wie dieser Array befüllt werden kann. Listing 31 erspart dem geneigten Leser ein Nachschlagen im Quelltext. Der Funktion MyRandomFloat werden hier die beiden Grenzen übergeben und ihr Rückgabewert wird der eigentlichen Konstanten V_l zugewiesen.

61 6 NepteronSoftware 56 Listing 31: Erzeugen eines begrenzten Zufallswertes und Zuweisung; aus CCNepteronInitDataFactory::ManipulateValues() Die NeuronTransferList Die NeuronTransferList (NTL) dient dazu, festzuhalten, welche Neuronen im Realtime-Modus übertragen werden sollen. Es gibt zwei Abschriften dieser Liste, eine im Nepteron-Prozessor und eine in der NepteronSoftware. Das Original wird vom Benutzer erzeugt und NepteronSoftware übergibt sie anschließend dem Prozessor. Dabei behält sie eine Kopie, die sie für den reibungslosen Ablauf er Datenverarbeitung benötigt. Um diese Kopie zu verwalten, wurde die Klasse CNeuronTransferList eingeführt. Sie besteht im Prinzip aus einem Array, der Neuronennummern in aufsteigender Reihenfolge enthält. Für den reibungslosen Programmablauf sorgt ein Bool-Array, der ein Bit pro Neuron enthält. Er trägt die Information, ob das entsprechende Neuron auf der Liste steht oder nicht. Um ein Hinzufügen von Neuronen auf die Liste zu erleichtern, wurde die Methode PutNeuronOnNTL() implementiert. Listing 32 zeigt den relevanten Code. Sie überprüft, ob die übergebene Neuronennummer im gültigen Bereich liegt, ob das Neuron schon auf der Liste steht und fügt es dem Array u32_antl der die eigentliche NTL darstellt hinzu. Anschließend setzt sie das passende Bit im Bool-Array. Schließlich sortiert sie den Array, weil der Nepteron-Prozessor die Liste in aufsteigender Reihenfolge benötigt. Auf ähnliche Weise können Methoden wie DeleteNeuronFromNTL() realisiert werden. Listing 32: Eine Neuronennummer wird auf die NTL gesetzt; aus CNeuronTransferList::PutNeuronOnNTL()

62 6 NepteronSoftware Datenverarbeitung Was CTaskProcessHLpkt tut, wurde bereits in verschiedenen Kapiteln beschrieben. Dieses Kapitel soll dabei helfen zu verstehen, wie es getan wird. Vor allem das Flussdiagramm aus Abb. 28 im gedruckten Anhang dient diesem Zweck. Der Block Ignoriere dieses LLpkt ist dabei kein Fehlerfall, sondern die Regel. Das letzte High-Level-Paket einer Simulationen ist meistens nicht vollständig gefüllt. Dennoch ist der übergebene Array 1024*512 Byte groß. Dieser Block sorgt dafür, dass die ungültigen Low-Level-Pakete nicht verarbeitet werden. Listing 33 zeigt, wie CTaskProcessHLpkt und CTaskUSBReader aufgerufen werden. Listing 33: Starten der Datenannahme und -verarbeitung auf vier Prozessoren; aus CTaskRoot::execute() Es soll an dieser Stelle noch darauf eingegangen werden, warum in diesem C++-Programm Assemblercode auftaucht: Die Neuronenpotentiale, die der Nepteron-Prozessor errechnet, sind 32 Bit Gleitkommazahlen, USB überträgt allerdings nur Bytes. Deshalb muss aus jeweils vier Byte eine Gleitkommazahl zusammengesetzt werden. Dies ist an sich kein Problem, da sie nur Bit für Bit aneinander gereiht werden müssten und anschließend als Float behandelt werden könnten. Diesem Plan macht das Typensystem von C++ allerdings einen Strich durch die Rechnung, denn die Bytes müssen als 32 Bit Integer zusammengesetzt werden, weil Gleitkommazahlen nicht binär verschoben werden können. Multiplikationen mit 2er-Potenzen helfen auch nicht, weil sie bei Gleitkommazahlen keine Verschiebung bewirken. Sind sie im Integerformat zusammengesetzt funktioniert eine Zuweisung der Art (f32_floatvar = u32_integervar) aber nicht, weil C++ den Integerwert automatisch in die Gleitkommadarstellung mit Mantisse und Exponent umrechnet. Die Lösung bietet der Code aus Listing 34. In den Zeilen 132 bis 135 wird die Integer Zahl zusammengesetzt. Diese Zahl soll nun vom Compiler als Float behandelt werden. Der Assemblercode in den Zeilen 136 bis 140 kopiert sie in eine vorher angelegte Gleitkommavariable, und diese kann in Zeile 142 schließlich in das Datenmodell geschrieben werden. Listing 34: Bitweises Kopieren von Int nach Float; aus CTaskProcessHLpkt

63 7 Funktionsnachweis 7 58 Funktionsnachweis Während der Software-Entwicklung wurde mit dem VisualC++ Debugger, verschiedenen Textdateien, Konsolenausgaben und Zählern gearbeitet, um die Funktion einzelner Programmteile zu überprüfen. Die Hardware-Entwicklung arbeitete mit dem Programm ModelSim, LEDs und der RS232 Schnittstelle, um das Selbe zu erreichen. Nachdem beide Teile zusammengefügt waren, genügten diese Tests allerdings nicht mehr. Es sollte ein umfassender und lückenloser Test durchgeführt werden. Zu diesem Zweck wurde mit ModelSim eine Textdatei erstellt, die die Simulationsergebnisse für einen größeren Zeitraum enthielt. Anschließend forderte NepteronSoftware exakt diesen Zeitraum vom realen Nepteron-Prozessor an und sortierte die ankommenden Daten ihn in sein Modell. Danach wurde eine eigens geschriebene Funktion aufgerufen, die alle Daten aus dem Modell in eine Textdatei schrieb. Diese wurde nach dem selben Format wie die ModelSim Datei aufgebaut. Die Dateien wurden mit dem Programm DiffMerge 8 verglichen. Es markiert die Unterschiede von Textdateien, indem es die entsprechenden Zeichen rot hinterlegt. Wie in den drei Abbildungen 29, 30 und 31, im gedruckten Anhang zu sehen ist, unterscheiden sich die Dateien durch jeweils ein Leerzeichen, in den Zeilen zwischen den Low-Level-Paketen. Das kam dadurch, dass ModelSim bei Zeilenumbrüchen automatisch Leerzeichen einfügt, die Funktion fprintf() aber nicht. Außerdem sind die letzten Low-Level-Pakete unterschiedlich. Das liegt daran, dass das letzte High-Level-Paket nicht vollständig gefüllt ist. ModelSim füllt es mit 0xFF und der Cypress USB-Treiber füllt sie mit 0xCD. Die eigentlichen Nutzdaten sind in beiden Dateien identisch. Das bedeutet, dass der NepteronProzessor das neuronale Netz wie geplant berechnet, dass die Datenübertragung auf beiden Seiten funktioniert und dass NepteronSoftware die Daten richtig verarbeitet. 8

64 8 Fazit, persönliches Resümee und Ausblick 8 59 Fazit, persönliches Resümee und Ausblick Zu Beginn dieser Diplomarbeit war beabsichtigt, NepteronSoftware von A bis Z komplett fertig zu stellen. Das heißt, es sollte sich in die benötigten Technologien eingearbeitet werden und eine Machbarkeitsstudie durchgeführt werden. Anschließend sollte die Software konzipiert, implementiert, getestet und dokumentiert werden. Dabei standen Datenannahme via USB, Datenverarbeitung, Initialisierung des Nepteron-Prozessors, graphische Oberfläche und Speichern der Simulationsergebnisse auf dem Plan. Nachdem sich herausgestellt hatte, dass echt parallele Datenverarbeitung eingesetzt werden musste stiegen die Anforderungen. Es zeigte sich, dass die Initialisierung wesentlich aufwändiger als erwartet war und es wurde langsam klar, dass diese Software nicht von einer Person in 5 Monaten, von einer leeren C++-Datei, bis zum fertigen Release, entwickelt werden konnte. Als schließlich deutlich wurde, dass zum Ablegen der Daten auf Festplatte, nur eine Datenbank in Frage kommt, galt der Rahmen dieser Arbeit offiziell als gesprengt. Es wurde in einem Gespräch, mit dem Projektverantwortlichen und dem betreuenden Professor der Umfang der Diplomarbeit reduziert. Die Machbarkeitsstudie war bereits durchgeführt, die Software geplant und die Initialisierung nahezu komplett. Es wurde daher beschlossen, die weitere Implementierung auf Datenannahme und Datenverarbeitung zu beschränken. Damit war das Thema schlüssig abgegrenzt, und der angestrebte Entwicklungsstand erschien für das Nepteron-Projekt von Nutzen. Diese Absicht wurde vom Projektleiter und Korreferenten abgesegnet und die Arbeit wurde fortgesetzt. Die Einarbeitung in die Cypress USB-Bibliothek war, mit Kenntnissen in der objektorientierten Programmierung, nicht weiter schwer. Der Umgang mit TBB lernte sich etwas zäher, war aber, dank der ausgezeichneten Dokumentation und den zahlreichen Beispielen seitens Intel, in vernünftiger Zeit zu erledigen. STL und Boost sind an zahlreichen Stellen im Internet extrem gut dokumentiert. Nachdem verstanden war, wie C++-Templates funktionieren eröffnete sich mit diesen Bibliotheken eine kleine, neue Welt. Dank all dieser Werkzeuge war es möglich NepteronSoftware zu implementieren und in den letzten Tagen, wie geplant, den Funktionsnachweis für Hard- und Software zu erbringen. Damit war die Praxisphase erfolgreich abgeschlossen. Auch für mich persönlich war diese Arbeit ein Erfolg. Die erledigten Aufgaben entsprachen meinen Vorstellungen, und ich konnte mich besser als erwartet weiterbilden. Vor allem die echt parallele Programmierung und den Umgang mit USB fand ich sehr interessant. Mit TR1 und Boost habe ich C++ Erweiterungen kennen gelernt, die in keinem Hochsprachler-Repertoire fehlen sollten. Zu Beginn der Diplomarbeit hatte ich vor, in der Embedded-Software-Entwicklung tätig zu werden. Dieses Vorhaben wurde durch die Entwicklung der NepteronSoftware noch einmal gefestigt. Ich hoffe, dass meine Arbeit dem Nepteron-Projekt von Nutzen sein wird. Um die NepteronSoftware zu einem anwendungsfreundlichen Programm zu machen, muss eine graphische Oberfläche realisiert werden. Damit es auch praktisch einsetzbar ist, müssen die Simulationsergebnisse in einer Datenbank abgelegt werden können. Es wäre es möglich. dass die graphische Oberfläche ihre Daten direkt aus dieser Datenbank bezieht. Dadurch würde sich der Eingriff in den bestehenden Code auf ein Minimum reduzieren. Falls in Zukunft Rückfragen nötig sind, stehe ich, wie versprochen, gern zur Verfügung.

65 9 Literaturverzeichnis Literaturverzeichnis9 [1] [2] Huber, M.T.; Braun, H.A.: Stimulus response curves of a neuronal model for noisy subthreshold oscillations and related spike generation ; University of Marburg, 2005 [3] Beuler, Marcel et al.; Echtzeitfähige Berechnung der Modellgleichungen von Huber-BraunNeuronen auf einem Virtex-4-FPGA [4] Beuler, Marcel et al; Detaillierte Beschreibung einer FIFO-Management-Unit zum USB-2.0Datentransfer zwischen Virtex-4-FPGA und PC [5] Kießling, J.P. et al; Übertragung und Echtzeitverarbeitung eines Datenstroms zwischen Virtex4 FPGA und Multi-Core PC mit USB [6] Meyers, Scott; Effektiv C++ programmieren, ADDISON-WESLEY [7] [8] [9] [10] Intel(R) Threading Building Blocks Reference Manual [11] [12] [13] [14] [15] sess=8d585b ff7058ee2cc6f5f [16] Kelm, H.J.; USB 2.0 Studienausgabe, FRANZIS PROFESSIONAL SERIES [17] Cypress CyAPI Programmer's Reference [18] EZ-USB Advanced Development Kit Users [19] Cypress CyAPI Programmer's Reference: [20] Cypress: Measuring Delivered USB 2.0 Bandwidth with an EZ-USB FX2 Development Board [21] Parallele Programmierung, Thomas Rauber,Gudula Rünger [22] CERN; FastFlow: Efficient Parallel Streaming Applications on Multi-core [23] Vogel-Heuser; Systems Software Engineering [24] [25] 9 Alle Internetadressen:

66 10 Gedruckter Anhang Gedruckter Anhang Programmiervorschriften und Nomenklatur für Variablen Damit ein größeres Programm auf Dauer lesbar bleibt, um die (Wieder)Einarbeitungszeit zu verkürzen, und um logische Fehler während der Programmierung zu vermeiden, ist es sinnvoll Kodierrichtlinien einzuführen und Variablen nach bestimmten Mustern zu benennen. Es gibt verschiedene Ansätze dazu, im Großen und Ganzen kocht jedoch jede Projektgruppe ihr eigenes Süppchen; wir wollen hier keine Ausnahme machen. Die verwendeten Regeln und die Nomenklatur wird auf den folgenden Seiten vorgestellt. Es ist verboten in einem verschachtelten Block den Variablennamen eines übergeordneten Blocks zu verwenden. Listing 35: Überdeckung eines übergeordneten Variablennamens Alle Variablennamen, Klassennamen, Kommentare, Konsolenausgaben etc. werden in Englisch gehalten. Die Angaben k, M und G werden IEEE konform als 10er-Potenzen verwendet. Wo nötig

67 10 Gedruckter Anhang 62 werden 2er-Potenzen entweder ausgeschrieben oder die entsprechenden Abkürzungen Ki, Mi und Gi eingesetzt. Pro Zeile maximal eine Variablendeklaration. Lokale Variablen werden alle am Anfang ihres Gültigkeitsbereiches deklariert und erst initialisiert, wenn sie gebraucht werden. Deklaration und Initialisierung dürfen nicht in der selben Zeile geschehen. Ausnahme: Referenzen und Konstanten. Geschweifte Klammern werden immer gesetzt, auch wenn syntaktisch darauf verzichtet werden könnte. Beispiel: Listing 36: Klammerpflicht Die "using namespace" Klausel ist verboten. Die goto Anweisung ist verboten. Jede Funktion und Methode hat exakt eine return Anweisung. Falls kein spezieller Rückgabetyp benötigt wird, wird signed int verwendet. Die Variable heißt s32_retval und "1" gilt als fehlerfrei ausgeführt. Programmzeilen zum Experimentieren oder Debuggen werden nicht eingerückt. Es ist dafür zu sorgen, dass sie jederzeit entfernt werden können, ohne die eigentliche Programmfunktion zu beeinflussen. Zeilen die einer weiteren Bearbeitung bedürfen werden mit //xxx gekennzeichnet. Objekte werden vorzugsweise per Initialisierungsliste initialisiert. Initialisierungslisten halten die Reihenfolge der Deklaration ein Ein Objekt darf keinen intelligenten Zeiger auf sich selbst besitzen. Die eigentlichen Variablennamen sollen so gewählt werden, dass möglichst ohne Kommentar zu verstehen ist, wozu sie dienen. Dabei soll wenig Rücksicht auf ihre Länge genommen werden. Sie werden letztlich öfter gelesen als geschrieben. Des Weiteren steht ihr Datentyp im Namen. Hier die eingesetzte Kodierung: Variablen beginnen mit einem Präfix, das ihren Datentyp anzeigt. Das Präfix ist Pflicht und wird durch einen Unterstich vom eigentlichen Variablennamen getrennt, der mit einem Großbuchstaben beginnen muss. Dabei ist: u unsigned s signed f float 8 8 Bit Bit Bit

68 10 Gedruckter Anhang 63 s struct str string c Konstante (muss immer an erster Stelle stehen) Beispiele (32 Bit System): unsigned int u32_foo; double f64_bar; const float cf32_pi = ; Mit Suffixen, werden ähnliche Variablen genauer unterschieden. Sie sind optional Beispiele: unsigned char u8_pktnumber_hi; //high Byte unsigned char u8_pktnumber_lo; //low Byte signed float sf32_torque_motor1; signed float sf32_torque_motor2; Dem eigentlichen Variablennamen wird ein kleiner Buchstabe vorangestellt, um ihn als Pointer oder Ähnliches zu kennzeichnen. Dabei ist: p Pointer a Array sa shared_array sp shared_ptr Beispiele: unsigned char u8_ameinarray[5]; unsigned char * u8_pmeinarray = &u8_ameinarray[0]; Klassennamen beginnen mit einem C, gefolgt von einem Großbuchstaben. Wird eine Klasse in einem Gültigkeitsbereich nur ein Mal instanziiert, dann heißt dieses Objekt wie die Klasse, ohne das führende C.

69 10 Gedruckter Anhang Tabelle 2: Pakettypen des Protokolls 0xA1 Pakettyp Bedeutung 0x07 Stoppaket (Abbruch durch PC Software) 0x11 Init. Codespeicher ADD_SUB_A 0x12 Init. Codespeicher ADD_SUB_B 0x13 Init. Codespeicher MULT_A 0x14 Init. Codespeicher MULT_A 0x15 Init. Codespeicher MULT_B 0x16 Init. Codespeicher MULT_C 0x17 Init. Codespeicher CORDIC 0x21 Init. ROM ADD_SUB_A 0x22 Init. ROM ADD_SUB_B 0x23 Init. ROM _MULT_A 0x24 Init. ROM _MULT_B 0x25 Init. ROM _MULT_C 0x31 Init. RAM ADD_SUB_A 0x32 Init. RAM ADD_SUB_B 0x41 Init. FIFO_7 0x51 NeuronTransferList 0x61 Startpaket (Starte Simulation) 0x81 Readypaket (Simulation abgeschlossen) 0xA1 Realtimepaket 0xB1 Snapshotpaket 0xC1 Firestatuspaket 64

70 10 Gedruckter Anhang Abbildung 27: Flussdiagramm SpeedTest2.7 65

71 10 Gedruckter Anhang Abbildung 28: CTaskProcessHLpkt::execute(void) 66

72 10 Gedruckter Anhang Abbildung 29: Funktionsnachweis mit DiffMerge, Anfang der Dateien 67

73 10 Gedruckter Anhang Abbildung 30: Funktionsnachweis mit DiffMerge, mittig 68

74 10 Gedruckter Anhang Abbildung 31: Funktionsnachweis mit DiffMerge, Ende der Dateien 69

Vorkurs C++ Programmierung

Vorkurs C++ Programmierung Vorkurs C++ Programmierung Klassen Letzte Stunde Speicherverwaltung automatische Speicherverwaltung auf dem Stack dynamische Speicherverwaltung auf dem Heap new/new[] und delete/delete[] Speicherklassen:

Mehr

Objektorientiertes Programmieren für Ingenieure

Objektorientiertes Programmieren für Ingenieure Uwe Probst Objektorientiertes Programmieren für Ingenieure Anwendungen und Beispiele in C++ 18 2 Von C zu C++ 2.2.2 Referenzen und Funktionen Referenzen als Funktionsparameter Liefert eine Funktion einen

Mehr

einkonto.zahle(+100); //Transaktion Einzahlung einkonto.zahle(-20); //Transaktion Auszahlung einkonto.zahle(+30); //Transaktion Einzahlung

einkonto.zahle(+100); //Transaktion Einzahlung einkonto.zahle(-20); //Transaktion Auszahlung einkonto.zahle(+30); //Transaktion Einzahlung PIWIN I Kap. 7 Objektorientierte Programmierung - Einführung 28 Testklasse public class TestGirokonto { public static void main(string[] args) { // erzeuge neues Konto Girokonto einkonto = new Girokonto();

Mehr

Kapitel 6. Vererbung

Kapitel 6. Vererbung 1 Kapitel 6 2 Ziele Das sprinzip der objektorientierten Programmierung verstehen Und in Java umsetzen können Insbesondere folgende Begriffe verstehen und anwenden können: Ober/Unterklassen Subtyping Überschreiben

Mehr

Kapitel 6. Vererbung

Kapitel 6. Vererbung 1 Kapitel 6 2 Ziele Das sprinzip der objektorientierten Programmierung verstehen Und in Java umsetzen können Insbesondere folgende Begriffe verstehen und anwenden können: Ober/Unterklassen Subtyping Überschreiben

Mehr

4 Vererbung, Polymorphie

4 Vererbung, Polymorphie 4 Vererbung, Polymorphie Jörn Loviscach Versionsstand: 21. März 2014, 22:57 Die nummerierten Felder sind absichtlich leer, zum Ausfüllen beim Ansehen der Videos: http://www.j3l7h.de/videos.html This work

Mehr

Übersicht. Informatik 2 Teil 3 Anwendungsbeispiel für objektorientierte Programmierung

Übersicht. Informatik 2 Teil 3 Anwendungsbeispiel für objektorientierte Programmierung Übersicht 3.1 Modell Konto 3.2 Modell Konto - Erläuterungen 3.3 Benutzer Ein- und Ausgabe mit Dialogfenster I 3.4 Benutzer Ein- und Ausgabe mit Dialogfenster II 3.5 Klassen- und Objekteigenschaften des

Mehr

Grundlagen der Verwendung von make

Grundlagen der Verwendung von make Kurzskript zum Thema: Grundlagen der Verwendung von make Stefan Junghans Gregor Gilka 16. November 2012 1 Einleitung In diesem Teilskript sollen die Grundlagen der Verwendung des Programmes make und der

Mehr

C++ - Einführung in die Programmiersprache Polymorphismus und Vererbung. Eltern

C++ - Einführung in die Programmiersprache Polymorphismus und Vererbung. Eltern C++ - Einführung in die Programmiersprache Polymorphismus und Vererbung Eltern Kind Kind Vererbung Definition von Klassen auf Basis von bestehenden Klassen. Implementierung von ist ein. bildet ein hierarchisches

Mehr

C vs. C++ Sebastian Meyer. Proseminar C - Grundlagen und Konzepte. Universität Hamburg

C vs. C++ Sebastian Meyer. Proseminar C - Grundlagen und Konzepte. Universität Hamburg C vs. C++ Sebastian Meyer Universität Hamburg Proseminar C - Grundlagen und Konzepte 2013 1 / 31 Gliederung 1 Einführung 2 Vergleich der Spracheigenschaften 3 Neue Sprachelemente in C++ 4 Fazit 5 Zusammenfassung

Mehr

Angewandte Mathematik und Programmierung

Angewandte Mathematik und Programmierung Angewandte Mathematik und Programmierung Einführung in das Konzept der objektorientierten Anwendungen zu mathematischen Rechnens WS 2013/14 Die Vererbung ermöglicht es, neue Klassen auf der Basis von schon

Mehr

Methoden. von Objekten definiert werden, Methoden,, Zugriffsmethoden und Read-Only

Methoden. von Objekten definiert werden, Methoden,, Zugriffsmethoden und Read-Only Methoden Wie Konstruktoren und Destruktoren zum Auf- und Abbau von Objekten definiert werden, Wie inline-methoden Methoden,, Zugriffsmethoden und Read-Only Only- Methoden einzusetzen sind, Der this-pointer

Mehr

Grundlagen der Informatik. Prof. Dr. Stefan Enderle NTA Isny

Grundlagen der Informatik. Prof. Dr. Stefan Enderle NTA Isny Grundlagen der Informatik Prof. Dr. Stefan Enderle NTA Isny 2 Datenstrukturen 2.1 Einführung Syntax: Definition einer formalen Grammatik, um Regeln einer formalen Sprache (Programmiersprache) festzulegen.

Mehr

PIWIN 1 Übung Blatt 5

PIWIN 1 Übung Blatt 5 Fakultät für Informatik Wintersemester 2008 André Gronemeier, LS 2, OH 14 Raum 307, andre.gronemeier@cs.uni-dortmund.de PIWIN 1 Übung Blatt 5 Ausgabedatum: 19.12.2008 Übungen: 12.1.2009-22.1.2009 Abgabe:

Mehr

Java Einführung Methoden in Klassen

Java Einführung Methoden in Klassen Java Einführung Methoden in Klassen Lehrziel der Einheit Methoden Signatur (=Deklaration) einer Methode Zugriff/Sichtbarkeit Rückgabewerte Parameter Aufruf von Methoden (Nachrichten) Information Hiding

Mehr

Die Bedeutung abstrakter Datentypen in der objektorientierten Programmierung. Klaus Kusche, September 2014

Die Bedeutung abstrakter Datentypen in der objektorientierten Programmierung. Klaus Kusche, September 2014 Die Bedeutung abstrakter Datentypen in der objektorientierten Programmierung Klaus Kusche, September 2014 Inhalt Ziel & Voraussetzungen Was sind abstrakte Datentypen? Was kann man damit grundsätzlich?

Mehr

Von der UML nach C++

Von der UML nach C++ 22 Von der UML nach C++ Dieses Kapitel behandelt die folgenden Themen: Vererbung Interfaces Assoziationen Multiplizität Aggregation Komposition Die Unified Modeling Language (UML) ist eine weit verbreitete

Mehr

Wintersemester Maschinenbau und Kunststofftechnik. Informatik. Tobias Wolf http://informatik.swoke.de. Seite 1 von 22

Wintersemester Maschinenbau und Kunststofftechnik. Informatik. Tobias Wolf http://informatik.swoke.de. Seite 1 von 22 Kapitel 19 Vererbung, UML Seite 1 von 22 Vererbung - Neben der Datenabstraktion und der Datenkapselung ist die Vererbung ein weiteres Merkmal der OOP. - Durch Vererbung werden die Methoden und die Eigenschaften

Mehr

Objektorientierte Programmierung. Kapitel 12: Interfaces

Objektorientierte Programmierung. Kapitel 12: Interfaces 12. Interfaces 1/14 Objektorientierte Programmierung Kapitel 12: Interfaces Stefan Brass Martin-Luther-Universität Halle-Wittenberg Wintersemester 2012/13 http://www.informatik.uni-halle.de/ brass/oop12/

Mehr

Perzentile mit Hadoop ermitteln

Perzentile mit Hadoop ermitteln Perzentile mit Hadoop ermitteln Ausgangspunkt Ziel dieses Projektes war, einen Hadoop Job zu entwickeln, der mit Hilfe gegebener Parameter Simulationen durchführt und aus den Ergebnissen die Perzentile

Mehr

PIWIN I. Praktische Informatik für Wirtschaftsmathematiker, Ingenieure und Naturwissenschaftler I. Vorlesung 3 SWS WS 2007/2008

PIWIN I. Praktische Informatik für Wirtschaftsmathematiker, Ingenieure und Naturwissenschaftler I. Vorlesung 3 SWS WS 2007/2008 PIWIN I Kap. 7 Objektorientierte Programmierung - Einführung 1 PIWIN I Praktische Informatik für Wirtschaftsmathematiker, Ingenieure und Naturwissenschaftler I Vorlesung 3 SWS WS 2007/2008 FB Informatik

Mehr

Objektorientierte Programmierung. Objektorientierte Programmierung. Klasse. Objekt. Beispiel: Sportfest1. Methode. Eine Einführung mit BlueJ

Objektorientierte Programmierung. Objektorientierte Programmierung. Klasse. Objekt. Beispiel: Sportfest1. Methode. Eine Einführung mit BlueJ Objektorientierte Programmierung Objektorientierte Programmierung Eine Einführung mit BlueJ stellt die Daten, ihre Struktur und ihre Beziehungen zueinander in den Vordergrund. Weniger im Blickpunkt: die

Mehr

Alltagsnotizen eines Softwareentwicklers

Alltagsnotizen eines Softwareentwicklers Alltagsnotizen eines Softwareentwicklers Entkoppeln von Objekten durch Callbacks mit c++-interfaces oder boost.function und boost.bind Tags: c++, entkoppeln, objekt, oop, callback, boost.bind, boost.function,

Mehr

Meeting C++ C++11 R-Value Referenzen

Meeting C++ C++11 R-Value Referenzen Meeting C++ Detlef Wilkening http://www.wilkening-online.de 09.11.2012 Inhalt Motivation L-Values und R-Values R-Value Referenzen Move Semantik std::move Funktionen mit R-Value-Referenz Parametern Fazit

Mehr

Verhindert, dass eine Methode überschrieben wird. public final int holekontostand() {...} public final class Girokonto extends Konto {...

Verhindert, dass eine Methode überschrieben wird. public final int holekontostand() {...} public final class Girokonto extends Konto {... PIWIN I Kap. 8 Objektorientierte Programmierung - Vererbung 31 Schlüsselwort: final Verhindert, dass eine Methode überschrieben wird public final int holekontostand() {... Erben von einer Klasse verbieten:

Mehr

Willkommen zur Vorlesung. Objektorientierte Programmierung Vertiefung - Java

Willkommen zur Vorlesung. Objektorientierte Programmierung Vertiefung - Java Willkommen zur Vorlesung Objektorientierte Programmierung Vertiefung - Java Zum Dozenten Mein Name: Andreas Berndt Diplom-Informatiker (TU Darmstadt) Derzeit Software-Entwickler für Web- Applikationen

Mehr

Kapitel 3 Das Projekt Bankkonto Seite 1

Kapitel 3 Das Projekt Bankkonto Seite 1 Kapitel 3 Das Projekt Bankkonto Seite 1 3 Das Projekt Bankkonto Nun wirst du dich etwas gründlicher mit dem Quelltext einer Klasse beschäftigen. Du lernst, wie zwei Objekte eine gemeinsame Aufgabe erledigen.

Mehr

Tutorium Java Ein Überblick. Helge Janicke

Tutorium Java Ein Überblick. Helge Janicke Tutorium Java Ein Überblick Helge Janicke 26. Oktober 2000 1 VORRAUSSETZUNGEN ZUM PROGRAMMIEREN MIT JAVA. 1 1 Vorraussetzungen zum Programmieren mit Java. Was braucht man, wenn man mit Java programmieren

Mehr

Javakurs 2013 Objektorientierung

Javakurs 2013 Objektorientierung Javakurs 2013 Objektorientierung Objektorientierte Programmierung I Armelle Vérité 7 März 2013 Technische Universität Berlin This work is licensed under the Creative Commons Attribution-ShareAlike 3.0

Mehr

Java Kurs für Anfänger Einheit 5 Methoden

Java Kurs für Anfänger Einheit 5 Methoden Java Kurs für Anfänger Einheit 5 Methoden Ludwig-Maximilians-Universität München (Institut für Informatik: Programmierung und Softwaretechnik von Prof.Wirsing) 22. Juni 2009 Inhaltsverzeichnis Methoden

Mehr

Klassenattribute und -methoden, Vererbung

Klassenattribute und -methoden, Vererbung Klassenattribute und -methoden, Vererbung Michael Dienert 27. März 2002 1 Prüfungsaufgabe Anwendungsentwicklung Winter 2001 Die folgende Aufgabe stammt aus der Abschlussprüfung für Fachinformatiker Anwendungsentwicklung

Mehr

Objektorientierte Programmierung für Anfänger am Beispiel PHP

Objektorientierte Programmierung für Anfänger am Beispiel PHP Objektorientierte Programmierung für Anfänger am Beispiel PHP Johannes Mittendorfer http://jmittendorfer.hostingsociety.com 19. August 2012 Abstract Dieses Dokument soll die Vorteile der objektorientierten

Mehr

Programmieren - Vererbung & Polymorphie

Programmieren - Vererbung & Polymorphie Programmieren - Vererbung & Polymorphie Reiner Nitsch r.nitsch@fbi.h-da.de Vererbung - Was ist das? Vererbung ist ein wichtiges Konzept zur Unterstützung der Wiederverwendbarkeit, wenn auch nicht das Wichtigste.

Mehr

Erwin Grüner 15.12.2005

Erwin Grüner 15.12.2005 FB Psychologie Uni Marburg 15.12.2005 Themenübersicht Mit Hilfe der Funktionen runif(), rnorm() usw. kann man (Pseudo-) erzeugen. Darüber hinaus gibt es in R noch zwei weitere interessante Zufallsfunktionen:

Mehr

Jetzt sollt ihr von der Vorlage der Grundversion 1.0 ein eigenes Textadventure erstellen.

Jetzt sollt ihr von der Vorlage der Grundversion 1.0 ein eigenes Textadventure erstellen. Teil B: Erweiterungen Jetzt sollt ihr von der Vorlage der Grundversion 1.0 ein eigenes Textadventure erstellen. Die folgenden Aufgaben und Ausführungen geben einige Hilfestellungen, welche (mindestens

Mehr

Inhalt: Version 1.7.5

Inhalt: Version 1.7.5 Inhalt: Objekte ohne Methoden Objekte mit einfachen Methoden Objekte und Methoden mit Parametern Objekte und Methoden mit Rückgabewert Objekte mit einem Array als Attribut Beziehungen zwischen Objekten

Mehr

Grundlagen. Kapitel 1

Grundlagen. Kapitel 1 Grundlagen Dieses Kapitel umfasst grundlegende Fragen und Aufgaben zur Erstellung von C++-Programmen. Hierzu zählen auch das Inkludieren von Header-Dateien Eine Header-Datei beinhaltet Informationen, die

Mehr

Programmieren. 10. Tutorium 4./ 5. Übungsblatt Referenzen

Programmieren. 10. Tutorium 4./ 5. Übungsblatt Referenzen Programmieren 10. Tutorium 4./ 5. Übungsblatt Inhalt I. Übungsblatt 4 II. III. - Rückgabe und Besprechung - Vorbereitung auf Wiederholung/ Nachtrag - Operatorpräzedenzen IV. Übungsblatt 5 - Vorstellung

Mehr

WHERE Klausel Generierung mit.net und Oracle. Aus unserer Projekterfahrung und Architektur-Kurs

WHERE Klausel Generierung mit.net und Oracle. Aus unserer Projekterfahrung und Architektur-Kurs Betrifft Art der Info Quelle WHERE Klausel Generierung mit.net und Oracle Technical Info Aus unserer Projekterfahrung und Architektur-Kurs Where ist the WHERE? Der Artikel untersucht die Möglichkeiten,

Mehr

3 Berechnungen und Variablen

3 Berechnungen und Variablen 3 Berechnungen und Variablen Du hast Python installiert und weißt, wie man die Python-Shell startet. Jetzt kannst Du etwas damit machen. Wir fangen mit ein paar einfachen Berechnungen an und wenden uns

Mehr

Objekt-Orientierte Programmierung

Objekt-Orientierte Programmierung Objekt-Orientierte Programmierung Ein OO-Programm modelliert eine Anwendung als eine Welt von Objekten, die miteinander in Beziehung stehen ( später). Ein Objekt kann andere Objekte erzeugen. Ein Objekt

Mehr

C# - PROGRAMME MIT PLUGINS ERWEITERN

C# - PROGRAMME MIT PLUGINS ERWEITERN C# - PROGRAMME MIT PLUGINS ERWEITERN Schreibt man ein Programm welches erweiterbar sein soll, dann gibt es häufig mehrere Möglichkeiten dies umzusetzen. Die Objektorientierung ist dabei der erste Schritt,

Mehr

Java: Vererbung. Teil 3: super() www.informatikzentrale.de

Java: Vererbung. Teil 3: super() www.informatikzentrale.de Java: Vererbung Teil 3: super() Konstruktor und Vererbung Kindklasse ruft SELBSTSTÄNDIG und IMMER zuerst den Konstruktor der Elternklasse auf! Konstruktor und Vererbung Kindklasse ruft SELBSTSTÄNDIG und

Mehr

C++-Zusammenfassung. H. Schaudt. August 18, 2005

C++-Zusammenfassung. H. Schaudt. August 18, 2005 C++-Zusammenfassung H. Schaudt August 18, 2005 1 Datentypen 1.1 Grunddatentypen int (-32xxx bis +32xxx, implementerungs-abhängig) char -128 bis +128 float double bool (C++) int und char sind austauschbar:

Mehr

White Paper. Embedded Treiberframework. Einführung

White Paper. Embedded Treiberframework. Einführung Embedded Treiberframework Einführung White Paper Dieses White Paper beschreibt die Architektur einer Laufzeitumgebung für Gerätetreiber im embedded Umfeld. Dieses Treiberframework ist dabei auf jede embedded

Mehr

Seminararbeit Ruby Uno Kartenspiel

Seminararbeit Ruby Uno Kartenspiel Seminararbeit Ruby Uno Kartenspiel Autor: Fabian Merki Fabian Merki 05.11.2006 1 von 10 Inhaltsverzeichnis Einleitung... 3 Die Idee... 4 Design und Implementierung in Ruby... 5 Testing... 7 Startbefehle...

Mehr

Moderne parallele Rechnerarchitekturen

Moderne parallele Rechnerarchitekturen Seminar im WS0708 Moderne parallele Rechnerarchitekturen Prof. Sergei Gorlatch Dipl.-Inf. Maraike Schellmann schellmann@uni-muenster.de Einsteinstr. 62, Raum 710, Tel. 83-32744 Dipl.-Inf. Philipp Kegel

Mehr

Programmieren. Wie entsteht ein Programm

Programmieren. Wie entsteht ein Programm Wie entsteht ein Programm 1/9 1. Schritt: Programmentwurf Der wichtigste Teil beim Erstellen eines Programms ist der Programmentwurf. Dabei wird das vorgegebene Problem analysiert, es wird ermittelt, welche

Mehr

Objektorientierte Programmierung mit Python Polymorphismus und Vererbung. Eltern

Objektorientierte Programmierung mit Python Polymorphismus und Vererbung. Eltern Objektorientierte Programmierung mit Python Polymorphismus und Vererbung Eltern Kind Kind Kind Kind Prinzipien der objektorientierten Programmierung Vererbung Strukturierung von Klassen. Oberbegriffe beschreiben

Mehr

Selbststudium OOP5 21.10.2011 Programmieren 1 - H1103 Felix Rohrer

Selbststudium OOP5 21.10.2011 Programmieren 1 - H1103 Felix Rohrer Kapitel 4.1 bis 4.3 1. zu bearbeitende Aufgaben: 4.1 4.1: done 2. Was verstehen Sie unter einem "Java-Package"? Erweiterungen verschiedener Klassen welche in Java benutzt werden können. 3. Sie möchten

Mehr

Drei-Schichten-Architektur. Informatik B - Objektorientierte Programmierung in Java. Vorlesung 16: 3-Schichten-Architektur 1 Fachkonzept - GUI

Drei-Schichten-Architektur. Informatik B - Objektorientierte Programmierung in Java. Vorlesung 16: 3-Schichten-Architektur 1 Fachkonzept - GUI Universität Osnabrück Drei-Schichten-Architektur 3 - Objektorientierte Programmierung in Java Vorlesung 6: 3-Schichten-Architektur Fachkonzept - GUI SS 2005 Prof. Dr. F.M. Thiesing, FH Dortmund Ein großer

Mehr

Programmieren I. Strategie zum Entwurf von Klassen. Beispiele. Design von Klassen. Dr. Klaus Höppner. Beispiel: Bibliothek

Programmieren I. Strategie zum Entwurf von Klassen. Beispiele. Design von Klassen. Dr. Klaus Höppner. Beispiel: Bibliothek Programmieren I Dr. Klaus Höppner Hochschule Darmstadt Wintersemester 2008/2009 1 / 22 2 / 22 Strategie zum Entwurf von Klassen Beispiele Objektorientierte Sichtweise: Mit welchen Objekten habe ich es

Mehr

Klausur Kommunikation I. Sommersemester 2003. Dipl.-Ing. T. Kloepfer

Klausur Kommunikation I. Sommersemester 2003. Dipl.-Ing. T. Kloepfer Kommunikation I 1 Klausur Kommunikation I Sommersemester 2003 Dipl.-Ing. T. Kloepfer Bearbeitungsinformationen Aufbau der Klausur Die Klausur ist wie folgt aufgebaut: Die Klausur ist in 18 Aufgaben unterteilt.

Mehr

5 Projekt Bankverwaltung

5 Projekt Bankverwaltung Kapitel 5 Bankverwaltung (Lösung) Seite 1/7 5 Projekt Bankverwaltung 5.1 Festlegen der Schnittstelle Bevor du mit der Programmierung beginnst, musst du dir einige Gedanken über die Schnittstelle zwischen

Mehr

Institut für Informatik

Institut für Informatik Technische Universität München Institut für Informatik Lehrstuhl für Computer Graphik & Visualisierung WS 2010 Praktikum: Grundlagen der Programmierung Lösungsblatt 7 Prof. R. Westermann, A. Lehmann, R.

Mehr

1. Java ist... 2. Stammbaum der Programmiersprachen 3. Die "Softwarekrise"

1. Java ist... 2. Stammbaum der Programmiersprachen 3. Die Softwarekrise im Überblick im Überblick Inhalt 1. Java ist... 2. Stammbaum der Programmiersprachen 3. Die Softwarekrise 1. Merkmale von Software 2. Fortlaufende Veränderungen 3. Erschwerte Rahmenbedingungen bei der

Mehr

C++ - Operatoren. Eigene Klassen mit neuen Funktionen

C++ - Operatoren. Eigene Klassen mit neuen Funktionen C++ - Operatoren Eigene Klassen mit neuen Funktionen Übersicht Klassen bisher Eigene Operatoren definieren 2 Bisher Durch Kapselung, Vererbung und Polymorphy können nun eigene Klassen definiert werden,

Mehr

7 Dateien und Datenströme (Streams)

7 Dateien und Datenströme (Streams) 7 Dateien und Datenströme (Streams) Jörn Loviscach Versionsstand: 21. März 2014, 22:57 Die nummerierten Felder sind absichtlich leer, zum Ausfüllen beim Ansehen der Videos: http://www.j3l7h.de/videos.html

Mehr

Programmieren in Java

Programmieren in Java Programmieren in Java objektorientierte Programmierung 2 2 Zusammenhang Klasse-Datei In jeder *.java Datei kann es genau eine public-klasse geben wobei Klassen- und Dateiname übereinstimmen. Es können

Mehr

Objective-C CheatSheet

Objective-C CheatSheet App-Templates: Erstellt automatisch einen Navigation Controller mit editierbarem UITableView und DetailView, der bei Klick auf einzelne UITableViewCell angezeigt wird. Kreiert einen GLKitViewController

Mehr

5.6 Vererbung. Vererbung

5.6 Vererbung. Vererbung 5.6 Vererbung Klassen können zueinander in einer "ist ein"- Beziehung stehen Beispiel: Jeder PKW ist ein Kraftfahrzeug, jedes Kraftfahrzeug ist ein Transportmittel aber: auch jeder LKW ist ein Kraftfahrzeug

Mehr

MGE Datenanbindung in GeoMedia

MGE Datenanbindung in GeoMedia TIPPS & TRICKS MGE Datenanbindung in GeoMedia 10. September 2002 / AHU INTERGRAPH (Schweiz) AG Neumattstrasse 24, CH 8953 Dietikon Tel: 043 322 46 46 Fax: 043 322 46 10 HOTLINE: Telefon: 043 322 46 00

Mehr

Aufgabenstellung und Zielsetzung

Aufgabenstellung und Zielsetzung Aufgabenstellung und Zielsetzung In diesem Szenario werden Sie eine Bestellung, vorliegend im XML-Format, über einen Web-Client per HTTP zum XI- System senden. Dort wird die XML-Datei mittels eines HTTP-Interfaces

Mehr

Grundlagen Programmierung

Grundlagen Programmierung 1. Aufgabe (Spielen mit Objekten) Gegeben sei der auch von der Veranstaltungsseite erhältliche Programmcode auf der rechten Seite, der im Detail zuerst nicht verstanden werden muss. a) Erzeugen Sie sich

Mehr

Javadoc. Programmiermethodik. Eva Zangerle Universität Innsbruck

Javadoc. Programmiermethodik. Eva Zangerle Universität Innsbruck Javadoc Programmiermethodik Eva Zangerle Universität Innsbruck Überblick Einführung Java Ein erster Überblick Objektorientierung Vererbung und Polymorphismus Ausnahmebehandlung Pakete und Javadoc Spezielle

Mehr

Autor: Michael Spahn Version: 1.0 1/10 Vertraulichkeit: öffentlich Status: Final Metaways Infosystems GmbH

Autor: Michael Spahn Version: 1.0 1/10 Vertraulichkeit: öffentlich Status: Final Metaways Infosystems GmbH Java Einleitung - Handout Kurzbeschreibung: Eine kleine Einführung in die Programmierung mit Java. Dokument: Autor: Michael Spahn Version 1.0 Status: Final Datum: 23.10.2012 Vertraulichkeit: öffentlich

Mehr

Einsatz von UML und C++ am Beispiel einer Satelliten-Lageregelungssoftware

Einsatz von UML und C++ am Beispiel einer Satelliten-Lageregelungssoftware Einsatz von UML und C++ am Beispiel einer Satelliten-Lageregelungssoftware Dipl. Inform. Olaf Maibaum DLR, Abt. Simulations- und Softwaretechnik DLR, Abt. Simulations- und Softwaretechnik 1 Übersicht Bird-Satellit

Mehr

Arbeiten mit Arrays. 4.1 Eigenschaften. 4.1.1 Schlüssel und Element. Kapitel 4

Arbeiten mit Arrays. 4.1 Eigenschaften. 4.1.1 Schlüssel und Element. Kapitel 4 Arbeiten mit s Eine effiziente Programmierung mit PHP ohne seine s ist kaum vorstellbar. Diese Datenstruktur muss man verstanden haben, sonst brauchen wir mit weitergehenden Programmiertechniken wie der

Mehr

Einführung in das Microsoft.NET-Framework. Programmiersprache C# MEF Das Managed Extensibility Framework. André Kunz

Einführung in das Microsoft.NET-Framework. Programmiersprache C# MEF Das Managed Extensibility Framework. André Kunz Einführung in das Microsoft.NET-Framework Programmiersprache C# MEF Das Managed Extensibility Framework André Kunz 21.09.2010 1 In dieser Einführung bekommen Sie einen kurzen Einstieg in das.net-framework

Mehr

C# im Vergleich zu Java

C# im Vergleich zu Java C# im Vergleich zu Java Serhad Ilgün Seminar Universität Dortmund SS 03 Gliederung Entstehung von C# und Java Überblick von C# und Java Unterschiede und Gemeinsamkeiten Zusammenfassung und Ausblick Entstehung

Mehr

Python Programmierung. Dipl.-Ing.(FH) Volker Schepper

Python Programmierung. Dipl.-Ing.(FH) Volker Schepper Python Programmierung Kontaktdaten Homepage: http://wwwlehre.dhbw-stuttgart.de/~schepper/ Email: Volker. Schepper [A@T] yahoo.de Vorlesung Skriptsprachen Vorlesung: 06.03.2013 13.03.2013 20.03.2013 27.03.2013

Mehr

Programmieren in C. Operatoren, Variablen und deren Sichtbarkeit. Prof. Dr. Nikolaus Wulff

Programmieren in C. Operatoren, Variablen und deren Sichtbarkeit. Prof. Dr. Nikolaus Wulff Programmieren in C Operatoren, Variablen und deren Sichtbarkeit Prof. Dr. Nikolaus Wulff Auswertung von Ausdrücken Was passiert wenn ein Ausdruck wie z. B. int y,x=2; y = ++x * x++; im Computer abgearbeitet

Mehr

C++ Grundlagen. ++ bedeutet Erweiterung zum Ansi C Standard. Hier wird eine Funktion eingeleitet

C++ Grundlagen. ++ bedeutet Erweiterung zum Ansi C Standard. Hier wird eine Funktion eingeleitet C++ Grundlagen ++ bedeutet Erweiterung zum Ansi C Standard Hier wird eine Funktion eingeleitet Aufbau: In dieser Datei stehen die Befehle, die gestartet werden, wenn das Programm gestartet wird Int main()

Mehr

Fortgeschrittene Servlet- Techniken. Ralf Gitzel ralf_gitzel@hotmail.de

Fortgeschrittene Servlet- Techniken. Ralf Gitzel ralf_gitzel@hotmail.de Fortgeschrittene Servlet- Techniken Ralf Gitzel ralf_gitzel@hotmail.de 1 Themenübersicht Ralf Gitzel ralf_gitzel@hotmail.de 2 Übersicht Servlet Initialisierung Attribute und Gültigkeitsbereiche Sessions

Mehr

C++ - Einführung in die Programmiersprache Objektorientierte Programmierung

C++ - Einführung in die Programmiersprache Objektorientierte Programmierung C++ - Einführung in die Programmiersprache Objektorientierte Programmierung hat Kanten hat eine Farbe hat eine Kantenfarbe Rechteck zeichnen Rechteck einfärben Rechteck drehen Modulare Programmierung Projekt

Mehr

Bedienung von BlueJ. Klassenanzeige

Bedienung von BlueJ. Klassenanzeige Im Folgenden werden wichtige Funktionen für den Einsatz von BlueJ im Unterricht beschrieben. Hierbei wird auf den Umgang mit Projekten, Klassen und Objekten eingegangen. Abgeschlossen wird dieses Dokument

Mehr

7.4 Analyse anhand der SQL-Trace. 7.3.5 Vorabanalyse mit dem Code Inspector

7.4 Analyse anhand der SQL-Trace. 7.3.5 Vorabanalyse mit dem Code Inspector 7.4 Analyse anhand der SQL-Trace 337 7.3.5 Vorabanalyse mit dem Code Inspector Der Code Inspector (SCI) wurde in den vorangegangenen Kapiteln immer wieder erwähnt. Er stellt ein paar nützliche Prüfungen

Mehr

Informatik 2 Labor 2 Programmieren in MATLAB Georg Richter

Informatik 2 Labor 2 Programmieren in MATLAB Georg Richter Informatik 2 Labor 2 Programmieren in MATLAB Georg Richter Aufgabe 3: Konto Um Geldbeträge korrekt zu verwalten, sind zwecks Vermeidung von Rundungsfehlern entweder alle Beträge in Cents umzuwandeln und

Mehr

Ziel, Inhalt. Programmieren in C++ Wir lernen wie man Funktionen oder Klassen einmal schreibt, so dass sie für verschiedene Datentypen verwendbar sind

Ziel, Inhalt. Programmieren in C++ Wir lernen wie man Funktionen oder Klassen einmal schreibt, so dass sie für verschiedene Datentypen verwendbar sind Templates und Containerklassen Ziel, Inhalt Wir lernen wie man Funktionen oder Klassen einmal schreibt, so dass sie für verschiedene Datentypen verwendbar sind Templates und Containerklassen 1 Ziel, Inhalt

Mehr

3. Konzepte der objektorientierten Programmierung

3. Konzepte der objektorientierten Programmierung 3. Konzepte der objektorientierten Programmierung 3.1 Basiskonzepte 3.2 Generalisierung / Spezialisierung 3.3 Aggregation 3.4 Assoziation 3.5 Nachrichten 3.6 Polymorphismus 3. Konzepte der Objektorientierung

Mehr

Übungspaket 19 Programmieren eigener Funktionen

Übungspaket 19 Programmieren eigener Funktionen Übungspaket 19 Programmieren eigener Funktionen Übungsziele: Skript: 1. Implementierung und Kodierung eigener Funktionen 2. Rekapitulation des Stack-Frames 3. Parameterübergabe mittels Stack und Stack-Frame

Mehr

Objektorientierte Programmierung

Objektorientierte Programmierung 1. Begriffe Objektorientierte Programmierung mit C++ Prozedurale Programmierung Sprachen: C, Pascal, Cobol, Basic,... Objektorientierte Programmierung Sprachen: C++, C#, Java... Methode: - Gesamtproblem

Mehr

ABB i-bus KNX. Software-Information. Melde- und Bedientableau. Typ: MT 701.2

ABB i-bus KNX. Software-Information. Melde- und Bedientableau. Typ: MT 701.2 Produkt: Melde- und Bedientableau Typ: MT 701.2 Aktuelles Anwendungsprogramm Plug-In für ETS 2 MT_701_2_ETS2_SOW_xx_V1-12a_de_en.exe Plug-In für ETS 3 MT_701_2_ETS3_SOW_xx_V1-12a_de_en.exe EIBTAB: MT_701_2_EIBTAB_SOW_de_V2-08-00A_EibTab+Firmware.EXE

Mehr

Objektorientierung: Klassen und Objekte

Objektorientierung: Klassen und Objekte Objektorientierung: Klassen und Objekte Klasse: Beschreibung für eine Menge von Objekten Schablone, Bauplan abstrakte Form Objekt: Instanz einer Klasse konkreter Inhalt (Werte) Klassen bestehen aus Attributen

Mehr

Programmierkurs: Delphi: Einstieg

Programmierkurs: Delphi: Einstieg Seite 1 von 6 Programmierkurs: Delphi: Einstieg Aus Wikibooks Inhaltsverzeichnis 1 Einstieg Einstieg Was ist Delphi Borland Delphi ist eine RAD-Programmierumgebung von Borland. Sie basiert auf der Programmiersprache

Mehr

Prinzipien Objektorientierter Programmierung

Prinzipien Objektorientierter Programmierung Prinzipien Objektorientierter Programmierung Valerian Wintner Inhaltsverzeichnis 1 Vorwort 1 2 Kapselung 1 3 Polymorphie 2 3.1 Dynamische Polymorphie...................... 2 3.2 Statische Polymorphie........................

Mehr

Dokumentation zum Projekt Mail-Adapter in SAP PI. 17.01.2011 Sinkwitz, Sven 519707 Theel, Thomas 519696

Dokumentation zum Projekt Mail-Adapter in SAP PI. 17.01.2011 Sinkwitz, Sven 519707 Theel, Thomas 519696 Dokumentation zum Projekt Mail-Adapter in SAP PI 17.01.2011 Sinkwitz, Sven 519707 Theel, Thomas 519696 Inhalt 1. Einleitung... 2 2. Vorgehen... 3 1. Datentyp für die Mail einrichten... 3 2. Message Typen

Mehr

RAID. Name: Artur Neumann

RAID. Name: Artur Neumann Name: Inhaltsverzeichnis 1 Was ist RAID 3 1.1 RAID-Level... 3 2 Wozu RAID 3 3 Wie werden RAID Gruppen verwaltet 3 3.1 Software RAID... 3 3.2 Hardware RAID... 4 4 Die Verschiedenen RAID-Level 4 4.1 RAID

Mehr

SmartExporter 2013 R1

SmartExporter 2013 R1 Die aktuelle Version wartet mit zahlreichen neuen Features und umfangreichen Erweiterungen auf. So können mit SmartExporter 2013 R1 nun auch archivierte Daten extrahiert und das Herunterladen der Daten

Mehr

Praktikum Internetprotokolle - POP3

Praktikum Internetprotokolle - POP3 Technische Universität Ilmenau Fakultät für Informatik und Automatisierung Institut für Praktische Informatik und Medieninformatik Fachgebiet Telematik/Rechnernetze 19. Mai 2008 1 Aufgabenstellung Praktikum

Mehr

Grundlagen der Parallelisierung

Grundlagen der Parallelisierung Grundlagen der Parallelisierung Philipp Kegel, Sergei Gorlatch AG Parallele und Verteilte Systeme Institut für Informatik Westfälische Wilhelms-Universität Münster 3. Juli 2009 Inhaltsverzeichnis 1 Einführung

Mehr

SEP 114. Design by Contract

SEP 114. Design by Contract Design by Contract SEP 114 Design by Contract Teile das zu entwickelnde Programm in kleine Einheiten (Klassen, Methoden), die unabhängig voneinander entwickelt und überprüft werden können. Einheiten mit

Mehr

C A R L V O N O S S I E T Z K Y. Boost C++ Libraries. Johannes Diemke. Department of Computer Science Learning and Cognitive Systems

C A R L V O N O S S I E T Z K Y. Boost C++ Libraries. Johannes Diemke. Department of Computer Science Learning and Cognitive Systems C A R L V O N O S S I E T Z K Y Boost C++ Libraries Johannes Diemke Department of Computer Science Learning and Cognitive Systems Grundlagen Freie von Experten begutachtete, portable C++ Bibliothek Nützlich,

Mehr

Das Studiengangsinformationssystem (SGIS)

Das Studiengangsinformationssystem (SGIS) Das Studiengangsinformationssystem (SGIS) Manual für Typo3-Redakteure Version 1.a Mai 2015 Kontakt: Referat 1.4 - Allgemeine Studienberatung und Career Service Christian Birringer, christian.birringer@uni-rostock.de

Mehr

Der Aufruf von DM_in_Euro 1.40 sollte die Ausgabe 1.40 DM = 0.51129 Euro ergeben.

Der Aufruf von DM_in_Euro 1.40 sollte die Ausgabe 1.40 DM = 0.51129 Euro ergeben. Aufgabe 1.30 : Schreibe ein Programm DM_in_Euro.java zur Umrechnung eines DM-Betrags in Euro unter Verwendung einer Konstanten für den Umrechnungsfaktor. Das Programm soll den DM-Betrag als Parameter verarbeiten.

Mehr

Selbstbestimmtes Lernen. Proinformatik III Objektorientierte Programmierung. Format. Inhalt. Buzzwords

Selbstbestimmtes Lernen. Proinformatik III Objektorientierte Programmierung. Format. Inhalt. Buzzwords 4.0 Proinformatik III Objektorientierte Programmierung Michael Kölling University of Kent Canterbury, UK Selbstbestimmtes Lernen Vorlesung Tutorium Übungen Buch Web-Seite Üben, üben, üben! Format Vorlesung:

Mehr

Name: ES2 Klausur Thema: ARM 25.6.07. Name: Punkte: Note:

Name: ES2 Klausur Thema: ARM 25.6.07. Name: Punkte: Note: Name: Punkte: Note: Hinweise für das Lösen der Aufgaben: Zeit: 95 min. Name nicht vergessen! Geben Sie alle Blätter ab. Die Reihenfolge der Aufgaben ist unabhängig vom Schwierigkeitsgrad. Erlaubte Hilfsmittel

Mehr

KURZANLEITUNG CLOUD BLOCK STORAGE

KURZANLEITUNG CLOUD BLOCK STORAGE KURZANLEITUNG CLOUD BLOCK STORAGE Version 1.12 01.07.2014 SEITE _ 2 INHALTSVERZEICHNIS 1. Einleitung......Seite 03 2. Anlegen eines dauerhaften Block Storage...Seite 04 3. Hinzufügen von Block Storage

Mehr

Einführung in die Informatik: Programmierung und Software-Entwicklung, WS 11/12. Kapitel 7. Grafische Benutzeroberflächen

Einführung in die Informatik: Programmierung und Software-Entwicklung, WS 11/12. Kapitel 7. Grafische Benutzeroberflächen 1 Kapitel 7 Ziele 2 (Graphical User Interfaces) als Anwendungsbeispiel für die objektorientierte Programmierung kennenlernen Benutzung von Vererbung zur Erstellung individueller GUI-Klassen durch Erweiterung

Mehr