Archived: Fehlerbehebung in Multicore-Anwendungen mit dem Real-Time Execution Trace Toolkit

NI does not actively maintain this document.

This content provides support for older products and technology, so you may notice outdated links or obsolete information about operating systems or other relevant products.

Überblick

Dieses Tutorium gehört zur Reihe "Grundlagen der Multicore-Programmierung" von NI
Grundlagen der Multicore-Programmierung
Die Fehlerbehebung ist die zeitaufwändigste Phase in der Softwareentwicklung [1]. Bei einer Umfrage auf der Embedded Systems Conference 2007 sagten 56 Prozent der Teilnehmer aus, dass die Fehlerbeseitigung der zeitraubendste Aspekt ihrer Arbeit sei. 59 Prozent sagten, die von ihnen verwendeten Werkzeuge zur Fehlerbehebung unterstützten keine Multicore- oder Multiprozessor-Entwicklung [2]. In diesem Whitepaper wird die Fehlerbehebung in Multicore-Anwendungen näher diskutiert.

Inhalt

Einleitung

 Multithreading wird bereits seit Mitte der 1990er von NI LabVIEW unterstützt. Aufgrund der Einführung von CPUs mit mehreren Kernen können Entwickler jetzt die Fähigkeiten dieser Technologie mit Hilfe von LabVIEW voll ausnutzen. Paralleles Programmieren wirft neue Herausforderungen auf, wenn es um die Entwicklung von Anwendungen für CPUs mit mehreren Kernen geht. Dazu gehören beispielsweise die Synchronisation simultanen Zugriffs mehrerer Threads auf gemeinsam genutzten Speicherbereich sowie die Zuordnung von Prozessorressourcen. LabVIEW verarbeitet die meisten Multithreading-Tasks automatisch, überlässt es aber dem Anwender, Threads an CPUs (oder Cores derselben CPU) zuzuweisen.

Bei der Entwicklung einer Echtzeitanwendung werden detaillierte CPU-Nutzung und andere Ereignisse am besten durch Analyse der Programmausführung auf den Echtzeitzielgeräten überwacht. Das Real-Time Execution Trace Toolkit ermöglicht die Ansicht und Analyse der Programmausführung von Echtzeitaufgaben, darunter Threads für virtuelle Instrumente (VIs) und Betriebssysteme. Das Real-Time Execution Trace Toolkit ist ein Add-on für LabVIEW Real-Time 7.1.1 (und später) sowie LabWindows/CVI Real-Time 8.5 (und später). Es umfasst zwei Bestandteile: die Instrumentation VIs und das Trace Viewing Utility. Die Instrumentation VIs werden um den Programmcode herum eingefügt, dessen Ausführung verfolgt werden soll. Das Trace Viewing Utility wird zur Beobachtung der erfassten Programmausführung eingesetzt.

Der Zugriff auf Low-Level-Ausführungsinformationen ist für die Optimierung und die Fehlersuche bei Echtzeitanwendungen entscheidend, weil somit leicht Fehlerquellen aufgrund von Zuordnung von Prozessorressourcen, Speicherzuweisungen, Prioritätsvererbung oder Race Conditions identifiziert werden können.

Die Nutzung gemeinsamer Ressourcen durch mehrere Threads mit unterschiedlichem Prioritätsniveau kann zu unerwartetem Verhalten der Anwendung führen. In Tabelle 1 sind einige gemeinsame Ressourcen sowie die möglichen Problemen, die in einer Applikation auftreten können, aufgeführt zu sehen.

Gemeinsam genutzte Ressourcen

Mögliche Probleme

  • LabVIEW Memory Manager
  • Nicht ablaufinvariante, gemeinsam genutzte SubVIs
  • Globale Variablen
  • Dateisystem
  • Prioritätsumkehr
  • Unbegrenzte Prioritätsumkehr
  • Zerstörter Determinismus

Tabelle 1: Gemeinsam genutzte Ressourcen und zugehörige mögliche Probleme

In den nächsten zwei Abschnitten wird dargestellt, wie die Zuordnung von Prozessorressourcen vorgenommen und überwacht werden kann und wie mögliche Probleme mithilfe gemeinsamer Ressourcen behandelt werden können.

Zuordnung von Prozessorressourcen

In den folgenden zwei Screenshots ist die Überwachung der Ausführung eines Programms dargestellt, das auf einem echtzeitfähigen Embedded-Zielgerät mit mehreren CPU-Kernen ausgeführt wurde. Die parallele Programmierung in NI LabVIEW wurde mithilfe zweier Timed-Loop-Strukturen implementiert. Jede dieser zeitgesteuerten Schleifen (Timed Loop, TL) wurde einem anderen CPU-Kern zugewiesen. Der gesamte Code innerhalb einer bestimmten zeitgesteuerten Schleife wird auf demselben CUU-Kern ausgeführt. Die Zuordnung von Prozessorressourcen wird aufrechterhalten, bis das Programm die Ausführung abschließt. Abbildungen 1 und 2 zeigen deutlich die Zuordnung der einzelnen Threads. Threads, die zu einem bestimmten CPU-Kern gehören, sind markiert. Die übrigen Threads sind ausgegraut. Die parallele Ausführung von Threads, die auf separaten CPU-Kernen laufen, wird ebenfalls angezeigt.


[+] Bild vergrößern


Abb. 1: Diese Execution Trace zeigt Threads, die zur CPU 0 gehören. Die übrigen Threads sind ausgegraut.


[+] Bild vergrößern

Abb. 2: Diese Execution Trace zeigt Threads, die zur CPU 1 gehören.

Siehe auch:

Multitasking in LabVIEW

Gemeinsam genutzte Ressourcen

