Aus Linux-Magazin 02/2005

Kernel- und Treiberprogrammierung mit dem Kernel 2.6 - Folge 18

Ob für Applikations- oder Treiberprogrammierer: Der asynchrone Zugriffsmodus im Kernel 2.6 verspricht Geschwindigkeitsvorteile bei der Ein- und Ausgabe. Dieser Artikel zeigt, wie Entwickler von Async-I/O profitieren, und untersucht, ob die Implementation die Versprechungen erfüllt.

Schreibt eine Anwendung auf die Festplatte, sind Verzögerungen programmiert: Das langsame Medium bremst die ganze Applikation und macht anderweitig erzielte Geschwindigkeitsvorteile zunichte. Abhilfe soll in diesem Fall der asynchrone Zugriffsmodus (Async-I/O) schaffen: Während das Ein- oder Ausgabesystem noch mit dem Lesen oder Schreiben beschäftigt ist, bereitet die Applikation bereits den nächsten I/O-Auftrag vor (Abbildung 1).

Im Unterschied dazu besitzen klassische Unix-Systeme nur den synchronen Zugriffsmodus, bei dem eine Applikation auf das Ende der Ein- und Ausgabe wartet. Will ein Programm trotz eines laufenden Auftrags weiterarbeiten, muss es einen zweiten Thread starten. Nach diesem Prinzip funktioniert die Posix-Variante des asynchronen Zugriffs[1]. Deren Nachteil ist, dass mit jedem I/O-Auftrag der Scheduler aktiv werden muss. Unnötige Performance-Einbußen sind die Folge.

Neben der Posix-Lösung gab es für Linux 2.4 bereits Patches, die den gewünschten Zugriffsmodus direkt im Kernel realisieren. Kernel 2.6 hat ihn schließlich eingebaut. Dennoch setzen bisher nur wenige Applikationen Async- I/O ein, denn es ist kaum dokumentiert, wie sich diese Zugriffsart nutzen lässt. Außerdem bringt die Implementierung in Kernel 2.6 nur teilweise die erwarteten Performance-Vorteile.

Zudem ist die Implementierung nicht vollständig und ihre Interfaces sind nicht allgemein verwendbar. Kein Wunder, dass außer Oracle kaum jemand den asynchronen Zugriff verwendet und das OSDL potenzielle Nutzer sucht[2].

Neue Systemcalls

Als User-API für Async-I/O implementiert Linux insgesamt fünf Systemaufrufe: »io_setup()«, »io_destroy()«, »io_ submit()«, »io_getevents()« sowie »io_ cancel()«. Mit »io_setup()« erzeugt die Applikation im Kernel einen Async-I/O-Kontext. Diesem Kontext ordnet der Kernel später I/O-Aufträge zu, sodass Anwendungen den Auftragsfortschritt erfragen können. Ein so genannter »iocb« (Input Output Control Block) beschreibt den Auftrag.

Der Systemcall »io_submit()« übergibt dem Kernel die »iocb«-Blöcke zur Abarbeitung. Die Applikation muss ihrerseits für jeden Auftrag Speicher bereitstellen, aus dem der Kernel die Daten bei einem Auftrag entnimmt respektive dort ablegt.

Speicher ausrichten

Dieser Speicherbereich muss bei asynchronem Zugriff Sector-aligned sein, also auf eine Sektorgröße ausgerichtet (genauer: ein Vielfaches von 512). Den notwendigen Speicher reserviert am einfachsten die Funktion »int posix_memalign (void **memptr, size_t alignment, size_t size)«, siehe Listing 1, Zeilen 31 und 32. Die Funktion »io_cancel()« beendet einzelne Aufträge wieder.

Schließlich benötigen Applikation und Kernel noch eine Datenstruktur, die den Auftragsstatus verwaltet: »struct io_ event«. Eine Liste dieser Event-Datenstrukturen übergibt »io_getevents()« dem Kernel. Der Systemcall blockiert aufrufende Rechenprozesse so lange, bis eine minimale Anzahl von Aufträgen abgearbeitet oder die angegebene Timeout-Zeit abgelaufen ist.

