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