Die Verwendung gemeinsam genutzter Ressourcen durch einen zeitkritischen Thread (ein Thread, der innerhalb einer deterministischen Zeit ausgeführt werden muss) kann zu zusätzlichem Jitter in einer Echtzeitanwendung führen. Der LabVIEW Memory Manager ist eine dieser gemeinsam genutzten Ressourcen. Er ist zuständig für die dynamische Zuweisung von Speicherplatz.

Wenn ein Programm mit normaler Priorität den Memory Manager als Ressource verwendet, müssen die übrigen Threads, so auch der zeitkritische Thread, warten, bis die gemeinsam genutzte Ressource frei wird. In solchen Fällen kommt es unweigerlich zu zeitlichen Abweichungen im zeitkritischen Thread. Um dieses Problem zu lösen, setzt der Thread Scheduler für eine gewisse Zeit die Priorität der Anwendung von normal auf zeitkritisch, so dass sie im zeitkritischen Thread ausgeführt wird. Somit kann sie schneller abgeschlossen werden und den Memory Manager freigeben. Dieses Phänomen ist als Prioritätsvererbung oder Prioritätsumkehr bekannt. Um solche Situationen zu vermeiden, empfiehlt NI, möglichst keine gemeinsamen Ressourcen zu nutzen. Eine Lösung im Fall des Memory Manager wäre eine Vorbelegung von Speicher in Form von Arrays.

Die Problematik aufgrund von Prioritätsvererbung kann in manchen Fällen verbessert werden. Dazu werden zwei Methoden zum Prioritäts-Scheduling zusammen genutzt: auf VI-Ebene und auf Ebene der zeitgesteuerten Schleife. In Abbildung 3 sieht man beispielsweise ein zeitkritisches SubVI und eine zeitgesteuerte Schleife, die um dieselben Speicherressourcen kämpfen. Dieser Wettlauf um gemeinsam genutzte Ressourcen führt zu einer Prioritätsvererbung.

Abb. 3: Beispiel für den Einsatz von zwei unterschiedlichen Methoden für die Prioritätszuweisung

Aus Abbildung 4 wird ersichtlich, dass die zeitgesteuerte Schleife zuerst startete, dann wurde sie vom zeitkritischen SubVI unterbrochen. Die roten Fähnchen zeigen den Start und die orangefarbenen das Ende der Unterbrechung der zeitgesteuerten Schleife. Nachdem die Unterbrechung beendet war, übernahm die zeitgesteuerte Schleife einen Teil der Priorität vom zeitkritischen SubVI. Das wird bei der Programmausführung durch das orangefarbene Fähnchen gekennzeichnet. Damit solche Konflikte möglichst selten auftreten, sollten keine gemeinsam genutzten Ressourcen in zeitkritischen Threads verwendet werden. NI empfiehlt zudem, dass nur eine Methode zur Prioritätszuweisung genutzt wird: entweder SubVIs oder zeitgesteuerte Schleifen unterschiedlicher Prioritätsebenen.


[+] Bild vergrößern

Abb. 4: Unterbrechung der zeitgesteuerten Schleife und Prioritätsvererbung

Abbildung 5 stellt die Ausführung eines anderen Programms dar, das ein SubVI normaler Priorität und ein zeitkritisches SubVI beinhaltet, die eine Ressource teilen: den Memory Manager. Die grünen Fähnchen zeigen die Zuweisung des dynamischen Speichers und das orangefarbene die Prioritätsvererbung. Die Ausführung des zeitkritischen Threads wurde unterbrochen, damit die Priorität des SubVIs angehoben werden kann, um im zeitkritischen Thread ausgeführt zu werden und somit die gemeinsam genutzte Ressource schneller wieder freizugeben. Das könnte den Determinismus des zeitkritischen SubVIs negativ beeinflussen.


[+] Bild vergrößern

Abb. 5: Die grünen Fähnchen zeigen die Zuweisung dynamischen Speichers bzw. den Zugriff auf den Memory Manager. Das orangefarbene Fähnchen zeigt die Prioritätsvererbung.

Siehe auch:
Arrays für deterministische Schleifen vorbelegen

Gemeinsam genutzte Ressourcen und Prioritätsumkehrungen für deterministische Anwendungen vermeiden

Speicherverwaltung (Beispielcode) mit LabVIEW Real-Time

Zusammenhängende Speicherkonflikte (Real-Time Module) vermeiden

Gründe für den Einsatz des Real-Time Execution Trace Toolkit

  • Identifikation von Laufzeitproblemen in Anwendungen mit Einzel- und Multiprozessoren
  • Überwachung von gemeinsam genutzten Ressourcen und Speicherzuweisung
  • Verifikation des Timing-Verhaltens
  • Überwachung der CPU-Nutzung und Multicore-Interaktion
  • LabVIEW-Ausführungsmodell

Weitere Werkzeuge zur Fehlerbehebung

Es wird empfohlen, nicht nur das Real-Time Execution Trace Toolkit zu verwenden, sondern auch andere Standardwerkzeuge für die Fehlerbehebung, um eine entsprechende Anwendungsleistung zu gewährleisten.

Fehlerbehebungswerkzeuge auf VI-Ebene

  • Sonden, Haltepunkte, Highlight-Funktion
  • NI Timestamp VIs (RDTSC)
  • RT Debug String VI
  • Benutzercode

Fehlerbehebungswerkzeuge auf Systemebene

Literaturverzeichnis:

  1. Richard Goering, Embedded Developer Survey Reveals Debugging Challenges, EETimes, 11. Mai 2007
  2. Umfrage von Virtutech auf der Embedded Systems Conference 2007 mit 354 Teilnehmern