Eine Unterstützung für die vorgestellten Systemcalls sucht man in der Standard-C-Bibliothek vergebens. Wer in seiner Applikation asynchron zugreifen möchte, muss die Aufrufe per »_syscall X«-Makro selbst programmieren (siehe[3]) oder die LibAIO herunterladen und installieren (siehe[4]).

Abbildung 1: Beim synchronen Zugriff (1) schläft die Applikation, bis der Treiber die Daten zum oder vom Speicher transferiert hat. Beim asynchronen Zugriff (2) arbeiten Applikation sowie Treiber und Kernel parallel.

Abbildung 1: Beim synchronen Zugriff (1) schläft die Applikation, bis der Treiber die Daten zum oder vom Speicher transferiert hat. Beim asynchronen Zugriff (2) arbeiten Applikation sowie Treiber und Kernel parallel.

User-Bibliothek: LibAIO

Dass es den Quellcode der Library nur als RPM-Paket gibt, ist dank »alien« auch für Debian-User kein großes Problem. Der Aufruf »alien libaio-0.3.102-1.src.rpm« erzeugt ein Verzeichnis »libaio-0.3.102« und legt darin das Archiv »libaio-0.3.102.tar.gz« ab. In dem mit »tar« extrahierten Verzeichnis »/usr/src/libaio-0.3.102« kompiliert und installiert der Befehl »make prefix=/usr install« die Bibliothek. Er kopiert auch die Headerdatei »libaio.h« an die richtige Stelle. Eine Beschreibung der wesentlichen Bibliotheksfunktionen und Makros gibt der Kasten “Applikationsfunktionen der LibAIO”.

Listing 1 zeigt eine einfache LibAIO-Applikation, die aus zwei Dateien jeweils einen Datenblock liest. Ein einziger Zugriff übergibt beide Lese-Aufträge dem Kernel. Das zugehörige Makefile, das besonders das Linken mit der LibAIO organisiert, ist unter[5] zu finden. Ein Test der Applikation benötigt die beiden Dateien /tmp/foo« und »/tmp/too«.

Kernel hilft

Abbildung 2 zeigt am Beispiel eines Lese-Auftrags, wie der Kernel die Systemcalls abarbeitet. Beim Aufruf von »io_submit()« führt er die Funktion »sys_io_submit()« aus. Sie zerlegt den Auftrag in einzelne Teile und reicht sie auch einzeln an »io_submit_one()« weiter. Diese Routine verwendet die Zugriffsfunktionen (»struct file_operations«) für die zum Auftrag gehörige Datei beziehungsweise für das entsprechende Gerät.

Beim asynchronen Lesen ruft der Kernel die im »struct file_operations« abgelegte Zugriffsfunktion »aio_read()« auf. Unterstützt ein zeichenorientierter Treiber Async-I/O, hinterlegt er in dieser Struktur eine passende Funktion. Bei einem normalen Dateizugriff hat der Kernel hier bereits die Standardfunktion »generic_file_aio_read()« eingehängt.

Abbildung 2 zeigt auch, dass bei einem normalen Dateizugriff keine asynchrone Verarbeitung stattfindet. Stattdessen initiiert »do_generic_file_read()« einen synchronen Zugriff. Dabei werden die Daten nicht direkt zwischen Applikationsspeicher und Festplatte ausgetauscht, sondern über den Page-Cache. Die Funktion wartet bei einem Leseauftrag so lange, bis der Page-Cache gefüllt ist und die Daten in den Applikationsspeicher transferiert sind.

Abbildung 2: Nicht jeden mit »io_submit()« übergebenen Auftrag verarbeitet der Kernel asynchron. Aufträge ohne »O_DIRECT« laufen beispielsweise synchron ab.

