Aus Linux-Magazin 06/2014

Kernel- und Treiberprogrammierung mit dem Linux-Kernel – Folge 74

© psdesign1, Fotolia

Die Ktap-Runtime-Library ermöglicht detaillierte Einsichten in den Linux-Kernel und eigene Treiber. Das Programmieren von Skripten im Ktap-eigenen Lua-Dialekt ist zum Glück leicht zu erlernen.

Der Blick in interne Abläufe des Linux-Kernels lohnt sich nicht nur aus reiner Neugier. Sowohl der Sysadmin als auch der Kernel- oder Anwendungsprogrammierer gewinnt damit wertvolle Informationen zur Optimierung und Fehlersuche. Mit dem Tracing-Toolkit Ktap [1] ist das erstaunlich leicht. Meist installiert es sich problemlos und ist dank des Programmierinterface einfach in der Handhabung.

Wie in der vorhergehenden Kern-Technik [2] gezeigt, gelingt es bereits mit einfachen Aufrufen, Datei-Überwachungen zu implementieren oder Histogramme über die am häufigsten verwendeten Systemcalls zu erzeugen. Aber es geht noch viel mehr. Wer sich mit der Programmiersprache Lua beschäftigt sowie mit der vom Ktap-Entwickler Jovi Zhangwei bereitgestellten Runtime-Library, kann Programme schreiben, die gezielt Informationen im Kernel abgreifen.

Die Programmiersprache Lua hat der Huawei-Mitarbeiter Zhangwei für die Problemstellung Kerneltracing angepasst. Das äußert sich etwa darin, dass die im Original Pascal-ähnliche Syntax einer C-ähnlichen Syntax gewichen ist. Statt einen Block mit »then« und »endif« zu kennzeichnen, nutzt Ktap geschweifte Klammern (Tabelle 1).

Tabelle 1

Ktap-Lua kurz und knapp

Konstrukt

Bedeutung

»if (Bedingung) {Anweisung} else {Anweisung)

Bedingte Ausführung

