Schwerpunkte

Software-Entwicklung

Embedded-Linux-Systeme tracen – Teil 2

23. November 2020, 06:00 Uhr   |  von Mohammed Billoo

Embedded-Linux-Systeme tracen – Teil 2
© Roman Samborskyi | shutterstock.com

Ende Oktober starteten wir mit unserer Artikel-Reihe über das Tracen von Embedded-Linux-Systemen mit Tracealyzer 4.4. Im ersten Teil wurde die Leistung des Linux-Treibers erforscht. Im zweiten Beitrag zeigen wir, wie Tracealyzer die Leistungsfähigkeit eines IRQ Handlers gewährleistet.

Im ersten Teil der Artikel-Reihe wurde beschrieben, wie der von Percepio entwickelte Tracealyzer für Linux die Leistungsfähigkeit eines Linux-Treibers gewährleistet. Gab der erste Artikel einen Überblick über die von Tracealyzer gebotenen Ansichten für Analysezwecke, befasst sich der zweite Teil mit einem Kernbestandteil eines jeden Linux-Gerätetreibers, nämlich dem Interrupt Request (IRQ) Handler. Hierbei wird gezeigt, wie Tracealyzer Rückmeldungen bezüglich der Leistungsfähigkeit des Handlers liefert.

Grundsätzlich sollen Anwender in einem IRQ Handler möglichst wenig Operationen ausführen, da sich der Kernel, ebenso wie der gesamte Prozessor, in einem sensiblen Betriebszustand befindet. Unter anderem sind sämtliche Interrupts maskiert – das heißt es kann kein anderer Interrupt gesetzt werden – und der Linux-Scheduler wird effektiv angehalten, solange der IRQ-Handler läuft. Deshalb muss ein Nutzer gewährleisten, dass für die Interrupt-Behandlung lediglich das absolute Minimum an Operationen ausgeführt wird. Beispielsweise Registeroperationen und das Verschieben von Daten im Speicher des Prozessors, während das Management der eigentlichen Datentransfer-Operation anderen Kernel-Mechanismen wie einem Tasklet übertragen wird. In dem vorliegenden Beispiel besagt die Gerätespezifikation, dass alle 80 ms ein Interrupt gesetzt wird. Hiermit ist bereits festgelegt, wie lange das Ausführen des IRQ Handlers maximal dauern darf.

Verzicht auf Print-Anweisungen

Mithilfe von Tracealyzer lässt sich gewährleisten, dass der IRQ Handler so wenig wie möglich tut. Hierbei hat das Nutzen von Tracealyzer folgenden Vorteil: Anwender können auf externe »printk-Aufrufe« vollständig verzichten. Sie können unkorrekte Implementierungen verbergen, wie später noch genauer erläutert wird. Zudem müssen Anwender keine Zeitstempel in Kernel Logs vergleichen. Mit Tracealyzer für Linux wird es außerdem überflüssig, zum Evaluieren der Leistungsfähigkeit »LTTng-Traces« zu durchsuchen. Stattdessen erhält man klaren Einblick in den IRQ Handler.

Wie aus dem zuvor behandelten Beispiel des Linux-Gerätetreibers für ein Datenerfassungs-Gerät bereits hervorgegangen ist, informiert ein vom Gerät kommendes GPIO-Signal den Treiber, dass Daten zum Auslesen bereit sind.

Nachfolgend ist dargestellt, wie der einfache IRQ Handler sowie der Code zu seiner »Registrierung« beim Kernel aussieht:

static int __init mab_init(void)
{
  result = request_irq(irq_number, (irq_handler_t) mab_irq_handler,
  IRQF_TRIGGER_RISING, "mab_irq_handler", NULL);
  return result;
}

static irq_handler_t mab_irq_handler(unsigned int irq,
void *device, struct pt_regs *regs)
{
  printk(KERN_INFO "MAB - got interrupt!\n");
  return (irq_handler_t) IRQ_HANDLED;
}

Mit einem printk-Aufruf wird im Kernel Log lediglich verzeichnet, dass ein Interrupt erhalten wurde, und der Rückgabewert »IRQ_HANDLED« informiert den Kernel, dass der Interrupt bearbeitet wurde.

Bevor das Kernel-Modul allerdings geladen und das Gerät verbunden wird, ist LTTng im Zielgerät auf etwas andere Weise zu starten, als es in der Anleitung »Getting Started With Tracealyzer for Linux« [1] beschrieben ist. Um LTTng anzuweisen, ausschließlich IRQ Handler zu erfassen, muss der Anwender die folgenden Befehle geben. Scheduler-Ereignisse sind hierbei zu ignorieren, damit der Fokus rein auf dem Interrupt-Handler liegt:

$> lttng create
$> lttng enable-event -k irq_*
$> lttng start

Nachdem die LTTng-Traces an die Host-Maschine übertragen und Tracealyzer gestartet wird, erscheint beim Öffnen der »Trace View«-, »Selection Details«- (rechts) und »Actor Instances«-Graphen folgende Ansicht (Bild 1). Im Actor Instance-Graph wurde lediglich der IRQ Handler ausgewählt, um ablenkende Informationen zu vermeiden:

Bild 1 Percepio
© Percepio

Bild 1 zeigt, dass der IRQ Handler wie erwartet etwa alle 80 Millisekunden aktiv wird.

In der Darstellung in Bild 1 wurde in der Dropdown-Box »View« die Option »Periodicity – From Ready« ausgewählt, die zeigt, wie oft der IRQ Handler aktiv wird. Wie zu sehen, erfolgt das etwa alle 80 ms, was den Erwartungen entspricht. Das Fenster »Selection Details« zeigt, dass das Verarbeiten einer Instanz des IRQ Handlers ungefähr 3,3 ms dauert. Wird in der Dropdown-Box »View« des Actor-Instance-Graphen die Option »Execution Time« gewählt, so ist zu erkennen, dass die Verarbeitungszeit im Durchschnitt 3,3 ms beträgt (Bild 2).

Percepio Verarbeitungszeit
© Percepio

Bild 2. Die Verarbeitungszeit beträgt mit printk-Aufruf im Durchschnitt 3,3 ms.

Entfernt man den printk-Aufruf und lässt sich daraufhin den Actor-Instance-Graphen anzeigen, wobei in der Dropdown-Box »View« wieder die Option »Execution Time« gewählt wird, erscheint folgende Darstellung in Bild 3.

Die maximale Verarbeitungszeit beträgt jetzt 14 µs, während der Durchschnittswert mit printk 3,3 ms betrug. Es verdeutlicht den erheblichen Verarbeitungsaufwand, den ein printk-Aufruf mit sich bringt. Angesichts der Unmenge verschiedener Fälle, die beim Schreiben an den Kernel Log zu berücksichtigen sind, ist das zu erwarten. Deshalb sollte printk oder eine seiner Varianten in einem IRQ Handler nicht allzu freizügig zum Einsatz kommen, denn schließlich soll die Leistung nicht beeinträchtigt werden.

Actor Instance-Graph
© Percepio

Bild 3. Ohne printk-Anweisung beträgt die maximale Verarbeitungszeit 14 µs, wobei recht große Schwankungen zu verzeichnen sind.

Aus Bild 3 lässt sich weiterhin erkennen, dass die Verarbeitungszeit stark schwankt. Zwar liegen die Unterschiede im Mikrosekundenbereich, jedoch ist es wichtig zu verstehen, was zu diesen großen Ungleichheiten führt. Beim Forschen nach den Ursachen hilft die Option »Periodicity – From Ready«, die Bild 4 anzeigt: Es ist klar zu erkennen, dass etwas nicht stimmt. Während der IRQ Handler im mittleren Teil der Darstellung erwartungsgemäß alle 80 ms aufgerufen wird, gibt es zu Beginn der Darstellung und an ihrem Ende Phasen, in denen die Aufrufe deutlich öfter erfolgen. Außerdem ist zu sehen, dass es gegen Ende der Aufzeichnung einen Fall gab, in dem die Verarbeitung nach 325 ms erfolgte.