Abbildung 2: Nicht jeden mit »io_submit()« übergebenen Auftrag verarbeitet der Kernel asynchron. Aufträge ohne »O_DIRECT« laufen beispielsweise synchron ab.

Listing 1:
»aio_appl.c«

01 #define _GNU_SOURCE
02 #include <stdio.h>
03 #include <stdlib.h>
04 #include <fcntl.h>
05 #include <libaio.h>
06 
07 #define BUF_SIZE (64*1024)
08 static char *buf, *buf2;
09 
10 int main( int argc, char **argv )
11 {
12    io_context_t ctxp = NULL;
13    struct iocb iocb, iocb2;
14    struct iocb *iocbs[] = { &iocb, &iocb2 };
15    struct io_event event[2];
16    int fd1, fd2;
17    struct timespec ts;
18    int result;
19 
20    if( io_setup( 1024, &ctxp ) ) {
21       perror( "io_setup" );
22       return -1;
23    }
24    fd1 = open( "/tmp/foo", O_RDWR|O_DIRECT );
25    //fd1 = open( "/tmp/aiodev", O_RDONLY );
26    fd2 = open( "/tmp/too", O_RDWR|O_DIRECT );
27    if( fd1<0 || fd2<0 ) {
28       perror("open");
29       return -2;
30    }
31    posix_memalign( (void**)&buf, 512, BUF_SIZE );
32    posix_memalign( (void**)&buf2, 512, BUF_SIZE );
33    io_prep_pread( &iocb,  fd1, buf,  BUF_SIZE, 0 );
34    io_prep_pread( &iocb2, fd2, buf2, BUF_SIZE, 0 );
35    io_submit( ctxp, 2, iocbs );
36    // ... do something else ...
37    ts.tv_sec = 5;
38    ts.tv_nsec= 0;
39    result = io_getevents( ctxp, 2, 2, &event[0], &ts );
40    printf("result= %d | result= %ld/%ld res2= %ld/%ldn", result,
41       event[0].res, event[0].res2,
42       event[1].res, event[1].res2);
43    //printf("Inhalt von buf: %sn", buf ); // Test mit "asynctest"
44    io_destroy( ctxp );
45    return 0;
46 }

Asynchrone Verarbeitung findet also nur statt, wenn die Datei mit dem Flag »O_DIRECT« geöffnet wurde. Dann greift die Applikation ohne Page-Cache auf die Daten zu (siehe Abbildung 3) und der Kernel führt »generic_file_direct_IO()« aus, das »generic_make_request()« aufruft. An dieser Stelle greift er auf die Daten zu und beendet den Systemcall »io_submit()«.

Während sich der Treiber um den Datentransfer kümmert, arbeitet die Applikation weiter. Allerdings gibt es auch bei Verwendung von »O_DIRECT« eine Einschränkung: Nur die Dateisysteme Ext 2, Ext 3, XFS und JFS unterstützen Async-Direct-I/O[6]. Liegt die Datei auf einem anderen Filesystem, schaltet der Kernel stillschweigend auf synchronen Zugriff um.

Abbildung 3: Neben dem direkten Zugriff auf zeichenorientierte Geräte gibt es beim Zugriff auf Block-Devices drei alternative Pfade.

Abbildung 3: Neben dem direkten Zugriff auf zeichenorientierte Geräte gibt es beim Zugriff auf Block-Devices drei alternative Pfade.

Character-Devices

Abbildung 4 zeigt die Abläufe innerhalb des Kernels beim Zugriff auf ein zeichenorientiertes Gerät. Der Aufruf des Systemcalls »io_submit()« (1) legt im Kernel eine Kopie des »iocb« an, die Struktur »kiocb«. Sie beschreibt den Auftrag und dient der im Treiber kodierten Funktion »aio_read()« als Argument (3). Falls »aio_read()« den Auftrag direkt (also synchron) bearbeiten kann, gibt diese Treiberfunktion »0« zurück und die Bearbeitung ist abgeschlossen.

Wahrscheinlicher ist der Fall, dass die Funktion »aio_read()« die Bearbeitung des Auftrags nur anstößt. Dann gibt sie den Wert »-EIOCBQUEUED« zurück (4). Problematisch bei der nun wirklich asynchron ablaufenden Verarbeitung ist, dass kein Userkontext mehr existiert. Stehen die angeforderten Daten bereit, kann man sie nicht einfach per »copy _to_user()« an die vorher übergebene Speicheradresse kopieren. Wahrscheinlich benutzt längst ein anderer Prozess diese Adresse.

Um dennoch Zugriff auf die Speicherbereiche der zugehörigen Applikation zu erhalten, bietet Async-I/O einen Ausweg: den Datentransfer im Kontext des »keventd« durchzuführen. Diese Lösung heißt in Kernel-Kreisen Kick-Interface.

Tabelle 1:
Applikationsfunktionen der LibAIO

 

long io_setup(unsigned nr_events, aio_context_t *ctxp)

Erzeugt einen Async-I/O-Kontext mit dem maximal
»nr_events« Aufträge überwacht werden
können. Läuft alles glatt, gibt die Funktion
»0« zurück.

long io_submit(aio_context_t ctx_id, long nr, struct iocb
**iocbb)

Übergibt dem Kernel die Liste »iocbb« mit
»nr« Aufträgen zur asynchronen Bearbeitung. Tritt
kein Fehler auf, gibt sie die Anzahl der übernommenen
Aufträge zurück.

long io_getevents(aio_context_t ctx_id, long min_nr, long nr,
struct io_events *event, struct timespec *timeout)

Blockiert den aufrufenden Thread so lange, bis mindestens
»min_nr« Aufträge ausgeführt sind.
»event« stellt eine Liste von »nr«
Strukturen vom Typ »struct io_event« dar. Die maximale
Wartezeit legt »timeout« fest. Das Ergebnis eines
Auftrags ist dann in der jeweiligen »struct io_event«
abzulesen. Hier gibt es für jeden abgeschlossenen Auftrag
gleich zwei Rückgabewerte: »res« gibt die Anzahl
der transferierten Daten an, die Bedeutung des Rückgabewerts
»res2« ist nicht festgelegt und wird von den meisten
Treibern auf »0« gesetzt.

long io_cancel (aio_context_t ctx_id, struct iocb *iocb, struct
io_event *result)

Bricht den in »iocb« angegebenen Auftrag ab. Tritt
kein Fehler auf, gibt die Funktion »0«
zurück.

long io_destroy (aio_context_t ctx)

Gibt den Async-I/O-Kontext »ctx« wieder frei. Sind
zum Zeitpunkt der Freigabe noch Aufträge in Bearbeitung,
versucht der Kernel sie abzubrechen. Solange aber nicht alle
Aufträge abgeschlossen sind, blockiert die Funktion. Sie gibt
»0« zurück, wenn kein Fehler auftritt.

void io_prep_pread(struct iocb *iocb, int fd, void *buf, size_t
count, long long offset)

Diese Inline-Funktion initialisiert den »iocb«
für einen Leseauftrag.

void io_prep_pwrite(struct iocb *iocb, int fd, void *buf,
size_t count, long long offset)

Diese Inline-Funktion initialisiert den »iocb«
für einen Schreibauftrag.

Kicker im Kernel

Läuft die Verarbeitung im Keventd-Kontext ab, sorgt Async-I/O dafür, dass das Memory-Management zum entsprechenden Zeitpunkt die für den Auftrag korrekte Speicherzuordnung wiederherstellt. Um diese Möglichkeit zu nutzen, muss der Entwickler eine so genannte Retry-Funktion zur Verfügung stellen, die im Userkontext arbeitet. Ein Aufruf der Methode »kick_iocb()« aktiviert diese Retry-Funktion.

