5.2. Den Kernel erweitern

5.2.1. Kernelmodule

Im Regelfall werden Treiber als ladbare Module erstellt. Diese Module werden – nachdem das Betriebssystem gestartet wurde – über das Kommando insmod (insert module, Modul einfügen) in den laufenden Kernel eingebunden. Beim Ladevorgang des Moduls wird die im Modul kodierte Funktion init_module aufgerufen. Falls die Initialisierung des Moduls erfolgreich war, gibt die Funktion gemäß Spezifikation »0« zurück, ansonsten einen Fehlercode.

Entsprechendes gilt für den Entladevorgang. Wird auf Applikationsebene das Kommando rmmod (remove module, Modul entfernen) aufgerufen, ruft der Kernel zunächst die im Modul kodierte Funktion cleanup_module auf. Diese Funktion hat allerdings keinen Rückgabewert (void). Sie dient dazu, »aufzuräumen« – so wird beispielsweise vorher allozierter Speicher wieder freigegeben. Nach der Abarbeitung wird das Modul wieder aus dem Kernel und damit auch aus dem Speicher entfernt.

Ein erstes Modul zu erstellen ist einfach. Hierzu müssen Sie nur die beiden Funktionen (init_module) und (cleanup_module) programmieren und zusätzlich die dem Modul zugrunde liegende Lizenz über das Makro MODULE_LICENSE einkodieren. Wenn ein Modul unter der GNU Public License (GPL) steht, kann der Treiber sämtliche Funktionen des Kernels nutzen. Und nur in einem solchen Fall kann der Entwickler von der Open-Source-Gemeinschaft Unterstützung bei der Fehlersuche erwarten (vgl. Kapitel Nicht vergessen: Auswahl einer geeigneten Lizenz).

Mit Kenntnis dieser Besonderheiten lässt sich mit wenigen Zeilen Code ein erstes Modul kodieren (siehe Der erste Kernelcode).

Beispiel 5-1. Der erste Kernelcode

#include <linux/version.h>                                 (1)
#include <linux/module.h>                                  (2)

MODULE_LICENSE("GPL");                                     (3)

int init_module(void)                                      (4)
{
    printk("init_module called\n");                        (5)
    return 0;                                              (6)
}

void cleanup_module(void)                                  (7)
{
    printk("cleanup_module called\n");
}

	
(1)
Über die Header-Datei <linux/version.h> wird die Kernelversion eingebunden, für die der Treiber kompiliert wird.
(2)
Die Header-Datei <linux/module.h> enthält Makros und Defines für die Integration des Moduls in den Kernel. Beispielsweise findet sich in dieser Header-Datei das Makro MODULE_LICENSE.
(3)
Mit diesem Makro wird die Lizenz festgelegt, unter der der Treiber steht.
(4)
Die Initialisierungsfunktion eines Moduls muss init_module genannt werden. Die Funktion bekommt keinerlei Parameter übergeben.
(5)
Das Beispielmodul macht beim Laden eine Ausgabe über die Kernellogs.
(6)
Der erfolgreiche Abschluss der Modulinitialisierung wird dem Kernel durch den Rückgabewert »0« angezeigt. In diesem Fall belässt der Kernel das Modul im Speicher. Wird jedoch ein anderer Wert zurückgegeben, entfernt der Kernel das Modul wieder. Die Funktion cleanup_module wird in diesem Fall nicht aufgerufen.
(7)
Die Funktion, die beim Entladen des Moduls aufgerufen wird, trägt den Namen cleanup_module. Diese Funktion hat weder Aufrufparameter noch einen Rückgabewert.

Um das Modul zu testen, muss es mit den zum Kernel gehörigen Compile-Optionen übersetzt werden. Dazu muss ein Makefile erstellt werden, das den Generierungsprozess steuert. Befindet sich der Modulcode in einer Datei namens mod1.c, könnte das zugehörige Makefile wie im Beispiel Makefile zum ersten Kernelcode dargestellt aussehen.

Beispiel 5-2. Makefile zum ersten Kernelcode

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

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

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

Das Makefile muss »Makefile« (erster Buchstabe groß) geschrieben werden. Der genaue Aufbau bzw. die Funktionalität des Makefiles soll zunächst noch nicht interessieren. Für die ersten Versuche reicht es aus, das Makefile wie vorgestellt zu verwenden. Eine detaillierte Erläuterung und Hinweise zur Modifikation bzw. Erweiterung befinden sich im Kapitel Kernel Build System.

Der Aufruf von make sorgt dafür, dass die Objektdatei mod1.o erzeugt und mit der Kernel-Versionsinformation (init/vermagic.o) zum Modul mod1.ko gelinkt wird.

Mit den Rechten des Superusers versehen, kann das Modul (mod1.ko) mit dem Kommando »insmod mod1.ko« in den Kernel geladen werden. Erscheint direkt nach der Eingabe des Kommandos der übliche Prompt, dürfte die Installation des Moduls erfolgreich gewesen sein:

root# insmod mod1.ko
root#

Anhand zweier Indizien lässt sich der Erfolg der Operation feststellen:

  1. Das Kommando lsmod zeigt alle erfolgreich geladenen Module an und muss folglich auch das soeben geladene Modul anzeigen:

    root# insmod mod1.ko
    root# lsmod
    Module                  Size  Used by    Not tainted
    mod1                     944   0  (unused)
    ppp_generic            24144   0  (autoclean)
    slhc                    5876   0  (autoclean) [ppp_generic]
    3c59x                  27752   1  (autoclean)
    ehci-hcd               18592   0  (unused)
    vfat                   10976   1  (autoclean)
    root#

  2. In den Protokolldateien des Systems muss die printk-Meldung des Treibers auftauchen. Um dies zu verifizieren, sollte – wie vorher beschrieben – in einem zweiten Fenster das Kommando »tail -f /var/log/messages« aufgerufen werden.

Wie gesehen lassen sich unter Linux Kernelerweiterungen in Form von Modulen kompakt und einfach kodieren. Da der vorgestellte Quellcode nur für Modultreiber geeignet ist, sollte er jedoch nicht als Ausgangsbasis (Template) für die weiteren Entwicklungen verwendet werden.

Built-in-Treiber unterscheiden sich nämlich von den Modultreibern im Wesentlichen nur durch die unterschiedliche Konvention bei der Namensgebung der Initialisierungs- und Deinitialisierungsfunktionen. Während die Namen dieser Funktionen für Modultreiber festgelegt sind, kann der Programmierer eines Built-in-Treibers frei wählen.

Mit den Makros module_init und module_exit bietet Linux die Möglichkeit, auch ohne störende »ifdefs« Quellcode, verwendbar für Modul- und für Built-in-Treiber zu erstellen. Dabei können die Namen der Initialisierungs- und Deinitialisierungsfunktionen frei gewählt und dann den beiden Makros als Parameter übergeben werden.

Ein Template, welches für alle weiteren Treiber dienen kann, zeigt Beispiel Codegerüst für einfache Treiber.

Beispiel 5-3. Codegerüst für einfache Treiber

#include <linux/module.h>
#include <linux/init.h>                                    (1)

// Metainformation                                         (2)
MODULE_AUTHOR("Jürgen Quade");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Just a Modul-Template, without specific functionality.");
MODULE_SUPPORTED_DEVICE("none");

static int __init mod_init(void)                           (3)
{
    printk("mod_init called\n");
    return 0;
}

static void __exit mod_exit(void)                          (4)
{
    printk("mod_exit called\n");
}

module_init( mod_init );                                   (5)
module_exit( mod_exit );                                   (6)

	
(1)
Die Header-Datei <linux/init.h> wird für die Schlüsselworte __init und __exit benötigt. Die Schlüsselworte, die im Kapitel Management implizit zugeteilter Ressourcen erklärt werden, helfen dem Kernel, die Speicher-Ressourcen besser zu verwalten.
(2)
Die folgenden Makros stellen Metainformation dar. Bis auf MODULE_LICENSE sind diese Makros optional. Hier können der Name des Autors, eine Beschreibung des Moduls und die unterstützten Geräte abgelegt werden. Auslesen lässt sich diese Metainformation über das Kommando modinfo.
(3)
Der Name der Init-Funktion kann frei gewählt werden; das Attribut __init ist optional.
(4)
Der Name der Deinitialisierungsfunktion kann frei gewählt werden. Das Attribut __exit ist optional.
(5)
Dieses Makro sorgt dafür, dass im Fall eines Modultreibers der frei gewählte Funktionsname der Initialisierungsfunktion in den Namen init_module umgesetzt wird.
(6)
Dieses Makro sorgt dafür, dass im Fall eines Modultreibers der frei gewählte Funktionsname der Deinitialisierungsfunktion in den Namen cleanup_module umgesetzt wird.

Anmerkung[KASTEN:] Fehlermöglichkeiten
 

Sowohl beim Generierungsprozess (make) als auch beim Laden des Moduls (insmod) kann es zu Fehlermeldungen kommen. Die wichtigsten sollen kurz vorgestellt werden.

/tmp/modultest/Makefile: No such file or directory

Fehler: Die Schreibweise von »Makefile« ist nicht korrekt.

Abhilfe: Korrigieren Sie den Namen des Makefiles. Der erste Buchstabe muss groß geschrieben werden, die weiteren Buchstaben klein.

mod1.ko: couldn't find the kernel version the module was compiled for

Fehler: Es wurde versucht, ein Modul eines Kernels der nächsten Generation unter einem stabilen Kernel (zum Beispiel 2.4.20) zu laden.

Abhilfe: Booten Sie einen Entwickler-Kernel.

insmod: QM_MODULES: Function not implemented

Fehler: Sie haben versucht, das Modul mit einer alten Version von insmod zu laden.

Abhilfe: Installieren Sie die neue Version der Modutils.

Im Syslog taucht die Meldung auf: mod1: no version magic, tainting kernel

Fehler: Es handelt sich nur um eine Warnung. Der Kernel hat das Modul geladen. konnte aber die Version nicht prüfen, da er das Modul nicht als mod1.ko, sondern als mod1.o erhielt.

Abhilfe: Laden Sie das Modul mod1.ko.

Im Syslog taucht eine Meldung der Art: mod1: version magic '2.5.70 preempt PENTIUMII gcc-2.95' should be '2.5.70 preempt PENTIUMII gcc-3.2' auf.

Fehler: Hierbei handelt es sich um eine Warnung, der Kernel hat das Modul dennoch geladen. Der Kernel ist mit GCC 3.2, das Modul jedoch mit GCC 2.95 übersetzt worden.

Abhilfe: Installieren Sie GCC 3.2 als Default-Compiler.

5.2.2. Vom Modul zum Treiber

Ein Modul wird zum Treiber, indem es sich beim IO-Management des Betriebssystemkerns anmeldet (registriert). Dazu ruft es innerhalb der Modulinitialisierung (init_module) die Kernelfunktion register_chrdev auf.

Die Funktion register_chrdev erwartet drei Parameter:

  1. eine Treiberidentifikations- bzw. Treiberauswahlnummer, die so genannte Majornumber,

  2. einen Namen für den Treiber und

  3. eine Tabelle mit Einsprungspunkten in den Treiber (Tabelle mit Adressen von Treiberfunktionen).

Die Majornumber stellt die Beziehung zwischen der im Filesystem angelegten Gerätedatei auf der einen Seite und dem Treiber auf der anderen Seite her. Diese Nummer muss in Bezug auf den Treiber eindeutig, das heißt im System nur einmal vergeben sein.

Beispiel 5-4. Anmelden des Treibers beim IO-Management

static struct file_operations fops;
static int __init mod_init(void)
{
    if( register_chrdev(240,"TestDriver",&fops)==0 ) {
        return 0; // Treiber erfolgreich angemeldet
    }
    return -EIO; // Anmeldung beim Kernel fehlgeschlagen
}

module_init(mod_init);

Im Beispiel Anmelden des Treibers beim IO-Management meldet sich der Treiber mit dem Namen »TestDriver« unter der Majornumber »240« beim IO-Management an. Dabei übergibt er die treiberspezifische Instanz der Datenstruktur struct file_operations, hier mit dem Namen »fops«. Diese Datenstruktur – deklariert in der Header-Datei <linux/fs.h> – enthält alle vom Kernel zu verwendenden Einsprungspunkte in den Treiber, die dort in Form von Funktionspointern (Adressen auf Funktionen) abgelegt sind.

Der Rückgabewert der Funktion register_chrdev spiegelt wider, ob der Treiber vom Kernel akzeptiert wurde oder nicht. Ist beispielsweise die Majornumber bereits vergeben oder im System keine freie Majornumber mehr verfügbar, zeigt dies der Rückgabewert der Funktion an.