Option »Periodicity – From Ready«
© Percepio

Bild 4. Die Option »Periodicity – From Ready« hilft bei der Ursachenforschung.

Das liegt daran, dass das Gerät im IRQ Handler nicht angewiesen wurde, mit dem Setzen des Interrupts aufzuhören. Da der Interrupt stets präsent ist, gibt der Linux-Scheduler ständig Verarbeitungs-Ressourcen an den IRQ Handler zurück. Üblicherweise wird dieses nicht wünschenswerte Phänomen als »Thrashing« bezeichnet.

Jedoch mag auffallen, dass sich die Periodizität des IRQ Handlers nach einiger Zeit stabilisiert (erwartet wurde, dass der IRQ Handler durchgehend angestoßen wird), obwohl das Gerät nicht angewiesen wurde den Interrupt zurückzusetzen. Nach einem genaueren Blick in die Geräte-Spezifikation zeigte sich, dass ein Fail-Safe-Mechanismus nach einiger Zeit den Interrupt zurücksetzt, falls er nicht über den I2C-Bus bestätigt wurde. Wenn das printk inkludiert ist, erstreckt sich die Ausführungszeit über die Zurücksetzzeit und maskiert hiermit, dass der eigene Code den Interrupt nicht angemessen bestätigt.

printk-Aufrufe verschleiern den Fehler

Dank Tracealyzer war zu erkennen, dass der printk-Aufruf den Fehler verschleiert hat. Da die Zeit zwischen den Aufrufen des IRQ Handler mit printk ungefähr 80 ms betrug, ist völlig untergegangen, dass man das Gerät hätte anweisen müssen, mit dem Setzen von Interrupts aufzuhören, bis der erste Interrupt bearbeitet war. Sichtbar wäre der Fehler erst kurz vor dem Release-Termin beim Entfernen der externen printk-Aufrufe geworden. In dem Stadium aber hätten Modifikationen am Treiber einen erheblichen Zeit- und Kostenaufwand verursacht.

Zusammenfassend ist festzustellen, dass das Nutzen von Tracealyzer in verschiedenen Phasen der Treiberentwicklung sowohl Bugs als auch Performance-Probleme aufdecken kann. Tracealyzer hat sich als äußerst wertvolles Hilfsmittel erwiesen, um die Leistungsfähigkeit des IRQ Handlers zu gewährleisten, der eines der wichtigsten Teile eines jeden Gerätetreibers ist. Ebenso lässt sich erkennen, welche Vorteile das Verwenden von Tracealyzer gegenüber dem Nutzen von printk-Aufrufen bietet, die einen beträchtlichen Mehraufwand verursachen können.

Literatur:
[1] https://percepio.com/tz/tracealyzer-for-linux/getting-started-tzlinux/
[2] http://www.mab-labs.com/

Mohammed Billoo
© Percepio

Mohammed Billoo ist Gründer von MAB Labs, LLC, einem Anbieter kundenspezifischer Embedded-Linux-Anwendungen.

Der Autor:
Mohammed Billoo ist Gründer von MAB Labs, LLC [2], einem Anbieter kundenspezifischer Embedded-Linux-Anwendungen für eine Vielzahl von Hardware-Plattformen. Mohammed verfügt über einen Master in Elektrotechnik und mehr als 12 Jahre Erfahrung in Design, Implementierung und Test von Embedded-Software. Er trägt aktiv zum Linux-Kernel sowie zahlreichen Open-Source-Bemühungen bei und unterrichtet zudem als Professor für Elektrotechnik an der Cooper Union for the Advancement of Science and Art in New York.

Auf Facebook teilenAuf Twitter teilenAuf Linkedin teilenVia Mail teilen

Das könnte Sie auch interessieren

Embedded-Linux-Systeme tracen – Teil 1
Mit Mutanten Tests testen
AIOps sollte auf der To-Do-Liste stehen

Verwandte Artikel

Percepio AB