Sobald der (asynchron stattfindende) Datentransfer (5) abgeschlossen ist, ruft der Treiber die Funktion »aio_complete()« auf. Dies kann beispielsweise in der »retry()«-Funktion geschehen. Sie signalisiert dem Kernel, dass der Treiber den Auftrag abgeschlossen hat.

Die Methode »aio_complete()« erwartet zwei Rückgabewerte: Der erste steht für die Anzahl der übertragenen Zeichen, der zweite ist nicht weiter spezifiziert und wird üblicherweise gleich »0« gesetzt. Während der Kernel die Rückgabewerte in der IO-Event-Datenstruktur zwischenspeichert (6), kann der Treiber den »kiocb« wieder freigeben. Für ihn ist an dieser Stelle nämlich die Bearbeitung des Auftrags beendet.

Die Applikation kann zu jeder Zeit den Zustand des Auftrags über den Systemcall »io_getevents()« abfragen (7). Ist der Auftrag beendet, werden die in den Event-Strukturen abgelegten Rückgabewerte in den Userspace kopiert (8).

Abbildung 4: Bei Async-I/O muss der Zugriff auf die Speicherbereiche der Applikation im Interrupt- oder Prozesskontext stattfinden. Im Treiber sorgt »driver_retry()« fürs Abholen der angeforderten Daten.

Abbildung 4: Bei Async-I/O muss der Zugriff auf die Speicherbereiche der Applikation im Interrupt- oder Prozesskontext stattfinden. Im Treiber sorgt »driver_retry()« fürs Abholen der angeforderten Daten.

Funktion zum Abbrechen

Wer in einem Treiber den asynchronen Zugriffsmodus unterstützen möchte, muss also im Wesentlichen die beiden Funktionen »aio_read()« und »aio_ write()« implementieren. Falls der Treiber auch den Systemcall »io_cancel()« beherrschen soll, muss er zusätzlich eine Cancel-Funktion implementieren und die Adresse dieser Funktion im »kiocb« des Auftrags ablegen (siehe Listing 2, Zeile 47). Die Cancel-Funktion selbst führt zum einen die im Treiber notwendigen Aktionen zum Rückruf des Auftrags aus. Zum anderen kennzeichnet sie die Struktur »kiocb« als »cancelled« (abgebrochen). Beim Programmieren dieser Funktion hilft das Makro »kiocbSetCancelled()«.

Der in Listing 2 dargestellte Treiber implementiert nur die Lesefunktion, aber inklusive Cancel. Er simuliert das asynchron arbeitende Gerät durch eine Timerfunktion, die zwei Sekunden nach Auftragsvergabe die Daten in den Userspace kopiert. Um die Implementierung einfach zu halten, legt das Beispiel die Aufrufparameter »buf« und »count« in zwei globalen Variablen ab. Ein echter Treiber müsste diese Parameter in einer separaten Datenstruktur speichern. Übersetzen lässt sich der Treiber nur mit einem aktuellen Kernel (etwa Version 2.6.8.1). Das zugehörige Makefile findet sich unter[5]. Nach dem Übersetzen und Laden des Treibers fehlt noch eine Gerätedatei mit der Major-Nummer 240:

make
insmod asynctest.ko
mknod /tmp/aiodev c 240 0

Der Code lässt sich mit der Applikation aus Listing 1 testen. Es ist allerdings nötig, das Öffnen der Datei »/tmp/foo« auszukommentieren (Zeile 24) und dafür das Öffnen von »/tmp/aiodev« (Zeile 25) einzufügen. Wichtig ist, nicht das Flag »O_DIRECT« anzugeben.

Der in Listing 2 dargestellte Treiber nutzt das schon beschriebene Kick-Interface. Wer es vorzieht, direkt vom Treiber aus auf den Speicher der Applikation zuzugreifen, findet Hinweise dazu im Kasten “Kernelzugriff auf Speicherbereiche im Userspace”.

Kernelzugriff auf
Speicherbereiche im Userspace

Mit ein paar Tricks lassen sich auch ohne Userkontext Daten zwischen Kernel- und Userspace austauschen. Dazu muss man wissen, dass Linux den gesamten Hauptspeicher in Speicherseiten (Pages) einer festen Größe, meist 4096 Bytes, organisiert (siehe dazu [8]). Folgende Aktionen sind im Kernel durchzuführen:

  • Im Userkontext lockt der Programmierer jene Speicherseite des
    physischen Speichers, die die Applikation nutzt. Damit ist sicher,
    dass sich die Seite im physisch vorhandenen Hauptspeicher befindet.
    Außerdem benötigt der Treiber für den späteren
    Zugriff deren (physische) Adresse.
  • Sowohl das Locking als auch die Identifikation der physischen
    Adresse sind über die Funktion »get_user_pages()«
    möglich. Neben der Page-Adresse wird später auch der
    Offset innerhalb der Seite benötigt. Dieser ist wie folgt zu
    berechnen (»buf« ist die virtuelle Adresse im
    Userspace):

    offset = (unsigned long)buf & ~PAGE_MASK;

    Je nach zu übertragender Datenmenge muss der Programmierer mit
    »get_user_pages()« mehrere Seiten anfordern. Der Bedarf
    ist vorher zu berechnen und entsprechend viel Speicherplatz
    für die Zeiger auf die Pages zu reservieren (siehe [9]).

  • Zum Zeitpunkt des Kopierens zwischen Kernel und Userspace
    braucht das Programm die gültige virtuelle (Kernel-) Adresse.
    Dazu dient die Funktion »page_ address(struct page *)«.
    Der Zugriff selbst erfolgt mit Hilfe der Funktion
    »memcpy()«. Achtung: den Offset nicht vergessen!
    Außerdem ist sicherzustellen, dass sich die Speicherseiten
    nicht im Highmem befinden. Gegebenenfalls schafft
    »kmap()« hier Abhilfe. Falls Datenmengen kopiert
    werden, die sich über mehr als eine Page verteilen, ist das
    Kopieren für jede Seite durchzuführen.
  • Die Speicherseite muss zuerst als verwendet markiert,
    später aber wieder freigegeben werden, sodass der Kernel sie
    bei Bedarf auslagern kann. Dazu dienen die Funktionen
    »SetPageDirty()« und
    »page_cache_release()«.
  • Das Markieren und Freigeben muss der Programmierer für jede zuvor gelockte Speicherseite durchführen.

Das Markieren und Freigeben muss der Programmierer für jede zuvor gelockte Speicherseite durchführen.

Fazit

Insgesamt wird deutlich, dass die gegenwärtige Implementierung von Async-I/O im Kernel 2.6 noch nicht zufrieden stellend ist. Angefangen bei Schwächen des Userinterface bis zum problematischen direkten Datentransfer zwischen asynchron arbeitender Applikation und dem Kernel gibt es noch einige Optimierungsmöglichkeiten. Da Async-I/O zudem bis jetzt keine spektakulären Performance-Wunder vollbringt, ist die gegenwärtige Zurückhaltung der Anwender nicht überraschend.

Auch die bereits vorhandene Reimplementierung[7], die den Zugriff auf gepufferte Dateien ermöglicht, weckt wenig Hoffnung: Erste Performance-Tests waren alles andere als Erfolg versprechend und Linus Torvalds ist folglich zurückhaltend. Die grundlegenden Probleme scheinen damit nämlich nicht beseitigt zu sein. Vielleicht ist die seit jeher vorhandene synchrone Ein- und Ausgabe in Linux auch einfach zu gut implementiert.

Bei aller Kritik sollte man allerdings nicht übersehen: Die Datenbankhersteller sind durchaus zufrieden. Ihre Anwendungen können dank geschickter Programmierung spürbare Performance-Steigerungen verbuchen. Mit den Einschränkungen können sie gut leben, da sie ohnehin nur direkt auf die Daten zugreifen. (ofr)