»while (Bedingung) {Anweisung

Kopfgesteuerte Schleife, Abbruch mit »break«

»repeat Anweisung until (Bedingung)«

Fußgesteuerte Schleife, Abbruch mit »break« ; keine Klammern, kein »while« , sondern »until«

»for ( i=Init, Limit, Step ) {Anweisung

Schleife im Lua- respektive Basic-Stil

»for ( Init; Bedingung; Körper) {Anweisung

Klassische C-Schleife, Abbruch mit »break«

»for (k, v in pairs([Tabelle])) {Anweisung

Key-Value-Schleife über den Inhalt einer Tabelle

»#«

Kommentar

»;«

Optionaler Abschluss eines Befehls

»function Funktionsname(Parameterliste) {Anweisung return …}«

Funktionen geben keinen, einen oder sogar mehrere Werte zurück

»+« , »-« , »*« , »/« , »%« , »^«

Arithmetische Operatoren

»and« , »or« , »not«

Logische Operatoren

»==« , »<« , »>« , »<=« , »>=« , »~=«

Vergleichsoperatoren

»..«

Stringoperator (zum Aneinanderhängen, entspricht »+« bei Java)

Sonst enthält Lua die typischen Elemente einer Programmiersprache: Bedingungen, Schleifen, Operatoren, Variablen. Letztere zeigen den größten Unterschied zu der von Kernelprogrammierern eingesetzten Sprache C: Lua-Variablen sind nicht typisiert. Erst wenn einer Variablen ein Wert zugewiesen wird, legt Lua den Datentyp fest. Dabei erkennt der Bytecode-Interpreter, ob es sich um einen Integerwert, eine Fließkommazahl, ein Boolean oder einen String handelt.

Eine weitere Besonderheit ist der Datentyp Tabelle. Lua verarbeitet Tabellen, die in ihren Zeilen nicht immer die gleiche Anzahl Spalten haben, die also beispielsweise dreieckig sind. Noch interessanter ist die Möglichkeit, Tabelleneinträge nicht nur per Index, sondern auch per Inhalt (Name) als Assoziativ-Array anzusprechen.

Das machen sich Ktap-Programme eifrig zu Nutze, wenn sie beispielsweise die Aufrufe eines Systemcalls zählen sollen (Abbildung 1). In diesem Fall dient der Name des Systemaufrufs (»probename« ) als Index in die Tabelle, der dort abgelegte Wert zählt das Auftreten. Über Tabellen kann man übrigens mit einer besonderen Art der For-Schleife iterieren, die auf der Ktap-Funktion »pairs()« beruht.

Abbildung 1: Histogramm eingebaut: Das Ktap-Programm »sctop.kp« ermittelt die am häufigsten verwendeten Systemcalls.

Abbildung 1: Histogramm eingebaut: Das Ktap-Programm »sctop.kp« ermittelt die am häufigsten verwendeten Systemcalls.

Tracepoints

Die Runtime-Library ist eine Lua-Erweiterung, die die Sprache in den Linux-Kernel einbettet. Sie hilft Daten ausgeben, enthält statistische Funktionen und setzt die Tracepunkte. Darüber hinaus bietet sie Zeitmessfunktionen, um beispielsweise Latenzen zu bestimmen. Tracepunkte setzt der Ktap-Anwender mit der Funktion »trace« . Die hat den folgenden Aufbau, wie auch Abbildung 2 zeigt:

Abbildung 2: Das zentrale Schlüsselwort für ein Ktap-Programm ist »trace«. Es lässt sich auf eine Funktion ansetzen und mit Filtern einschränken.

Abbildung 2: Das zentrale Schlüsselwort für ein Ktap-Programm ist »trace«. Es lässt sich auf eine Funktion ansetzen und mit Filtern einschränken.

trace Funktion / Filter / { Aktion }

Sobald der Kernel die spezifizierte Funktion aufruft, wendet Ktap den Filter an. Trifft die Filterbedingung zu, wird der hier als Aktion angegebene Lua-Code abgearbeitet. Im Rahmen dieses Codes kann der Anwender auf eine Reihe von Variablen zugreifen (Tabelle 2). Es ist auch möglich, auf die Argumente der Kernelfunktion zuzugreifen, die den Tracepunkt aktiviert hat. Falls es sich um Adressen aus dem Userland handelt, lassen sich auch die dazugehörigen Speicherzellen auslesen.

Tabelle 2

Eingebaute Variablen

Variablenname

Bedeutung

»arg0« … »arg9«

Argumente des Event-Objekts

»cpu«

ID der aktuell genutzten CPU

»pid«

Prozess-Identifikationsnummer

»tid«

Thread-Identifikationsnummer

»uid«

User-Identifikationsnummer

»execname«

Name des gerade ausgeführten Programms

»argstr«

Repräsentation des Event-Strings

»probename«

Name des Events

An dieser Stelle fallen dem Leser möglicherweise die unterschiedlichen Bezeichnungen im Vergleich zum vorigen Artikel [2] auf – der Ktap-Entwickler Zhangwei hat die letzten Monate damit verbracht, Ktap auf die Aufnahme in den Standard-Linux-Kernel vorzubereiten. Im Zuge dieser noch nicht abgeschlossenen Tätigkeit hat er auch einige Veränderungen am Interface vorgenommen. Daher ist es ratsam, Ktap per »git pull« auf den neuesten Stand (bei Redaktionsschluss 0.4) zu bringen.

Die Spezifikation eines Tracepunkts in der beschriebenen Art und Weise ist insofern kompliziert, als Ktap verschiedene Trace-Methoden respektive Event-Typen des Linux-Kernels nutzt, die der Anwender spezifizieren muss (Abbildung 3). So lassen sich Linux-Systemcalls wie »open()« , »read()« , »exit()« oder »exec()« mit Hilfe des Schlüsselworts »syscall« überwachen, viele interne Kernelfunktionen über »ftrace« und Kernelprobes über »kprobe« .

Abbildung 3: Hintergrundinformationen über Tracepoints bezieht Ktap aus dem »/sys«-Filesystem.

Abbildung 3: Hintergrundinformationen über Tracepoints bezieht Ktap aus dem »/sys«-Filesystem.

Anfang und Ende

Ein weiteres wichtiges Feature bietet außerdem die Möglichkeit, den Tracepunkt auf den Einstieg in (»enter« ) oder den Ausstieg (»exit« oder auch »%return« ) aus einer Kernel- oder Bibliotheksfunktion zu setzen.

Die Runtime-Library stellt mit »tick-*« , »profile-*« und »trace_end« weitere Event-Funktionen zur Verfügung (Tabelle 3). Die Timer »tick« und »profile« bieten die Möglichkeit, Funktionen zeitgesteuert abarbeiten zu lassen. Den Code einer »tick« -Funktion arbeitet eine einzelne CPU periodisch ab, den Code einer »profile« -Funktion jeder der in einem System vorhandenen Prozessoren.

Tabelle 3

Elemente der Ktap-Runtime-Library

Schlüsselwort

Beschreibung

Basisfunktionen

print

Unformatierte Ausgabe

printf

Formatierte Ausgabe (wie in C)

print_hist

Ausgabe eines Histogramms

pairs

Funktion zur Iteration durch eine Tabelle

len

Länge eines Strings

delete

Löschen von Tabellen

stack

Stacktrace

print_trace_clock

Ausgabe eines genauen Zeitstempels

num_cpus

Anzahl der CPU-Kerne

arch

Architektur

kernel_v

Kernelversion

kernel_string

Konvertiert eine Kerneladresse in einen String

user_string

Konvertiert eine Userspace-Adresse in einen String

stringof

Konvertiert eine Adresse in einen String

ipof

Adresse der übergebenen Funktion

gettimeofday_ns

Zeitstempel in Nanosekunden

gettimeofday_us

Zeitstempel in Mikroekunden

gettimeofday_ms

Zeitstempel in Millisekunden

gettimeofday_s

Zeitstempel in Sekunden

curr_taskinfo

Informationen zur aktiven Task

in_iowait

Wahr, falls sich die Task im Schlafen-Zustand befindet

in_interrupt

Wahr, falls sich der Kernel gerade im Interruptkontext befindet

exit

Beendet das Ktap-Programm

Ausgabe

ansi.clear_screen

Löschen des Bildschirms

ansi.set_color

Setzen der Farbe 1

ansi.set_color2

Setzen der Farbe 2

ansi.set_color3

Setzen der Farbe 3

ansi.reset_color

Farbpalette zurücksetzen

ansi.new_line

Neue Zeile ausgeben

Tracing

trace_by_id

Tracepunkt über die Event-ID setzen

trace_end

Funktion, die bei Skriptabbruch aufgerufen wird

kdebug.tracepoint

Tracepunkt per Kerneldebugger setzen

kprobe

Tracepunkt per Kprobe setzen

Netzwerk

ip_sock_saddr

Quelladresse eines Sockets extrahieren

ip_sock_daddr

Zieladresse eines Sockets extrahieren

format_ip_addr

IP-Adresse in String konvertieren

Timer

tick_us

Funktion periodisch auf einer CPU aufrufen, Zeitangabe in Mikrosekunden

tick_ms

Funktion periodisch auf einer CPU aufrufen, Zeitangabe in Millisekunden

tick_s

Funktion periodisch auf einer CPU aufrufen, Zeitangabe in Sekunden

profile_us

Funktion periodisch auf jedem CPU-Kern aufrufen, Zeitangabe in Mikrosekunden

profile_ms

Funktion periodisch auf jedem CPU-Kern aufrufen, Zeitangabe in Millisekunden

profile_s

Funktion periodisch auf jedem CPU-Kern aufrufen, Zeitangabe in Sekunden

Dateizugriffe

Mit diesem Wissen ausgestattet kann der Anwender beispielsweise den Zugriff auf bestimmte Dateien überwachten. Möchte er etwa wissen, welche Tasks auf die Dateien »/etc/passwd« oder »/etc/shadow« zugreifen, verwendet er das Ktap-Skript aus Listing 1. Damit das Skript die zugreifenden Tasks identifizieren kann, setzt es einen Tracepoint auf den Beginn des Systemcalls »open()« .

Listing 1

fileaccess.kp überwacht Dateien

01 #!/usr/local/bin/ktap -q
02
03 var path = {}
04
05 printf("%5s %6s %-12s %3s %3s %s\n",
06     "UID", "PID", "COMM", "FD", "ERR", "PATH");
07
08 trace syscalls:sys_enter_open {
09     path[tid] = user_string(arg1)
10 }
11
12 trace syscalls:sys_exit_open {
13     var fd
14     var errno
15
16     if (arg1 < 0) {
17         fd = 0
18         errno = -arg1
19     } else {
20         fd = arg1
21         errno = 0
22     }
23     #if (execname=="w") {
24     #   path[tid] = 0
25     #    return
26     #}
27     if (path[tid]=="/etc/passwd") {
28         printf("%5d %6d %-12s %3d %3d %s\n",
29             uid, pid, execname, fd,
30             errno, path[tid])
31     }
32     if (path[tid]=="/etc/shadow") {
33         printf("%5d %6d %-12s %3d %3d %s\n",
34             uid, pid, execname, fd,
35             errno, path[tid])
36     }
37     path[tid] = 0
38 }

Sobald der Kernel diesen aufruft, speichert das Skript den Namen der geöffneten Datei. Eigentlich könnte an dieser Stelle direkt eine Ausgabe erfolgen, falls es sich um die Datei »/etc/passwd« oder »/etc/shadow« handelt. Möchte der Anwender zugleich mitprotokollieren, ob das Programm erfolgreich Zugriff erhält, wertet er zusätzlich den Returnwert des Systemcalls aus. Daher setzt er einen zweiten Tracepoint auf das Ende des Systemcalls »sys_exit_open()« . Dann erfolgt die Ausgabe.

Den Namen der zu öffnenden Datei in einem Feld (Assoziativ-Array) abspeichern ist notwendig, da mehrere Tasks zugleich den Systemcall »open()« verwenden könnten. Das Feld, indiziert mit der Thread-ID, rettet die Namen auch im Fall paralleler Zugriffe. Auf den Anfang und auf das Ende von Funktionen triggern zu können ermöglicht es, die Verarbeitungsdauer einer Funktion auszumessen. Das mit Ktap ausgelieferte Beispielprogramm »function_time.kp« zeigt, wie das geht. Es ermittelt die minimale, durchschnittliche und maximale Reaktionszeit des Systemcalls »read()« (Kernelfunktion »vfs_read()« ). Dazu setzt das Skript zwei Tracepoints, den ersten zu Beginn der Funktion »vfs_read()« . Bei dessen Auslösung speichert es per »gettimeofday_us()« die aktuelle Zeit in einer Tabelle ab.

Am Ende der Funktion nimmt das Programm wiederum die Zeit. Mittels Subtraktion der Zeitstempel lässt sich dann die Zeitdauer ermitteln. Das Ktap-Skript überprüft noch, ob es damit einen neuen Maximal- oder Minimalwert gibt, danach aktualisiert es zur Berechnung der Durchschnittszeit die bisherige Gesamtzeit und die Anzahl der Aufrufe. Wird das Skript abgebrochen, gibt »trace_end« die erfassten Zeiten aus (Listing 2).

Listing 2

Differenzzeitmessung mit function_time.kp

01 #!/usr/bin/env ktap
02
03 var self = {}
04 var count_max = 0
05 var count_min = 0
06 var count_num = 0
07 var total_time = 0
08
09 printf("measure time(us) of function vfs_read\n");
10
11 trace probe:vfs_read {
12     if (execname == "ktap") {
13         return
14     }
15
16     self[tid] = gettimeofday_us()
17 }
18
19 trace probe:vfs_read%return {
20     if (execname == "ktap") {
21         return
22     }
23
24     if (self[tid] == nil) {
25         return
26     }
27
28     var durtion = gettimeofday_us() - self[tid]
29     if (durtion > count_max) {
30         count_max = durtion
31     }
32     var min = count_min
33     if (min == 0 || durtion < min) {
34         count_min = durtion
35     }
36
37     count_num = count_num + 1
38     total_time = total_time + durtion
39
40     self[tid] = nil
41 }
42
43 trace_end {
44     var avg
45     if (count_num == 0) {
46         avg = 0
47     } else {
48         avg = total_time/count_num
49     }
50
51     printf("avg\tmax\tmin\n");
52     printf("-------------------\n")
53     printf("%d\t%d\t%d\n", avg, count_max, count_min)
54 }

Außerdem können Treiberentwickler mit Ktap ihren selbst geschriebenen Code überwachen. Listing 3 zeigt ein Programm, das die Funktion »driver_read()« eines Gerätetreibers überwacht, wie ihn etwa die Kern-Technik-Folge 69 [3] beschrieben hat. Dabei kommt die Filterfunktion zum Einsatz, die den Instruction Pointer (»ip« , Befehlszähler der CPU) auf die Adresse der Funktion »driver_read()« überwacht. Ein einfaches Skript, das Ftrace verwendet, gibt bei jedem Zugriff eine Meldung aus und beim Abbrechen die Gesamtzahl der Zugriffe.

Listing 3

driverread.kp überwacht driver_read

01 var count=0;
02
03 trace ftrace:function /ip==driver_read*/ {
04     print("driverread ")
05     count+=1;
06 }
07
08 trace_end {
09     print("driverread hist:")
10     print(count)
11 }

Besonders gut verdeutlicht das von Tadaki Sakai bereitgestellte Skript »tetris.kp« die Leistungsfähigkeit von Ktap. Damit spielen Anwender das altbekannte Tetris – jedoch im Kernel (Abbildung 4). Dabei läuft im Linux-Kern nicht nur der Spielalgorithmus, sondern sogar die komplette Eingabe. Ein Blick in den für x86-Prozessoren geschriebenen Programmcode offenbart, wie das Skript eine Kernelprobe auf die Funktion »kbd_event()« [4] setzt und dort aus den CPU-Registern den Keycode ausliest.

Abbildung 4: Tetris im Linux-Kernel: Ktap und seine eingebaute Skriptsprache machen's möglich.

Abbildung 4: Tetris im Linux-Kernel: Ktap und seine eingebaute Skriptsprache machen’s möglich.

Zusätzlich aktiviert das Skript alle 200 Millisekunden eine Timerfunktion, die den Keycode auswertet und damit den Spielstein bewegt oder rotiert sowie die Ausgabe auf der Konsole initiiert. Der Code zum Ktap-Tetris findet sich im Ktap-Archiv unter »sample/games/tetris.kp« .

Work in Progress

Eigentlich müsste unter den Ktap-Appetithappen jetzt ein Abschnitt über das Foreign Function Interface (FFI) folgen. Es ermöglicht, direkt aus Ktap heraus auf Kernel-interne Datenstrukturen zuzugreifen und sogar Kernelfunktionen selbst aufzurufen [5]. Funktionen lassen sich also nicht nur überwachen, sondern auch im Programm verwenden. Die Beispielskripte zeigen das Aufrufen der Funktion »printk()« , über die Ktap Daten im Kernel-Log ablegt oder den Zugriff auf die zentrale Datenstruktur »skb« des Netzwerk-Subsystems. Das Skript »ffi_kmalloc.kp« ruft 1000-mal die Funktionen »kmalloc()« und »kfree()« auf, um die dafür benötigte Zeit zu messen.

Allerdings ist in der aktuellen Version 0.4 der Code für das FFI kaputt, da sich Jovi Zhangwei ganz auf die Integration der zentralen Komponenten seines Trace-Subsystems in den Standardkernel konzentriert. Das prinzipielle Placet hat er zwar, aber für die konkrete Aufnahme fordern die Core-Entwickler noch schlankeren Bytecode. Ktap ist also “Work in progress” und arbeitet zudem nicht in allen Punkten fehlerfrei. Laufen Ktap-Programme über einen längeren Zeitraum, kommt es schon mal zu Abbrüchen. In machen Fällen muss man zur Behebung in »include/ktap_types.h« Konstanten anpassen.

Und schließlich die Dokumentation: Das beiliegende Tutorial [6] ist verglichen mit den Möglichkeiten von Ktap als bescheiden zu bezeichnen. Um diese auszuschöpfen, ist aber viel Know-how aus dem Bereich Kerneltracing notwendig. Einen ersten Einstieg mit Schwerpunkt auf dem Werkzeug Dtrace liefert [7]. Wer bereits Erfahrungen mit Dtrace oder Systemtap besitzt, wird schnell die Parallelen erfassen. Neulinge sollten sich vor allem die Beispielprogramme ansehen, die dem Ktap-Quellcode unterhalb des Verzeichnisses »samples/« beiliegen. Es gibt noch viel zu tun für den Entwickler Jovi Zhangwei, vielleicht finden sich ja noch ein paar Mitstreiter. (mhu)

Infos

  1. Ktap: http://www.ktap.org
  2. Quade, Kunst: “Kern-Technik – Folge 73”: Linux-Magazin 04/14, S. 92
  3. Quade, Kunst: “Kern-Technik – Folge 69”: Linux-Magazin 08/13, S. 86
  4. »kbd_event« : http://lxr.free-electrons.com/source/drivers/tty/vt/keyboard.c#L1364
  5. Qingping, Eason, “Ktap FFI Proposal”: https://www.cs.cmu.edu/~412/lectures/L09_KTAP_Proposal.pdf
  6. Ktap-Tutorial: http://www.ktap.org/doc/tutorial.html
  7. Brendan Gregg, “Linux Performance Analysis and Tools”: http://www.brendangregg.com/Slides/SCaLE_Linux_Performance2013.pdf
  8. Listings zum Artikel: https://www.linux-magazin.de/static/listings/magazin/2014/06/kern-technik/

Der Autor

Eva-Katharina Kunst, Journalistin, und Jürgen Quade, Professor an der Hochschule Niederrhein, sind seit den Anfängen von Linux Fans von Open Source. Mit “Embedded Linux lernen mit dem Raspberry Pi” erscheint Quades drittes Linux-Buch.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben