Aus Linux-Magazin 11/2005

Kernel- und Treiberprogrammierung mit dem Kernel 2.6 - Folge 24

Wer per Konsole Dateien manipuliert, erwartet auch, dass das korrespondierende Konqueror-Fenster daneben die mitbekommt. Konqueror pollt dazu nicht das Verzeichnis, sondern setzt das Filemonitoring des Kernels ein. Besonders effizient geht das mit dem neuen Inotify.

Neue Kernel, neue Features. Zwei Monate dauerten die Arbeiten am Kernel 2.6.13, der mit einem ganzen Reigen überarbeiteter und neuer Funktionen auftrumpft: von A wie Alsa bis Z wie Zeitbasis. Eine wichtige Novität ist Inotify (Inode Notify, [1]). Es ersetzt das aus älteren Kerneln bekannte Dnotify. Beide sind für das Filesystem-Monitoring zuständig, bei dem der Kernel die Applikationen über Modifikationen im Dateisystem informiert.

Anwendungen hierfür gibt es reichlich, wenn beispielsweise Nautilus oder Konqueror die grafische Anzeige zu aktualisieren haben, sobald jemand im Hintergrund Dateien löscht oder neu anlegt. Oder ein Security-Monitor erkennt bösartige Manipulationen an Dateien und das neue Suchprogramm Beagle [2] aktualisiert Suchanfragen on the fly. Auch ein Backup-System könnte nicht zu festen Zeitpunkten, sondern immer dann sichern, sobald sich Dateien geändert haben. Solche Applikationen pollen dazu nicht im Dateisystem nach Veränderungen, sondern lassen sich effizienter durch den Kernel – genauer durch Inotify – informieren.

Nicht nur Verzeichnisse

Ein Ersatz für Dnotify war aus mehreren Gründen notwendig. Zunächst ermöglicht Dnotify nur die Überwachung ganzer Verzeichnisse, nicht jedoch von einzelnen Dateien. Jedes per Dnotify überwachte Verzeichnis benötigt zudem einen eigenen Filedeskriptor. Bei vielen überwachten Verzeichnissen ist das Limit der für einen Rechenprozess erlaubten Filedeskriptoren schnell erreicht.

Operationen auf die überwachten Verzeichnisse, beispielsweise Löschen oder Verschieben, bekommt die Applikation per Signal kundgetan. Signale mit ihren Tücken sind bei den Entwicklern nicht sonderlich beliebt. Die Liste der Kritikpunkte ließe sich fortsetzen und gipfelt in der Unmöglichkeit, das überwachte Verzeichnis zu mounten. Der überwachende Rechenprozess greift wie beschrieben über seinen Filedeskriptor auf das Verzeichnis zu und belegt es damit. Im Zeitalter von USB und Wechselmedien ist das nicht tragbar.

Inotify dagegen fußt darauf, dass die Applikation den Kernel ein Inotify-Objekt instanzieren lässt. Das Objekt bekommt die Überwachungspunkte (also Verzeichnisse und Dateien) – die so genannten Watches – übergeben. Ob sich an einem der Watches eine Veränderung ergeben hat, erfährt die Applikation über die bekannten Datei-Zugriffsfunktionen »read()«, »select()« oder »poll()«.

Damit schmiegt sich der Mechanismus harmonisch in das Unix-System ein und ermöglicht gerade deshalb einen problemlosen Unmount-Event. Hängt nämlich jemand ein Dateisystem mit einer überwachten Datei aus, empfängt die Applikation den Unmount-Event und der Kernel entfernt den Watch.

Inotify mit nur vier Funktionen handhaben

So nützlich, wie Inotify für Applikationen ist, so einfach ist es einsetzbar. Neben »read()« hält der Kernel drei weitere neue Systemcalls bereit:

  • Mit »int inotify_init( void )« fordert die
    Applikation einen Filedeskriptor zur Überwachung von
    Verzeichnissen und Dateien an. Der Systemcall instanziert das
    Inotify-Objekt. Rückgabewert ist der Filedeskriptor, mit dem
    »read()«-, »select()«- und
    »poll()«-Aufrufe möglich sind.
  • »int inotify_add_watch( int fd, const char **Path,
    unsigned int Mask )« übergibt dem Kernel die zu
    überwachenden Verzeichnisse und Dateien (»Path«)
    mitsamt der Eventmaske (»Mask«). Die Eventmaske
    spezifiziert, welche Ereignisse der Applikation zu melden sind, zum
    Beispiel das Öffnen oder Schließen einer Datei. Tabelle
    1 listet die möglichen Operationen auf. Der Systemcall gibt
    einen Watch-Deskriptor zurück. Dieser referenziert den
    Watch.
  • »int inotify_rm_watch ( int fd, int wd )« entfernt
    den über »wd« referenzierten Watch.
  • Der mehrfach erwähnte Systemcall »int read( int fd,
    char *buf, int Maxsize )« holt die einzelnen Ereignisse ab.
    Die Anwendung übergibt dazu einen ausreichend dimensionierten
    Speicherbereich »buf«. Wenn niemand die Zugriffsart
    durch Aufruf der Funktion »fcntl()« von
    “blockierend” auf “nicht blockierend”
    geändert hat, schläft der Rechenprozess, bis einer oder
    auch mehrere der Kernel-Events eintreffen. Der Kernel speichert
    jeden Event in einer Struktur vom Typ »struct
    inotify_event«, deren Aufbau Abbildung 1 zeigt. Im
    übergebenen Speicher »mbuf« liegen so viele
    derartige Strukturen, wie Events vorhanden sind respektive solange
    noch Platz im Speicher ist. Da direkt am Ende der Struktur
    »struct inotify_event« unter Umständen ein an der
    Operation beteiligter Dateiname hängt, ist die Länge
    nicht fix (siehe Listing 1, Zeile 90).

Neben »read()« lassen sich selbstverständlich auch die Aufrufe »select()« und »poll()« verwenden.

Abbildung 1: Der Kernel schickt der Applikation ein nach dieser Struktur aufgebautes Inotify-Event-Objekt.

Abbildung 1: Der Kernel schickt der Applikation ein nach dieser Struktur aufgebautes Inotify-Event-Objekt.

Abbildung 2: Die Option für Inotify findet sich in der Kernelkonfiguration unterhalb »File systems«.

Abbildung 2: Die Option für Inotify findet sich in der Kernelkonfiguration unterhalb »File systems«.

Tabelle 1:
Standard-Inotify-Flags und -Events

 

Define

Bedeutung

IN_ACCESS

Zugriff auf die Datei

IN_MODIFY

Modifikation der Datei

IN_ATTRIB

Veränderung von Meta-Information

IN_CLOSE_WRITE

Ein zum Schreiben geöffnetes File ist geschlossen

IN_CLOSE_NOWRITE

Ein zum Lesen geöffnetes File ist geschlossen

IN_OPEN

Öffnen der Datei

IN_MOVED_FROM

Eine Datei ist aus dem Verzeichnis verschoben

IN_MOVED_TO

Eine Datei ist ins Verzeichnis verschoben

IN_CREATE

Anlegen einer Datei

IN_DELETE

Löschen einer Datei im Verzeichnis

IN_DELETE_SELF

Löschen der Datei oder des Verzeichnisses

IN_MOVE_SELF

Verschieben der Datei oder des Verzeichnisses

Events, die an alle Inotify-User geschickt werden

 

IN_UNMOUNT

Filesystem ist ausgehängt

IN_Q_OVERFLOW

Zu viele Events

IN_IGNORED

Ignorierte Datei

Spezielle Flags

 

IN_ISDIR

Der Event bezieht sich auf ein Verzeichnis

IN_ONESHOT

Nur einmal verschickter Event

Hilfsdefinitionen

 

IN_CLOSE

IN_CLOSE_WRITE | IN_CLOSE_NOWRITE

IN_MOVE

IN_MOVED_FROM | IN_MOVED_TO

IN_ALL_EVENTS

Die Oder-Verknüpfung der Standard-Events

Bei der Glibc ist zurzeit nichts zu holen

Die aktuelle Version der Standard-C-Bibliothek kennt die nagelneuen Systemcalls noch nicht. Für den regelmäßigen Leser der Kern-Technik ist das aber kein Problem. Schließlich hat bereits die Folge 13 [3] gezeigt, wie Systemcalls auch ohne Glibc funktionieren. Einzig die Headerdateien des Kernels 2.6.13 müssen zum Kompilieren greifbar sein – hier findet der Compiler die Systemcall-Nummern. Den Pfad zu den Kernel-Headerdateien reflektiert das zugehörige Makefile.

Wer einen Kernel 2.6.13 oder jünger hat, in dem »Inotify« einkompiliert ist (siehe Abbildung 2), kann das File-Monitoring mit den wenigen Zeilen Code von Listing 1 ausprobieren. Zum Übersetzen eignet sich folgendes Makefile:

CFLAGS=-g -Wall -I/usr/src/linux-2.6.13/includeall: inotify

Die entstandene Applikation startet auf der Konsole unter Angabe des Verzeichnisses oder der Datei, die sie überwachen soll. Wer dies beispielsweise für das Temp-Directory tun will, gibt

./inotify /tmp/ &

ein. Abbildung 3 zeigt das Programm in Aktion. Die drei gelb markierten Zeilen sind Benutzereingaben, die eine Datei kopieren oder bewegen. Die roten Markierungen betreffen die ausgelösten Events, wie sie Tabelle 1 bezeichnet. Hier ist der Unterschied zwischen dem Kopieren zwischen Verzeichnissen und dem Verschieben innerhalb gut zu sehen.

Drei neue Objekte-Klassen im Kernel

Inotify ist im Kernel geradlinig implementiert. Insgesamt gibt es drei neue Objekte (Datenstrukturen): Inotify-Device, Inotify-Watch und Inotify-Event. Der Kernel legt beim Aufruf von »init_inotify()« ein Inotify-Device-Objekt für die Applikation an. Es dient als Container für die Watch-Objekte, die der Aufruf von »inotify_add_watch()« erzeugt. Eine zusätzliche Referenz auf das Watch-Objekt entsteht in der Inode der Datei respektive des Verzeichnisses, die der Watch durch die Angabe des Pfades überwachen soll.

Listing 1: File-Monitoring mit
»inotify.c«

01 #include <stdio.h>
02 #include <stdlib.h>
03 #include <unistd.h>
04 #include <sys/syscall.h>
05 #include <linux/inotify.h>
06 #include <asm/unistd.h>
07 
08 // Funktions-Wrapper fuer die Systemcalls
09 static inline int inotify_init (void)
10 {
11     return syscall(__NR_inotify_init);
12 }
13 
14 static inline int inotify_add_watch (int fd, const char *name, __u32 mask)
15 {
16     return syscall(__NR_inotify_add_watch, fd, name, mask);
17 }
18 
19 static inline int inotify_rm_watch (int fd, __u32 wd)
20 {
21     return syscall(__NR_inotify_rm_watch, fd, wd);
22 }
23 
24 // Eigentlicher Programmcode
25 static char *watch_event( int event )
26 {
27     switch( event ) {
28     case IN_ACCESS:
29         return "IN_ACCESS";
30     case IN_MODIFY:
31         return "IN_MODIFY";
32     case IN_ATTRIB:
33         return "IN_ATTRIB";
34     case IN_CLOSE_WRITE:
35         return "IN_CLOSE_WRITE";
36     case IN_CLOSE_NOWRITE:
37         return "IN_CLOSE_NOWRITE";
38     case IN_OPEN:
39         return "IN_OPEN";
40     case IN_MOVED_FROM:
41         return "IN_MOVED_FROM";
42     case IN_MOVED_TO:
43         return "IN_MOVED_TO";
44     case IN_CREATE:
45         return "IN_CREATE";
46     case IN_DELETE:
47         return "IN_DELETE";
48     case IN_DELETE_SELF:
49         return "IN_DELETE_SELF";
50     case IN_MOVE_SELF:
51         return "IN_MOVE_SELF";
52     case IN_UNMOUNT:
53         return "IN_UNMOUNT";
54     case IN_Q_OVERFLOW:
55         return "IN_Q_OVERFLOW";
56     case IN_IGNORED:
57         return "IN_IGNORED";
58     }
59     return "unknown";
60 }
61 
62 int main( int argc, char **argv )
63 {
64     int fd, watch_descriptor, len, ret;
65     struct inotify_event *ie;
66     char buf[1000];
67 
68     if( argc != 2 ) {
69         printf("usage: %s pathn", argv[0] );
70         exit( -1 );
71     }
72     fd = inotify_init();
73     if( fd<0 ) {
74         exit( -2 );
75     }
76     watch_descriptor = inotify_add_watch(fd, argv[1],
77         IN_ALL_EVENTS|IN_UMOUNT);
78     while( 1 ) {
79         len = read( fd, buf, sizeof(buf) );
80         ie = (struct inotify_event *)&buf;
81         while( len>0 ) {
82             printf("wd=%04x, %16.16s (0x%08x),"
83                 " cookie=%04x, len=%04x ",
84                 ie->wd, watch_event(ie->mask),
85                 ie->mask, ie->cookie, ie->len);
86             if( ie->len )
87                 printf("name="%s"", ie->name);
88             putchar( 'n' );
89             len -= sizeof(struct inotify_event)+ie->len;
90             ie = (struct inotify_event *) ((char*)ie + sizeof(struct inotify_event)+ie->len);
91         }
92     }
93     ret = inotify_rm_watch (fd, watch_descriptor);
94     return 0;
95 }

Wenn eine Applikation eine Datei-Operation bemüht, ruft der VFS [4] die Funktion »inotify_inode_queue_event()« auf. Sie prüft, ob es einen Watch zur Inode gibt. Falls ja, erzeugt Inotify ein Event-Objekt und übergibt es dem Inotify-Device-Objekt. Sollte ein Rechenprozess auf dem Inotify-Device-Objekt schlafen, weckt der Kernel ihn auf. Der Aufruf »read()« übergibt schließlich die gequeuten Inotify-Events der Applikation.

Abbildung 3: Das Testprogramm meldet jede Datei-Operation. Das Verschieben von Dateien (»IN_MOVE_*«) machen (hier grüne) Cookies deutlich.

Abbildung 3: Das Testprogramm meldet jede Datei-Operation. Das Verschieben von Dateien (»IN_MOVE_*«) machen (hier grüne) Cookies deutlich.

Außer den VFS-Funktionen ist auch die »generic_shutdown_superblock()« manipuliert. Bei ihrem Aufruf aktiviert sich die Funktion »inotify_super_block_u-mount()«, die alle Inodes des zugehörigen Filesystems durchgeht. Ist dort ein Watch referenziert, erzeugt sie einen Watch-Event vom Typ »IN_UMOUNT« und zerstört bei der Gelegenheit gleich das Inotify-Watch-Objekt. Wenn der Kernel ein Inotify-Objekt erzeugt, überprüft er zugleich, ob das Flag »IN_ONESHOT« gesetzt ist. Wenn ja, löscht der Betriebssystemkern das zugehörige Watch-Objekt. Dieses Feature hatten sich die Samba-Entwickler erbeten.

Zum Schluss ein Wermutstropfen: Im Kernel 2.6.13 funktionieren leider (noch) nicht alle Features. Das betrifft den »IN_UMOUNT«-Event ebenso wie das »IN_ONESHOT«-Flag. (jk)

File-Monitoring-Applikationen

Desktop-Systeme verlangen schon lange nach effizienten Methoden, mit denen sich der Zustand von Geräten, Verzeichnissen und Dateien nahezu in Echtzeit verfolgen lässt. Dnotify und insbesondere das neue Inotify machen das sonst fällige Polling überflüssig, lösen aber nicht das mögliche Problem, dass mehrere Applikationen auf dasselbe Objekt einen Watch setzen. Zu viele Watches führen zu einem Overhead im Kernel.

Die erste Lösung dieses Problems gelang SGI mit dem File Alteration Monitor. FAM besteht aus einem Server, der unter Linux auf Dnotify (auf Irix heißt der Mechanismus Imon) aufsetzt, und der Client-Bibliothek Libfam. Die Client-Bibliothek realisiert transparent für den Programmierer die Kommunikation mit dem Server [5]. Weiterer Vorteil von FAM: Im Gegensatz zu Dnotify ist die FAM-Schnittstelle plattformunabhängig.

Eine Entwicklergruppe rund um den Gnome-Hacker Daniel Veillard entwickelte FAM weiter zu Gamin [6]. Sie legten bei ihrer Entwicklung besonderen Wert auf Sicherheit und eine vereinfachte Codebasis. Zudem nutzt der Gamin-Server den vorgestellten Inotify-Mechanismus. Doch mit der Einfachheit des Interface zu Inotify schmelzen die Vorteile von FAM und Gamin dahin. Erste Applikationsprogrammierer setzen direkt auf Inotify auf und degradieren damit Gamin & Co. zu Kandidaten für die Rumpelkammer.

Inotify ist übrigens nicht in der Lage, neue Geräte zu erkennen. Hierfür sind weiterhin Hotplug und der Hardware Abstraction Layer (HAL) mit dem Hal-Daemon »hald« zuständig [7].

Infos

[1] Dokumentation zu Inotify in den Kernelquellen: »/usr/src/linux-2.6.13/Documentation/filesystems/inotify.txt«

[2] Beagle: [http://beaglewiki.org/Main_Page]

[3] E.-K. Kunst und J. Quade: “Kern-Technik”, Folge 13, Linux-Magazin 08/04, S. 92

[4] E.-K. Kunst und J. Quade: “Kern-Technik”, Folge 23, Linux-Magazin 10/05, S. 90

[5] FAM: [http://oss.sgi.com/projects/fam/]

[6] “Gamin the File Alteration Monitor”: [http://www.gnome.org/~veillard/gamin/]

[7] Hardware Abstraction Layer HAL: [http://freedesktop.org/Software/hal]

Die Autoren

Eva-Katharina Kunst, Journalistin, und Jürgen Quade, Professor an der Hochschule Niederrhein, sind seit den Linux-Anfängen Open-Source-Fans. Unter dem Titel “Linux Treiber entwickeln” haben sie ein Buch zum Kernel 2.6 veröffentlicht.

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