Listing 2:
»asynctest.c«

01 #include <linux/module.h>
02 #include <linux/fs.h>
03 #include <asm/uaccess.h>
04 #include <linux/mm.h>
05 
06 static wait_queue_head_t read_wq;
07 static struct timer_list ptimer;
08 static void *copy_to;
09 static unsigned long max_copy;
10 
11 void timer_function( unsigned long p )
12 {
13    struct kiocb *iocb = (struct kiocb *)p;
14 
15    if( !kiocbIsCancelled( iocb ) ) {
16       kick_iocb(iocb);
17    } else {
18       printk("iocb is cancelled ...n");
19    }
20    return;
21 }
22 
23 static int driver_cancel( struct kiocb *iocb, struct io_event *event )
24 {
25    printk("driver_cancel ...n");
26    del_timer( &ptimer );
27    kiocbSetCancelled( iocb );
28    aio_complete( iocb, -1, 0 );
29    return 0;
30 }
31 
32 static long driver_retry( struct kiocb *iocb )
33 {
34    printk("driver_retry ...n");
35    if( copy_to )
36            copy_to_user(copy_to, "Hello World",(max_copy>12)?12:max_copy);
37    aio_put_req(iocb);
38    return (max_copy>12)?12:max_copy;
39 }
40 
41 static ssize_t driver_aioread(struct kiocb *iocb, char __user *buf,
42         size_t count, loff_t offset)
43 {
44    printk( "driver_aioread(%p,%p,%d)n", iocb, buf, count );
45    copy_to  = (void *)buf;
46    max_copy = count;
47    iocb->ki_cancel = driver_cancel;
48    iocb->ki_retry = driver_retry;
49    ptimer.function = timer_function;
50    ptimer.data = (unsigned long)iocb;
51    ptimer.expires = jiffies + (2*HZ);
52    add_timer( &ptimer );
53    return -EIOCBQUEUED;
54 }
55 
56 static struct file_operations fops = {
57    .owner=THIS_MODULE,
58    .aio_read=driver_aioread,
59 };
60 
61 static int __init async_init(void)
62 {
63    if(register_chrdev(240, "async_test", &fops) == 0) {
64       init_waitqueue_head( &read_wq );
65       init_timer( &ptimer );
66       return 0;
67    };
68    return -EIO;
69 }
70 
71 static void __exit async_exit(void)
72 {
73    del_timer_sync( &ptimer );
74    unregister_chrdev(240,"async_test");
75 }
76 
77 module_init( async_init );
78 module_exit( async_exit );
79 MODULE_LICENSE("GPL");

Infos

[1] Chinmay Albal, “Comparative study of the Asynchronous I/O programming interfaces on Linux”: Proceedings of the 11th International Linux-Kongress 2004, Erlangen 2004

[2] Jonathan Corbet, “Kernel Summit: Asynchronous I/O”: [http://lwn.net/Articles/94566/]

[3] Eva-Katharina Kunst und Jürgen Quade: “Kern-Technik”, Folge 13: Linux-Magazin 8/04, S. 92

[4] Quellcode der LibAIO: [http://rpmseek.com/rpm-pl/libaio.html?hl=de&cs=libaio:PN:0:0:0:0]

[5] Listings und Makefile: [https://www.linux-magazin.de/Service/Listings/2005/02/Kern-Technik/]

[6] Kernel Asynchronous I/O (AIO) Support: [http://lse.sourceforge.net/io/aio.html]

[7] Suparna Batthacharya: Patches für Async-I/O: [http://www.kernel.org/pub/linux/kernel/people/suparna/aio/]

[8] Stefan Klett, “Virtual Memory. So verwaltet der Linux-Kernel den Speicher”: Linux-Magazin 09/03, S. 91

[9] Jonathan Corbet, ” Driver porting: Zero-copy user-space access”: [http://lwn.net/Articles/28548]

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