5.3. Die Treiber-Einsprungspunkte

Verwendet eine Applikation einen Systemcall, der eine Interaktion mit einem Gerät zur Folge hat, wird durch den Betriebssystemkern im Treiber eine korrespondierende Funktion aufgerufen (die so genannten applikationsgetriggerten Treiberfunktionen). Die Namen dieser Treiber-Einsprungspunkte sind zwar frei wählbar, wir verwenden aber folgendes Schema: Der Name bildet sich aus dem Namensvorsatz »driver« und dem Namen des zugehörigen Systemcalls (zum Beispiel »read« für read). Dies ergibt die folgenden Funktionsnamen:

Die Adressen dieser, vom Treiberentwickler bereitzustellenden, Funktionen werden in eine Struktur vom Typ struct file_operations abgelegt. Beispiel Die Struktur struct file_operations zeigt die Elemente dieser Datenstruktur, von denen später die Wesentlichen vorgestellt werden sollen.

Beispiel 5-6. Die Struktur struct file_operations

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, struct dentry *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
    ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
    ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long,
        unsigned long, unsigned long, unsigned long);
};

Abbildung 5-2. Einbindung des Gerätetreibers in das System

Die Abbildung Einbindung des Gerätetreibers in das System verdeutlicht noch einmal die dargestellten Komponenten und Abläufe.

Der Treiber kann nur verwendet werden, wenn im Dateisystem eine Zuordnung zwischen dem symbolischen Gerätenamen und der Majornumber, die innerhalb des Kernels den Treiber identifiziert, angelegt wurde. Hierzu dient das mknod-Kommando. Außerdem muss der Treiber überhaupt geladen sein. Dies geschieht über insmod. Beim Laden des Treibers wird zunächst die Treiber-Funktion init_module aufgerufen. Diese Funktion hat die Aufgabe, das zugehörige Gerät zu finden und schließlich den Treiber beim System anzumelden (register_chardev).

Anhand des beim open-Call übergebenen Dateinamens erkennt das Betriebssystem, über welchen Treiber die Applikation auf ein Gerät zugreifen möchte. Dieser Dateiname stellt im Filesystem die Zuordnung zwischen dem symbolischen Gerätenamen und der zugehörigen Majornumber her. Der Aufruf des Systemcalls open durch die Applikation führt dazu, dass der Kernel

Die Instanz der Datenstruktur struct file repräsentiert die zugreifende Applikation und wird im Folgenden mit Treiberinstanz bezeichnet (vergleiche Treiberinstanzen). Eine Treiberinstanz kennzeichnet einen Rechenprozess. Falls der Rechenprozess mehrfach das Gerät öffnet, umfasst er mehrere Treiberinstanzen.

Die Aufgabe der driver_open-Funktion besteht darin, zu überprüfen, ob die Applikation auf das Gerät zugreifen darf oder nicht. Darf die Applikation zugreifen, gibt die Treiber-Funktion »0«, ansonsten einen sinnvollen Fehlercode zurück. Der Rückgabewert wird der Applikation durch den Betriebssystemkern als Ergebnis des open-Systemcalls übergeben.

Konnte das Gerät »geöffnet« werden, greift die Applikation über den beim Öffnen zurückgegebenen Dateideskriptor über die Funktionen read und write zu.

Schließt die Applikation das Gerät beispielsweise durch Aufruf des Systemcalls close wieder, wird wiederum eine korrespondierende Funktion im Treiber aufgerufen (driver_close). Die Adresse dieser Funktion muss in der Struktur struct file_operations in der Variablen mit Namen release abgelegt werden. Auch wenn ansonsten die Namen der Variablen in dieser Struktur mit den Namen der Funktionen am Applikationsinterface übereinstimmen – in diesem Fall wurde eine Ausnahme gemacht.

5.3.1. driver_open: die Zugriffskontrolle

Ruft die Applikation ein open auf, überprüft der Kernel zunächst, ob durch den Aufruf dieser Funktion irgendwelche Zugriffsrechte, die mit der Gerätedatei assoziiert sind, verletzt werden. Ist dies der Fall, teilt der Betriebssystemkern dies der Applikation in Form eines negativen Fehlercodes mit. Ansonsten ruft der Kernel die zugehörige driver_open-Funktion im Treiber auf. Dieser Funktion werden zwei Parameter übergeben:

  1. struct file *instanz

  2. struct inode *geraetedatei

Die Struktur struct file *instanz enthält sämtliche Elemente, die die aufrufende Treiberinstanz spezifizieren. Innerhalb des Treibers werden Sie auf diese Struktur zugreifen:

  1. um festzustellen, in welchem Modus der Zugriff erfolgt, ob lesend, schreibend oder lesend und schreibend und

  2. um auszuwerten, ob die Treiberinstanz, also der Rechenprozess, einen blockierenden oder einen nicht blockierenden Zugriff durchführt.

Die Struktur struct inode *geraetedatei enthält sämtliche Elemente, die die zugehörige Gerätedatei spezifizieren. Dazu gehören die Zugriffsrechte ebenso wie Informationen über den Besitzer (Owner) der Datei. Der häufigste Grund, auf die Elemente dieser Datenstruktur zuzugreifen, besteht darin, die Major-, aber vor allem auch die Minor-Nummer auszulesen. Hiermit können Sie feststellen, über welche Gerätedatei die Applikation zugreift.

Der Funktion driver_open fällt die wichtige Aufgabe zu, zu überprüfen, ob die zugreifende Treiberinstanz zum Zugriff berechtigt ist. Die Regeln, ob eine Treiberinstanz legitimiert ist oder nicht, legt der Treiberentwickler fest. Darüber hinaus führt die Funktion noch Initialisierungen durch. In Kapitel Treiberinstanzen wird beispielsweise gezeigt, wie die Struktur struct file innerhalb der driver_open-Funktion um instanzenspezifische Parameter erweitert wird.

Nicht jeder Treiber muss driver_open kodieren. Ist keine solche Funktion vorhanden, geht der Kernel davon aus, dass der Zugriff auf den Treiber erlaubt ist. Dies entspricht der Implementierung einer Funktion, bei der bei jedem Aufruf grundsätzlich der Wert »0« zurückgegeben wird:

static int driver_open( struct inode *geraetedatei, struct file *instanz )
{
    return 0;
}

Ein Beispiel soll die Implementierung dedizierter Zugriffsrechte verdeutlichen. So soll ein Treiber realisiert werden, der genau einer Instanz den schreibenden Zugriff, beliebig vielen Treiberinstanzen aber den lesenden Zugriff gestattet. Für die Realisierung muss der Treiberentwickler wissen, dass im Feld f_flags der Struktur struct file die Zugriffsflags abgelegt sind. Die Zugriffsarten selbst sind im Kernel genauso kodiert wie in der Applikation. Mit diesem Wissen ergibt sich die in Beispiel Dedizierter Zugriffsschutz in driver_open vorgestellte Realisierung der Funktion driver_open.

Beispiel 5-7. Dedizierter Zugriffsschutz in driver_open

static int driver_open( struct inode *geraetedatei, struct file *instanz )
{
    if( instanz->f_flags&O_RDWR || instanz->f_flags&O_WRONLY ) {
        if( write_count > 0 ) {
            return -EBUSY;
        }
        write_count++;
    }
    return 0;
}

In einem zweiten Beispiel soll ein Codefragment vorgestellt werden, welches den Zugriff auf den Treiber über unterschiedliche Gerätedateien veranschaulicht. Die unterschiedlichen Gerätedateien sind anhand der Minornumber erkennbar. Die Minornumber wiederum ist in der vom Kernel der Funktion driver_open übergebenen Struktur struct inode zu finden. Hier ist es jedoch nicht notwendig, den genauen Feldnamen zu kennen. Vielmehr stellt der Kernel mit iminor eine einfach verwendbare Zugriffsfunktion zur Verfügung:

static int driver_open( struct inode *geraetedatei, struct file *instanz )
{
    if( iminor(geraetedatei)==0 ) {
        ... // Zugriff über die Gerätedatei mit der Minor-Nummer 0
    } else {
        ... // Zugriff über alle anderen Gerätedateien
    }
    ...

Für den Zugriff auf die Majornumber existiert ebenfalls eine eigene Funktion: imajor.

5.3.2. Aufräumen in driver_close

Ruft die Applikation ein close auf, wird treiberseitig die korrespondierende Funktion driver_close angestoßen, deren Adresse innerhalb der struct file_operations unter dem Namen release abgelegt ist.

Aufgabe von driver_close alias release ist es, die mit der Treiberinstanz assoziierten Ressourcen freizugeben. Fallen jedoch beim Schließen der Gerätedatei keinerlei Aufgaben für den Treiber an – würde die Funktion driver_close also nur »0« zurückgeben – muss die Funktion nicht kodiert werden.

Im Folgenden soll die zum Beispiel Dedizierter Zugriffsschutz in driver_open passende driver_close-Funktion vorgestellt werden. Da beim driver_open die Anzahl zugreifender Instanzen aufaddiert wird, muss diese bei Aufruf von driver_close wieder nach unten korrigiert werden:

static int driver_close( struct inode *geraetedatei, struct file *instanz )
{
    if( instanz->f_flags&O_RDWR || instanz->f_flags&O_WRONLY ) {
        write_count--;
    }
    return 0;
}

5.3.3. driver_read liefert die Daten

Wird in der Applikation der Systemcall read aufgerufen, führt dies dazu, dass im zugehörigen Treiber ebenfalls eine read-Funktion (driver_read) aktiviert wird. Die in der Applikation übergebenen Parameter, nämlich die Adresse eines Speicherbereiches (Buffer) im User-Space und die maximal zu lesende Anzahl Bytes, werden direkt an den Treiber weitergereicht.

Da das Memory-Management die Speicherbereiche der Applikation und des Kernels voreinander schützt, darf der Treiber auf den von der Applikation übergebenen Buffer nicht direkt zugreifen. Der Zugriff erfolgt vielmehr über Funktionen, die im Kapitel Daten zwischen Kernel- und User-Space transferieren ausführlich vorgestellt werden.

Der Prototyp der read-Funktion sieht folgendermaßen aus:

static ssize_t driver_read( struct file *instanz, char __user *userbuffer, size_t count,
            loff_t *offset )

Im Treiber lauten die Parameter, die die Applikation bereitgestellt hat, »userbuffer« (Adresse des Speicherbereichs im User-Space) und »count« (maximale Anzahl zu kopierender Bytes).

Der Parameter »instanz« kennzeichnet die zugehörige Treiberinstanz. Über diesen Parameter kann beispielsweise festgestellt werden, auf welches logische Gerät (erkennbar an der Minornumber) zugegriffen wird und ob der Zugriff blockierend oder nicht blockierend erfolgt.

Der Parameter »offset« ist ein Zeiger auf den Offset, auf den innerhalb des Gerätes zugegriffen werden soll. Die Applikation beeinflusst diesen Parameter durch den Systemcall lseek. In vielen Fällen spielt dieser Parameter keine Rolle.

Die driver_read-Funktion hat zur Aufgabe, die vom Gerät angeforderten Daten in den Speicherbereich der Applikation zu kopieren. Dabei ist darauf zu achten, dass nicht mehr Bytes in den übergebenen Buffer kopiert werden, als dieser überhaupt zur Verfügung stellt. Das eigentliche Kopieren wird über eine Funktion (copy_to_user) angestoßen. Diese Funktion kopiert die Daten nur, falls die Adresse, die die Applikation übergeben hat, auf einen gültigen (existenten) Speicherbereich zeigt.

Der Rückgabewert der Funktion copy_to_user ist die Anzahl Bytes, die nicht kopiert werden konnte.

Fordert eine Applikation Daten an, obwohl zum gegenwärtigen Zeitpunkt keine solchen vorhanden sind, reagiert driver_read abhängig vom Zugriffsmodus der Treiberinstanz. Greift die Treiberinstanz nicht blockierend zu, wird der Fehlercode »-EAGAIN« (definiert in der Datei <asm/errno.h>) zurückgegeben. Befindet sich dagegen die Treiberinstanz im blockierenden Modus, muss die driver_read-Funktion den zugehörigen Rechenprozess (Task oder Thread) in den Zustand schlafend versetzen. Dies wird im Kapitel Zugriffsmodi im Treiber realisieren erläutert.

Konnten Daten in den Applikationsbereich kopiert werden, wird die Anzahl der kopierten Daten zurückgegeben. Sind keine Daten vorhanden, die kopiert werden können, wird »0« zurückgegeben, was soviel wie End Of File (EOF) bedeutet.

Mit den bisherigen Kenntnissen lassen sich bereits einfache Treiber erstellen. Im Beispiel Hello-World-Treiber stellt der Treiber ein virtuelles Gerät zur Verfügung, das bei einem lesenden Aufruf den String »Hello World« zurück gibt.

Beispiel 5-8. Hello-World-Treiber

/* vim: set ts=4: */
#include <linux/fs.h>
#include <linux/version.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/uaccess.h>                                   (1)

#define DRIVER_MAJOR 240

// Metainformation
MODULE_AUTHOR("Eva-Katharina Kunst");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A virtual device, which returns hallo.");
MODULE_SUPPORTED_DEVICE("none");

static char hello_world[]="Hello World\n";                 (2)

static int driver_open( struct inode *geraete_datei, struct file *instanz )
{
    printk("driver_open called\n");                        (3)
    return 0;
}

static int driver_close( struct inode *geraete_datei, struct file *instanz )
{
    printk("driver_close called\n");
    return 0;
}

static ssize_t driver_read( struct file *instanz, char *user, size_t count,
            loff_t *offset )
{
    int not_copied, to_copy;

    to_copy = strlen(hello_world)+1;
    if( to_copy > count )                                  (4)
        to_copy = count;
    not_copied=copy_to_user(user,hello_world,to_copy);     (5)
    return to_copy-not_copied;                             (6)
}

static struct file_operations fops = {                     (7)
    .owner= THIS_MODULE,
    .read= driver_read,
    .open= driver_open, 
    .release= driver_close,
};

static int __init mod_init(void)
{
    if(register_chrdev(DRIVER_MAJOR, "Hello", &fops) == 0) (8)
        return 0;
    printk("register_chrdev failed!\n");
    return -EIO;
}

static void __exit mod_exit(void)
{
    unregister_chrdev(DRIVER_MAJOR,"Hello");               (9)
}

module_init( mod_init );
module_exit( mod_exit );

	
(1)
Diese Header-Datei enthält die Prototypen für die Funktionen copy_to_user und copy_from_user.
(2)
Hier werden die Daten des virtuellen Gerätes im Hauptspeicher definiert.
(3)
Rechenprozesse haben uneingeschränkten Zugriff auf den Treiber. Daher kann direkt »0« zurückgegeben werden.
(4)
In der Read-Funktion wird nur der Längenparameter überprüft und gegebenenfalls angepasst.
(5)
Wie viele Bytes nicht kopiert werden konnten, muss protokolliert werden, damit die richtige Anzahl der Bytes zurückgegeben wird, die die Treiberfunktion kopiert hat.
(6)
Hier wird die Anzahl der kopierten Bytes zurückgegeben.
(7)
Mit dieser Datenstruktur werden dem Kernel die Einsprungsfunktionen in den Treiber übergeben. Wird diese Datenstruktur zu Beginn des Quelltextes definiert, müssen die Prototypen der Einsprungsfunktionen vorher angegeben werden. Weil hier die Definition nach den Funktionen erfolgt, werden keine Prototypen benötigt.
(8)
Hier meldet sich das Modul als Treiber beim Kernel an.
(9)
Der Treiber muss sich ordnungsgemäß beim Kernel abmelden.

Um den Treiber testen zu können, muss dieser zunächst generiert werden. Hat der Entwickler ein geeignetes Makefile (siehe Beispiel Makefile zum Hello-World-Treiber) erstellt, reicht der Aufruf von make aus.

Beispiel 5-9. Makefile zum Hello-World-Treiber

ifneq ($(KERNELRELEASE),)
obj-m   := hello.o

else
KDIR    := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

default:
    $(MAKE) -C $(KDIR)  SUBDIRS=$(PWD) modules
endif

Heißt die Quelldatei des Treibers »hello.c«, so heißt das zu ladende Modul »hello.ko«. Der Superuser hat das Recht, das Kommando insmod auszuführen.

root# insmod hello.ko
root#

Mit Hilfe des Kommandos lsmod lässt sich verifizieren, ob das Laden erfolgreich war oder nicht. Konnte das Modul nicht geladen werden, liegt das vielleicht daran, dass bereits ein Treiber geladen ist, der die gleiche Major-Nummer verwendet. Entweder ist dieses bereits geladene Modul zu entladen oder eine andere, freie Majornumber zu verwenden.

Ist der Treiber erfolgreich geladen, kann er ausgetestet werden. Dazu muss der Treiberentwickler, mit Superuser-Rechten ausgestattet, zunächst die Gerätedatei anlegen:

root# mknod hellodevice c 240 0
root#

Die Majornumber muss natürlich mit der Majornumber übereinstimmen, unter der sich der Treiber beim Kernel angemeldet hat. Da bei dem Hello-World-Treiber die Minornumber nicht ausgewertet wird, kann hier jede beliebige Minornumber verwendet werden.

Damit die Treiberfunktionen getriggert werden können, muss eine Applikation auf den Treiber zugreifen. Es ist jedoch nicht zwangsläufig notwendig, eine eigene Applikation zu schreiben, die die Systemcalls open und read aufruft. Vielmehr kann auf Applikationen zurückgegriffen werden, die auf jedem Unix-System enthalten sind, wie beispielsweise cat

$ cat hellodevice

Das Programm cat liest so lange aus der angegebenen Datei, bis es End Of File (0 Bytes gelesen) zurückbekommt. Da der Hello-World-Treiber jedoch ständig die Länge des Strings »Hello World« zurückgibt, bricht cat nicht von alleine ab. Dies muss der Anwender mit der Tastenkombination »CTRL C« selbst erledigen.

Auch innerhalb der Funktion driver_read kann die Minornumber ausgelesen werden. Der Übergabeparameter struct file enthält eine Referenz auf die zugehörige Gerätedatei. Der Zugriff sieht damit folgendermaßen aus:

static ssize_t minor_read( struct file *instanz, char __user *userbuffer, size_t count,
    loff_t *offs )
{
    int minor_number = iminor(instanz->f_dentry->d_inode);
    ...

5.3.4. Schreibzugriffe im Treiber

Eine Applikation, die Daten ausgeben möchte, ruft den Systemcall write auf. Dieser Systemcall triggert die write-Funktion im Treiber, die hier als driver_write bezeichnet wird.

Der Prototyp dieser Funktion sieht folgendermaßen aus:

static ssize_t driver_write( struct file *instanz, const char __user *userbuffer, size_t count,
    loff_t *offs)

Die Parameter haben die gleiche Bedeutung wie bei der driver_read-Funktion. Der Parameter »instanz« repräsentiert sowohl die zugreifende Treiberinstanz mit dem logischen Gerät, welches über die Minornumber identifiziert wird, als auch den Zugriffsmodus (blockierend oder nicht blockierend). Der Parameter »userbuffer« enthält die Adresse des Speicherbereichs im User-Space, aus dem die Daten, die geschrieben werden sollen, stammen. Diese Adresse entspricht exakt der Adresse, die in der Applikation beim Aufruf des Systemcalls write angegeben wurde. Beim Parameter »count« handelt es sich um die Anzahl der Bytes, die geschrieben werden sollen. Der Parameter stammt ebenfalls direkt aus der zugreifenden Applikation.

Der Parameter »offs« gibt an, ab welchem Offset innerhalb des Gerätes (falls ein Gerät intern mehrere Adressen zur Verfügung stellt) die Daten abgelegt werden sollen. Dieser Parameter wird von den meisten zeichenorientierten Gerätetreibern nicht ausgewertet.

Um die Daten aus der Applikation in den Treiber zu bekommen, kann der Treiberentwickler auf die Funktion copy_from_user zurückgreifen. Die Funktion überprüft, ob die von der Applikation übergebene Adresse gültig ist. Daraufhin kopiert sie die Daten in einen Speicherbereich, den der Treiber angeben kann. Die Funktion copy_from_user gibt die Anzahl der Bytes zurück, die nicht kopiert werden konnten. Ist alles gut gegangen, gibt die Funktion also »0« zurück.

Die Funktion driver_write selbst gibt – ähnlich wie driver_read – die Anzahl der geschriebenen Bytes zurück. Falls keine Daten geschrieben werden konnten, returniert sie »0«.

Mit Hilfe dieser Kenntnisse ist das Schreiben eines Codes, der das logische Gerät /dev/null implementiert, trivial. /dev/null hat die Eigenschaft, alle übergebenen Daten ins »Nichts« zu schreiben. Für eine solche Funktionalität muss driver_write allein die Anzahl der zu schreibenden Bytes zurückgeben (siehe Beispiel Implementierung des logischen Gerätes /dev/null).

Beispiel 5-10. Implementierung des logischen Gerätes /dev/null

static ssize_t driver_write( struct file *instanz, char __user *userbuffer,
    size_t count, loff_t *offset )
{
    return count;
}

5.3.5. Die Universalschnittstelle IO-Control

Der Aufruf der Funktion IO-Control bzw. der daraus resultierende Aufruf des zugehörigen Systemcalls triggert die IO-Control-Funktion im Treiber, die von uns driver_ioctl genannt wird.

Die typische Struktur einer driver_ioctl-Funktion ist durch eine Switch-Case-Anweisung gekennzeichnet. Abhängig vom Kommando, welches der Funktion ioctl durch die Applikation mit übergeben wurde, werden die zusätzlichen Parameter ausgewertet und die zugehörige Aktion im Treiber durchgeführt. Auch wenn es gemäß Prototyp möglich ist, zu einem IO-Control-Kommando mehrere Parameter zu spezifizieren, ist dies in der Praxis unüblich. Vielmehr wird eine zum Kommando gehörige Datenstruktur definiert und der Zeiger auf diese Datenstruktur als Parameter beim Aufruf mit übergeben.

Der zum Kommando gehörige Parameter ist per definitionem vom Typ unsigned long, so dass innerhalb der Funktion driver_ioctl in vielen Fällen eine Typwandlung (z.B. per Typecasting) durchgeführt werden muss. Beispiel Implementierung einer driver_ioctl-Funkion zeigt die Implementierung im Treiber für ein IO-Control IOCTL_BAUDRATE, Beispiel Komplettbeispiel IO-Control zeigt einen vollständigen, funktionstüchtigen Treiber.

Beispiel 5-11. Implementierung einer driver_ioctl-Funkion

static int my_ioctl( struct inode *geraetedatei, struct file *instanz,
       unsigned int cmd, unsigned long arg )
{
    unsigned int baudrate;

    switch( cmd ) {                                        (1)
    case IOCTL_BAUDRATE:
       copy_from_user( (void *)&baudrate, (void *)arg, sizeof(baudrate) );(2)
       set_new_baudrate( baudrate );
       break;
    default:
       printk("unknown IOCTL 0x%x\n", cmd);
       return -EINVAL;
    }
    return 0;
}
(1)
Die IO-Control-Kommandos werden im Regelfall über eine Switch-Anweisung differenziert.
(2)
Der Treiberentwickler hat festgelegt, dass das Kommando IOCTL_BAUDRATE genau einen zusätzlichen Parameter, nämlich die Baudrate, besitzt. Dieser Parameter ist vom Typ unsigned int. Beim Kopieren des Datums aus dem User-Space in den Kernel-Space mit Hilfe der Funktion copy_from_user wird der Typ von unsigned long auf unsigned int gewandelt.

Im fehlerfreien Fall – das Kommando war gültig und konnte erfolgreich ausgeführt werden – gibt die Funktion driver_ioctl »0« zurück. Ein negativer Wert bedeutet, dass ein Fehler aufgetreten ist.

Die beiden häufigsten Fehlercodes für IO-Control lauten

  1. -EFAULT: das Argument zeigt auf einen ungültigen Speicherbereich und

  2. -EINVAL: das Kommando oder der bzw. die zugehörigen Parameter sind falsch.

Beispiel 5-12. Komplettbeispiel IO-Control

#include <linux/fs.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <asm/uaccess.h>

MODULE_LICENSE("GPL");

#define IOCTL_MAJOR 240
#define IOCTL_GETVALUE 0x0001

static int driver_ioctl( struct inode *inode, struct file *instanz,
    unsigned int cmd, unsigned long arg)
{
    printk("ioctl called 0x%4.4x %p\n", cmd, (void *)arg );
    switch( cmd ) {
    case IOCTL_GETVALUE:
        copy_to_user( (void *)arg, "Hollahop\n", 10 );
        break;
    default:
        printk("unknown IOCTL 0x%x\n", cmd);
        return -EINVAL;
    }
    return 0;
}

static struct file_operations ioctl_fops = {
    .owner= THIS_MODULE,
    .ioctl= driver_ioctl,
};

static int __init ioctl_init(void)
{
    if(register_chrdev(IOCTL_MAJOR, "ioctl-Driver", &ioctl_fops) == 0) {
        return 0;
    };
    printk("ioctl: unable to get major %d\n",IOCTL_MAJOR);
    return -EIO;
}

static void __exit ioctl_exit(void)
{
    printk("cleanup_module called\n");
    unregister_chrdev(IOCTL_MAJOR,"ioctl-Driver");
}

module_init( ioctl_init );
module_exit( ioctl_exit );

Die Kernelentwickler haben ein Schema festgelegt, wie die IO-Control Kommandos kodiert werden sollen. Eine kurze Beschreibung dazu findet sich im Abschnitt Mit Stil programmieren.

5.3.6. Wenn Applikationen mehrere Ein-/Ausgabekanäle überwachen

Über den poll- bzw. select-Systemcall erhält eine Applikation die Möglichkeit, gleich mehrere Ein- bzw. Ausgabequellen (Dateideskriptoren) auf Veränderung zu überwachen. Damit kann ein Anwenderprogramm mit einem Aufruf überprüfen, ob Daten an einer oder an mehreren Schnittstellen bzw. Geräten anliegen, so z.B., ob Daten von der Tastatur oder der seriellen Schnittstelle zum Lesen bereitliegen oder ob Daten an die parallele Schnittstelle ausgegeben werden können. Diese Überwachung findet zudem zeitüberwacht – mit einem im Mikrosekundenbereich einstellbaren Timeout-Wert – statt.

Die vom Treiber zur Verfügung zu stellende Poll-Funktion hat die Aufgabe, festzustellen, ob Daten mit dem nächsten Aufruf (read) gelesen und ob Daten mit dem nächsten Aufruf (write) geschrieben werden können. Da die Applikation, die den Poll-Systemcall aufruft, durch das Betriebssystem eventuell schlafen gelegt wird (dann, wenn keines der spezifizierten Geräte Daten zum Lesen bereithält bzw. Daten zum Schreiben entgegennehmen kann), muss das Betriebssystem auch mitbekommen, wenn der Treiber eventuell wieder Daten zur Verfügung hat. Da das »Schlafenlegen« der Applikation im Regelfall über eine Warteschlange erfolgt, werden dem Betriebssystem alle Warteschlangen bekannt gegeben, über die eine auf Daten von dem zugehörigen Gerät wartende Applikation aufgeweckt würde.

Beispiel 5-13. Die Funktion driver_poll

unsigned int driver_poll( struct file *instanz, poll_table *wait )
{
    unsigned int mask = 0;

    if( data_avail_to_read ) {
        mask |= POLLIN | POLLRDNORM;                       (1)
    }
    if( data_can_be_written ) {
        mask |= POLLOUT | POLLWRNORM;                      (2)
    }
    poll_wait( instanz, &read_queue, wait );               (3)
    poll_wait( instanz, &write_queue, wait );              (4)
    return mask;
}
(1)
Wenn für den nächsten Leseaufruf Daten zur Verfügung stehen, müssen die entsprechenden Flags gesetzt werden. Die Bedeutung der Flags ist in Tabelle Die wichtigsten Flags der driver_poll-Funktion erläutert.
(2)
Wenn Daten auf das Gerät geschrieben werden können, müssen die entsprechenden Flags gesetzt werden. Die Bedeutung der Flags ist in Tabelle Die wichtigsten Flags der driver_poll-Funktion erläutert.
(3)
Dem Kernel wird die Warteschlange mitgeteilt, über die ein Prozess normalerweise aufgeweckt wird, der auf Daten zum Lesen von diesem Gerät wartet (Funktion poll_wait).
(4)
Dem Kernel wird die Warteschlange mitgeteilt, über die ein Prozess normalerweise aufgeweckt wird, der auf das Gerät Daten schreiben möchte.

Tabelle 5-2. Die wichtigsten Flags der driver_poll-Funktion

Name Bedeutung
POLLIN Daten können von der zugehörigen Treiberinstanz gelesen werden, ohne dass diese blockiert.
POLLOUT Daten können von der zugehörigen Treiberinstanz geschrieben werden, ohne dass diese blockiert.
POLLERR Ein Fehler ist aufgetreten.
POLLRDNORM »Normale« Daten liegen zum Lesen bereit.
POLLWRNORM Daten können geschrieben werden.

Innerhalb der driver_poll-Funktion werden die einzelnen Flags miteinander verodert. Wenn Daten ohne zu blockieren gelesen werden können, werden die Flags POLLIN und POLLRDNORM gesetzt; wenn Daten geschrieben werden können, die Flags POLLOUT und POLLWRNORM (siehe Die Funktion driver_poll).

Die Applikationsfunktionen select (siehe Aufruf der Funktion select aus der Applikation) bzw. poll sind derart definiert, dass der dem select bzw. poll folgende read- bzw. write-Aufruf in jedem Fall erfolgreich sein muss, falls der entsprechende Dateideskriptor gesetzt wurde (FD_ISSET == TRUE).

Beispiel 5-14. Aufruf der Funktion select aus der Applikation

	select( fdlast+1, &fdsetread, NULL, NULL, NULL );         (1)
	if( FD_ISSET(fd, &fdsetread) ) {                          (2)
        read( fd, buffer, sizeof(buffer) );                (3)
	}
(1)
Der Rechenprozess wird so lange schlafen gelegt, bis für einen der Filedeskriptoren, die im Set fdsetread spezifiziert sind, Daten zum Lesen bereitliegen.
(2)
Daten liegen für die Treiberinstanz zum Lesen bereit.
(3)
Dieser read-Aufruf muss in jedem Fall sofort bedient werden, ohne dass der zugehörige Rechenprozess schlafen gelegt wird.
Schwierig wird die Implementierung dieser Funktionalität, wenn mehrere Instanzen auf den Treiber zugreifen. In diesem Fall muss sichergestellt werden, dass diejenige Treiberinstanz, der beim Durchlauf durch die driver_poll-Funktion signalisiert wurde, dass der nächste Zugriff erfolgreich ist, auch wirklich ohne zu blockieren zugreifen kann.

Wartet beispielsweise ein Rechenprozess mittels select darauf, dass er Daten lesen kann und signalisiert select schließlich, dass die Daten wirklich gelesen werden können, dann muss der folgende Aufruf von read ohne Unterbrechung durch den Treiber durchgeführt werden. Und wenn genau nach dem select und vor dem read ein zweiter Rechenprozess lesend auf den Treiber zugreift? Dann wird dieser zweite Rechenprozess so lange schlafen gelegt, bis für ihn weitere Daten angekommen sind. Der erste Rechenprozess wird in jedem Fall bedient, sobald er einen read-Systemcall absetzt. Im Treiber muss also in der Poll-Funktion zwischengespeichert werden, dass eine Treiberinstanz Daten lesen darf und der Zugriff für andere Rechenprozesse in der Zwischenzeit nicht möglich ist.

Abbildung 5-3. Reservierung von Daten nach einem select-Aufruf

Eine mögliche Lösung für das Problem ist im Beispielprogramm Realisierung einer Poll-Funktion im Treiber vorgestellt. Aus Gründen der Übersichtlichkeit ist der Code für den schreibenden Zugriff weggelassen und nur der Code für den lesenden Zugriff dargestellt. Auch ist nur der Kontrollfluss innerhalb des Treibers auskodiert, ein eigentlicher Datenaustausch (z.B. über die Funktion copy_from_user) wird nicht gezeigt. Die driver_write-Funktion wird im Beispiel verwendet, um dem Treiber das Vorhandensein von Daten zu signalisieren. In einem realen Beispiel würde diese Funktionalität wohl eher in einer Interrupt-Service-Routine zu finden sein.

Beispiel 5-15. Realisierung einer Poll-Funktion im Treiber

/******************************************************************/
/* Das Programm demonstriert eine Möglichkeit sicherzustellen,    */
/* dass nur die Treiberinstanz Daten vom Treiber liest            */
/* die auch beim select (poll) die Information bekommen hat, dass */
/* Daten zum Lesen vorhanden sind.                                */
/******************************************************************/
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <asm/io.h>
#include <asm/uaccess.h>   /* copy_to_user()       */

MODULE_LICENSE("GPL");

#define DRIVER_MAJOR 240

static wait_queue_head_t read_queue;
static unsigned long data_avail_for_read = 0;              (1)
static struct file *reserved_for_proc=NULL;                (2)

static int driver_close( struct inode *inode, struct file *instance )
{
    printk("driver_close aufgerufen\n");
    if( reserved_for_proc==instance ) {                    (3)
        reserved_for_proc=NULL;
        set_bit(0,&data_avail_for_read);
        wake_up_interruptible( &read_queue );
    }
    return 0;
}

static ssize_t driver_read( struct file *instance, char *user, size_t count,
        loff_t *Offset )
{
    int to_copy = count, retval;

    while( !test_and_clear_bit(0,&data_avail_for_read) ) { (4)
        if( reserved_for_proc==instance ) {                (5)
            reserved_for_proc=NULL;
            break;
        }
        printk("going to sleep %p...\n", instance );
        retval=wait_event_interruptible(read_queue,data_avail_for_read);
        printk("woke up %p...\n", instance );
        if( retval==-ERESTARTSYS ) {
            printk("got signal, break\n");
            return -EINTR;
        }
    }
    return to_copy;
}                                                          (6)

static unsigned int driver_poll(struct file *instance, poll_table *event_list)
{
    unsigned int mask = 0;

    if( test_and_clear_bit(0,&data_avail_for_read) ) {
        reserved_for_proc =instance;                       (7)
        mask |= POLLIN | POLLRDNORM;                       (8)
    }                                                      (9)
    poll_wait( instance, &read_queue, event_list );        (10)
    return mask;
}

static ssize_t driver_write( struct file *instance, const char *User,
        size_t count, loff_t *offset)
{                                                          (11)
    int to_copy = count;

    printk("Daten koennen gelesen werden.\n");
    set_bit(0,&data_avail_for_read);
    wake_up_interruptible( &read_queue );
    return to_copy;
}

static struct file_operations fops = {
.owner=   THIS_MODULE,
.read=    driver_read,  /* read */
.write=   driver_write, /* write */
.poll=    driver_poll,  /* poll */
.release= driver_close, /* release */
};

static int __init buf_init(void)
{
    if(register_chrdev(DRIVER_MAJOR, "Polltest", &fops) == 0) {
        init_waitqueue_head( &read_queue );
        return 0;
    };
    printk("Polltest unable to get major %d\n",DRIVER_MAJOR);
    return -EIO;
}

static void __exit buf_exit(void)
{
    unregister_chrdev(DRIVER_MAJOR,"Polltest");
}

module_init( buf_init );
module_exit( buf_exit );
// vim:set aw ic sw=4 ts=4:
(1)
Die Variable data_avail_for_read signalisiert, ob Daten zum Lesen vorhanden sind oder nicht.
(2)
Die Variable reserved_for_proc speichert die Adresse der Treiberinstanz struct file *, die im Fall eines erfolgreichen select-Aufrufs die Daten abholen darf. Wenn mit Ausnahme des Rechenprozesses, der den select-Aufruf durchgeführt hat, keine Daten abholbereit sind, muss gewartet werden.
(3)
Wenn eine Applikation Daten per select reserviert, dann aber nicht abholt, werden die Daten für andere Rechenprozesse freigegeben, sobald die Applikation beendet ist.
(4)
Hier beginnt die Hauptschleife, in der entschieden wird, ob der zugreifende Prozess in den Zustand »wartend« versetzt wird oder nicht.

Ist das Flag »NULL«, bedeutet das nicht, dass generell keine Daten zum Lesen vorhanden sind. Es bedeutet vielmehr, dass nicht jeder x-beliebige Prozess Daten lesen kann.

(5)
Für die Treiberinstanz, die ein select aufgerufen hat, ist der dem select folgende Lesezugriff erlaubt. Daher kann die Schleife in diesem Fall abgebrochen werden.
(6)
Der eigentliche Datentransfer ist nicht auskodiert.
(7)
Sind Daten zum Lesen vorhanden, muss dies dem aufrufenden Rechenprozess mitgeteilt werden.
(8)
Obwohl Daten zum Lesen vorhanden sind, wird innerhalb des Treibers durch Setzen des Flags der Zugriff gesperrt. Der Zugriff auf die Daten ist nur der Treiberinstanz erlaubt, der das Vorhandensein der Daten soeben signalisiert wurde.
(9)
Diese Treiberinstanz wird hier als zugriffsberechtigt markiert.
(10)
Gemäß der Spezifikation wird dem Betriebssystemkern per Flags mitgeteilt, dass über den fraglichen Filedeskriptor Daten zum Lesen abgeholt werden können.
(11)
Die driver_write-Funktion dient hier einzig dem Zweck, den Treiber austesten zu können. Durch das Schreiben auf den Treiber wird diesem signalisiert, dass Daten zum Lesen vorhanden sind.


Lizenz