Dass die Treiberauswahlnummer beschränkt ist (sie ist vom Typ char und kann damit nur 256 unterschiedliche Werte annehmen), ist eines der historischen Probleme von Unix. Da die Majornumber für jeden Treiber eindeutig sein muss, wird sie an zentraler Stelle verwaltet. Die bisher belegten Nummern finden sich in der Datei /usr/src/linux/Documentation/devices.txt. Möchte man eine der wenigen freien Nummern für seinen Treiber zugewiesen bekommen, ist die im Dokument genannte Person Ansprechpartner. Allerdings werden bereits seit geraumer Zeit keine Auswahlnummern mehr vergeben. Stattdessen sollen Treiber die für freie Zwecke zur Verfügung stehenden Nummern verwenden. Gegenwärtig sind das die Nummern von:

Die Majornumber »0« hat darüber hinaus eine besondere Bedeutung: Registriert sich der Treiber unter dieser Nummer beim IO-Management, dann weist das IO-Management dem Treiber die nächste freie Majornumber aus dem zur Verfügung stehenden Bereich zu (dynamische Zuteilung der Majornumber durch den Kernel). Durch die dynamische Zuteilung wird verhindert, dass es zu Überschneidungen bei der Vergabe der Auswahlnummern kommt. Nachteilig ist, dass jedes Mal nach dem Laden des Treibers eine Gerätedatei im Dateisystem über das Kommando mknod mit der neu zugeordneten Major-Nummer angelegt werden muss.

Normalerweise gibt die Funktion register_chrdev im fehlerfreien Fall »0« zurück. Das ist bei Verwendung dynamisch zugeteilter Auswahlnummern anders. Hier wird bei Erfolg die zugewiesene Majornumber zurückgegeben, bei Misserfolg aber die »0«. Der Treiber muss die zurückgegebene Majornumber abspeichern, da die aktuell verwendete Majornumber zum Abmelden des Treibers bekannt sein muss. Der Rückgabewert der Funktion ist also unglücklicherweise davon abhängig, ob der Treiber seine eigene Majornumber mitbringt oder sich eine dynamisch vom Kernel zuteilen lässt.

Wird das Modul wieder entladen und dabei die Deinitialisierungsfunktion cleanup_module aufgerufen, muss sich der Treiber vorher noch beim Kernel abmelden. Dazu dient die Funktion unregister_chrdev. Diese Funktion bekommt sowohl die Majornumber als auch den Namen übergeben, unter dem der Kernel den Treiber führt. Nur wenn der Name auch genau mit dem Namen übereinstimmt, den der Kernel für den Treiber abgespeichert hat, kann sich der Treiber erfolgreich beim Betriebssystemkern abmelden!

static void __exit mod_exit(void)
{
    unregister_chrdev(240,"TestDriver");
}

Hat man während der Entwicklungsphase einmal vergessen, den Treiber wieder abzumelden, kann ein konsistenter Systemzustand nur durch Runter- und wieder Hochfahren des Rechners hergestellt werden.

5.2.3. Gerätenummern ersetzen Major-Nummern

Anmerkung[KASTEN]: Ein Treiber für viele Geräte
 

Die Gerätedatei ist nicht nur mit einer Majornumber, sondern auch mit einer Minornumber verknüpft. Die Minornumber ist wie die Majornumber 8 Bit breit. Es lassen sich daher mit der gleichen Majornumber bis zu 256 verschiedene Gerätedateien anlegen.

Einerlei auf welche dieser 256 Gerätedateien eine Applikation zugreift – der Systemcall endet immer in dem gleichen, durch die Majornumber bestimmten Treiber. Innerhalb des Treibers kann dann festgestellt werden, über welche Gerätedatei – sprich über welche Minornumber – die Applikation gerade zugreift. Abhängig von dieser Minornumber können im Treiber ganz unterschiedliche Verarbeitungsstränge ablaufen.

Über die Minornumber wird beispielsweise für einen Festplattentreiber bestimmt, auf welche Festplatte – die erste oder die zweite – zugegriffen werden soll. Darüber hinaus sind noch weitere Festlegungen getroffen worden. So definiert die Minornumber zusätzlich, ob auf die gesamte Platte oder auf eine einzelne Partition zugegriffen werden soll.

Während bei Blockgeräten die Bedeutung der Minornumber auf die Auswahl des Gerätes und die Partition festgelegt ist, können Minor-Nummern bei zeichenorientierten Geräten völlig frei verwendet werden.

Wie die Minornumber innerhalb des Treibers ausgelesen werden kann, wird im Kapitel driver_open: die Zugriffskontrolle beschrieben.

Die meisten Treiber für den Kernel 2.6 werden wohl auf Basis der in Kapitel Vom Modul zum Treiber beschriebenen Major-Nummern realisiert. Kernel 2.6 arbeitet intern jedoch nicht mehr mit Major-Nummern, sondern mit Gerätenummern. Es gibt derzeit drei Gründe, im Treiber Gerätenummern statt der bekannten Major-Nummern zu verwenden:

  1. Ein Treiber soll mehr als 256 physikalische oder logische Geräte unterstützen.

  2. In einem System ist keine der klassischen Major-Nummern mehr verfügbar.

  3. Der Treiber möchte respektvoll mit den vorhandenen Ressourcen umgehen und nicht mehr Minor-Nummern verbrauchen, als er wirklich benötigt.

Allerdings muss gleich eingeschränkt werden: Zwar lassen sich Treiber auf Basis der neuen Gerätenummern realisieren, von diesen lassen sich gegenwärtig aber nur solche wirklich nutzen, die weniger als 256 Minor-Nummern anfordern. Für alle anderen Treiber auf Basis der neuen Gerätenummern fehlt bislang die Unterstützung von Filesystemen und Applikationen.

Noch eine weitere Einschränkung ist hinzunehmen. Da die Gerätenummern erst kurz vor Veröffentlichung des Kernels 2.6.0 integriert wurden, sind sie noch nicht sehr verbreitet. Ihre Handhabung ist umständlich.

Während Major- und Minor-Nummern jeweils 8 Bit breit sind und damit nur theoretische 216=65536 Geräte ermöglichen, besitzen Gerätenummern 32 Bit, können also über 4 Milliarden Geräte ansprechen. Wichtiger noch ist, dass es keine Unterscheidung zwischen Major- und Minor-Nummern gibt. Ein Treiber kann somit selbst festlegen, wie viele Geräte er maximal bedienen möchte.

Abbildung 5-1. Abbildung der alten Major- und Minor-Nummern auf die neuen Gerätenummern

Damit beide Systeme miteinander existieren können, hat Linus Torvalds ein Mapping der alten Major- und Minor-Nummern auf die neuen Gerätenummern spezifiziert. Demnach werden die oberen 12 Bit einer Gerätenummer durch die Major-Nummer definiert, die unteren 20 Bit durch die Minor-Nummer (siehe Abbildung Abbildung der alten Major- und Minor-Nummern auf die neuen Gerätenummern).

Um aus einer gegebenen Major- und Minor-Nummer die zugehörige Gerätenummer zu erhalten, steht das Makro MKDEV zur Verfügung. Und umgekehrt: Um aus einer gegebenen Gerätenummer die Major- und Minor-Nummer abzuleiten, müssen die beiden Makros MAJOR und MINOR verwendet werden.

Um einen Treiber auf Basis einer Gerätenummer beim Kernel anzumelden, sind drei Schritte durchzuführen:

  1. Die Ressource Gerätenummer muss reserviert werden.

  2. Ein Treiber-Objekt muss alloziert und initialisiert werden.

  3. Das Treiber-Objekt muss dem Kernel übergeben werden.

Für die Zuteilung der Gerätenummern gibt es die beiden Funktionen register_chrdev_region und alloc_chrdev_region. Der erste Parameter der Funktion ist die erste Gerätenummer, die reserviert werden soll, der zweite Parameter spezifiziert die Anzahl der aufeinander folgenden Gerätenummern und der dritte Parameter schließlich den Namen, unter dem die Reservierung durchgeführt wird. Bei der Funktion alloc_chrdev_region wird die zu reservierende Gerätenummer nicht vorgegeben, sondern es wird vom Kernel der nächste freie, ausreichend große Bereich von Gerätenummern reserviert. Der erste Parameter zur Funktion spezifiziert dabei eine Speicherzelle, in die die erste Gerätenummer abgelegt wird. Sollte die Reservierung erfolgreich vonstatten gegangen sein, geben beide Funktionen »0« zurück, ansonsten einen Fehlercode.

Das Objekt, welches einen zeichenorientierten Treiber schließlich beim Kernel anmeldet, ist vom Typ struct cdev. Um ein solches Objekt zu allozieren wird die Funktion cdev_alloc aufgerufen; initialisiert wird das Objekt durch Aufruf der Funktion cdev_init. Neben der Adresse des Objektes ist hier vor allem eine Instanz der bekannten struct file_operations mit zu übergeben. Zusätzlich zu den innerhalb der Funktion durchgeführten Initialisierungen sollten noch weitere Felder vorbelegt werden: Um einen Entladeschutz des Moduls realisieren zu können, ist das Feld owner mit dem definierten Symbol THIS_MODULE zu belegen. Wird zudem das in der Struktur eingebettete Kernel-Objekt kobj über die Funktion kobject_set_name mit einem Namen belegt, legt das Gerätemodell (siehe Kapitel Das neue Gerätemodell) später einen Eintrag im Sys-Filesystem an. Auch bei dieser Funktion ist der Rückgabewert auszuwerten, da innerhalb der Funktion unter Umständen dynamisch Speicher alloziert wird, ein Vorgang, der fehlschlagen kann.

Ist das Treiber-Objekt initialisiert, kann der Treiber endlich angemeldet werden. Dazu reicht es aus, die Funktion cdev_add aufzurufen. Auch hier kann der Treiberentwickler anhand des Rückgabewertes den Erfolg (»0«) beziehungsweise Misserfolg ablesen.

Das Abmelden des Treibers ist unglücklicherweise noch sehr kompliziert. Es bleibt zu hoffen, dass die Kernelmacher möglichst bald geeignete Funktionen zur Verfügung stellen, die die internen Abläufe und Zusammenhänge verstecken. In den Kernelversionen bis einschließlich 2.6.2 musste zunächst das Treiber-Objekt aus internen Listen entfernt werden, was die Funktion cdev_unmap erledigt hat. Ab Kernel 2.6.3 ist dies aber nicht mehr notwendig. Anschließend wird das Objekt als solches entfernt (cdev_del). Als Drittes schließlich wird die Ressource Gerätenummer wieder freigegeben (unregister_chrdev_region).

Beispiel An- und Abmelden eines Treibers, der mehr als 256 Geräte unterstützt, zeigt, wie sich ein Treiber auf Basis einer Gerätenummer beim Kernel an- beziehungsweise wieder abmeldet.

Beispiel 5-5. An- und Abmelden eines Treibers, der mehr als 256 Geräte unterstützt

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

MODULE_LICENSE("GPL");

static struct cdev *driver_object=NULL;

static struct file_operations fops = {
    .owner=THIS_MODULE,
};

static int myregister_chrdev( dev_t device_number, int count, char *name,
    struct file_operations *fops )
{
    if( register_chrdev_region( device_number, count, name ) ) {
        printk("Devicenumber 0x%x not available ...\n", device_number );
        return -1;
    }
    driver_object = cdev_alloc();
    if( driver_object==NULL ) {
        printk("cdev_alloc failed ...\n");
        goto free_device_number;
    }
    kobject_set_name(&driver_object->kobj, name );
    driver_object->owner = THIS_MODULE;
    cdev_init( driver_object, fops );
    if( cdev_add( driver_object, device_number, count ) ) {
        printk("cdev_add failed ...\n");
        goto free_cdev;
    }
    return 0;
free_cdev:
    kobject_put(&driver_object->kobj);
    driver_object = NULL;
free_device_number:
    unregister_chrdev_region( device_number, count );
    return -1;
}

int myunregister_chrdev(dev_t device_number, int count)
{
    if( driver_object )
        cdev_del( driver_object );
    unregister_chrdev_region( device_number, count );
    return 0;
}

static int __init buf_init(void)
{
    if(myregister_chrdev(MKDEV(241,0),300, "300Devices", &fops) == 0) {
        return 0;
    }
    return -EIO;
}

static void __exit buf_exit(void)
{
    dev_t device_number = MKDEV(241,0);

    if( driver_object )
        myunregister_chrdev( device_number, 300 );
}

module_init( buf_init );
module_exit( buf_exit );


Lizenz