Die Struktur net_device im Detail

Die net_device-Struktur ist der innerste Kern der Netzwerk-Treiber-Schicht und verdient es daher, vollständig beschrieben zu werden. Beim ersten Lesen können Sie diesen Abschnitt allerdings überspringen, weil Sie die Struktur nicht vollständig verstehen müssen, um loslegen zu können. In dieser Liste werden alle Felder beschrieben, aber mehr in der Absicht, eine Referenz zu bieten, als daß die Felder auswendig gelernt werden sollten. Der Rest des Kapitels beschreibt kurz jedes Feld, sobald es im Beispiel-Code vorkommt, so daß Sie nicht jedesmal zu diesem Abschnitt hier zurückspringen müssen.

struct net_device kann vollständig in zwei Teile aufgeteilt werden, den “sichtbaren” und den “unsichtbaren”. Der sichtbare Teil der Struktur besteht aus den Feldern wie den beiden in snull oben gezeigten, die in statischen net_device-Strukturen explizit zugewiesen werden. Alle Strukturen in drivers/net/Space.c werden so initialisiert, ohne Verwendung der Tag-Syntax zur Initialisierung von Strukturen. Die verbleibenden Felder werden intern vom Netzwerk-Code verwendet und normalerweise nicht während des Kompilierens initialisiert, nicht einmal mit der Tag-Syntax. Manche der Felder werden von Treibern verwendet (etwa solche, die zur Initialisierung zugewiesen werden), während andere nicht angefaßt werden sollten.

Der sichtbare Anfang

Der erste Teil von struct net_device besteht aus den folgenden Feldern in der angegebenen Reihenfolge:

char name[IFNAMSIZ];

Der Name des Geräts. Wenn der Name einen %d-Format-String enthält, dann wird der erste verfügbare Geräte-Name mit der angegebenen Basis verwendet; die zugewiesenen Nummern fangen mit 0 an.

unsigned long rmem_end;, unsigned long rmem_start;, unsigned long mem_end;, unsigned long mem_start;

Geräte-Speicher-Informationen. Diese Felder enthalten die Anfangs- und Endadressen des vom Gerät gemeinsam genutzten Speichers. Wenn das Gerät unterschiedliche Speicherbereiche zum Senden und Empfangen verwendet, dann enthalten die mem-Felder den Bereich zum Senden und die rmem-Felder den zum Empfangen. mem_start und mem_end können beim Hochfahren des Systems auf der Kommandozeile des Kernels angegeben und mit ifconfig abgefragt werden. Die Felder rmem werden nie außerhalb des Treibers verwendet. Per Konvention werden die end-Felder so belegt, daß end - start die Menge des verfügbaren Speichers auf der Karte angibt.

unsigned long base_addr;

Die I/O-Basisadresse der Netzwerk-Schnittstelle. Dieses Feld wird wie die vorigen beim Suchen des Gerätes belegt. Mit ifconfig kann der aktuelle Wert angezeigt und verändert werden. base_addr kann auf der Kommandozeile des Kernels beim Booten oder beim Laden des Moduls explizit angegeben werden. Das Feld wird wie die oben genannten Speicherfelder nicht vom Kernel verwendet.

unsigned char irq;

Die zugewiesene Interrupt-Nummer. Der Wert von dev->irq wird von ifconfig ausgegeben, wenn die Schnittstellen aufgeführt werden. Dieser Wert kann üblicherweise beim Hochfahren oder Laden gesetzt und später mit ifconfig modifiziert werden.

unsigned char if_port;

Gibt an welcher Port auf Geräten mit mehreren Ports verwendet wird. Dieses Feld wird beispielsweise bei Geräten verwendet, die sowohl Koax- (IF_PORT_10BASE2-) als auch Twisted-Pair-Ethernet-(IF_PORT_10BASET)- Verbindungen unterstützen. Die bekannten Port-Typen sind in <linux/netdevice.h> definiert.

unsigned char dma;

Der vom Gerät allozierte DMA-Kanal. Das Feld ist nur bei einigen Peripherie-Bus-Systemen wie ISA sinnvoll. Es wird außerhalb des Gerätetreibers selbst nur zu Informationszwecken (in ifconfig) verwendet.

unsigned long state;

Der Gerätezustand. Dieses Feld enthält mehrere Flags. Treiber manipulieren diese Flags normalerweise nicht direkt, sondern verwenden statt dessen eine Reihe von Hilfsfunktionen. Wir sprechen über diese Funktionen weiter unten, wenn wir auf den Treiber-Betrieb eingehen.

struct net_device *next;

Ein Zeiger auf das nächste Gerät in der globalen verketteten Liste; der Treiber sollte dieses Feld nicht benutzen.

int (*init)(struct net_device *dev);

Die weiter oben beschriebene Initialisierungsfunktion.

Die versteckten Felder

Die Struktur net_device enthält mehrere zusätzliche Felder, die normalerweise bei der Initialisierung des Geräts zugewiesen werden. Einige dieser Felder enthalten Informationen über die Schnittstelle, während andere nur für den Treiber existieren (d. h., sie werden nicht vom Kernel verwendet). Es gibt auch noch weitere Felder, vor allem die Geräte-Methoden, die ein Bestandteil der Schnittstelle zwischen Kernel und Treiber sind.

Wir werden hier die drei Gruppen separat aufführen und dabei die eigentliche Reihenfolge der Felder, die nicht relevant ist, außer acht lassen.

Schnittstellen-Informationen

Die größte Teil der Informationen über die Schnittstelle wird schon von ether_setup korrekt ausgefüllt. Ethernet-Karten können sich bei den meisten Feldern auf diese allgemein verwendbare Funktion verlassen, aber die Felder flags und dev_addr sind gerätespezifisch und müssen bei der Initialisierung explizit zugewiesen werden.

Auch einige Nicht-Ethernet-Schnittstellen können auf ähnliche Hilfsfunktionen wie ether_setup zurückgreifen. driver/net/net_init.c exportiert eine Reihe solcher Funktionen, darunter:

void ltalk_setup(struct net_device *dev);

Richtet die Felder für ein LocalTalk-Gerät ein.

void fc_setup(struct net_device *dev);

Initialisierung für Fiber Channel-Geräte.

void fddi_setup(struct net_device *dev);

Konfiguriert eine Schnittstelle für ein Fiber Distributed Data Interface-(FDDI)Netzwerk.

void hippi_setup(struct net_device *dev);

Bereitet die Felder für einen Treiber für den High-Performance Parallel Interface-(HIPPI)Standard vor.

void tr_configure(struct net_device *dev);

Erledigt das Einrichten von Token Ring-Netzwerk-Schnittstellen. Beachten Sie, daß es im 2.4-Kernel auch eine Funktion tr_setup gibt, die interessanterweise gar nichts macht.

Die meisten Geräte werden in einer dieser Klassen enthalten sein. Wenn Ihres radikal neu und anders ist, müssen Sie aber alle Felder von Hand belegen.

unsigned short hard_header_len;

Die Hardware-Header-Länge ist die Anzahl der Oktetts, die im übertragenen Paket vor dem IP-Header oder anderen Protokoll-Informationen stehen. Bei Ethernet-Schnittstellen ist dieser Wert 14.

unsigned short mtu;

Die Maximum Transfer Unit. Dieses Feld wird von der Netzwerk-Schicht während der Paket-Übertragung verwendet. Ethernet hat eine MTU von 1500 Oktetten (ETH_DATA_LEN).

unsigned long tx_queue_len;

Die maximale Anzahl der Frames, die in der Übertragungswarteschlange des Geräts abgelegt werden können. Dieser Wert wird von ether_setup auf 100 gesetzt, kann aber von Ihnen geändert werden. plip verwendet beispielsweise 10, um die Verschwendung von Systemspeicher zu vermeiden (plip hat einen geringeren Durchsatz als eine echte Ethernet-Schnittstelle).

unsigned short type;

Der Hardware-Typ der Schnittstelle. Dieses Feld wird von ARP benutzt, um festzustellen, welche Art von Hardware-Adressen die Schnittstelle unterstützt. Der korrekte Wert für Ethernet-Schnittstellen ist ARPHRD_ETHER, was auch von ether_setup gesetzt wird. Die bekannten Typen sind in <linux/if_arp.h> definiert.

unsigned char addr_len;, unsigned char broadcast[MAX_ADDR_LEN];, unsigned char dev_addr[MAX_ADDR_LEN];

Hardware-(MAC-)Adreßlänge und Geräte-Hardware-Adressen. Die Ethernet-Adreßlänge beträgt sechs Oktetts (hier geht es um die Hardware-ID der Schnittstellen-Karte), und die Broadcast-Adresse besteht aus sechs 0xff- Oktetten. ether_setup initialisiert diese Werte korrekt. Die Geräte-Adresse muß dagegen aus der Schnittstellen-Karte ausgelesen und nach dev_addr geschrieben werden. Das Auslesen ist gerätespezifisch. Die Hardware-Adresse wird verwendet, um korrekte Ethernet-Header zusammenzustellen, bevor das Paket an den Treiber zur Übertragung übergeben wird. snull verwendet keine physikalische Schnittstelle und denkt sich daher eine eigene Hardware-Adresse aus.

unsigned short flags;

Flags für das Interface; werden gleich ausführlich besprochen.

Das Feld flags ist eine Bitmaske, die sich unter anderem aus den im Folgenden aufgeführten Bit-Werten zusammensetzt. Das Präfix IFF_ steht dabei für “InterFace Flags”. Einige Flags werden vom Kernel verwaltet, andere werden von der Schnittstelle bei der Initialisierung gesetzt, um diverse Capabilities (und andere Merkmale) der Schnittstelle bekanntzugeben. Die zulässigen Flags sind in <linux/if.h> definiert und lauten:

IFF_UP

Der Treiber kann dieses Flag nur lesen. Der Kernel setzt es, wenn die Schnittstelle aktiv ist und Pakete übertragen kann.

IFF_BROADCAST

Dieses Flag teilt mit, daß die Schnittstelle das Broadcasting zuläßt. Ethernet-Karten unterstützen das Broadcasting.

IFF_DEBUG

Debug-Modus. Dieses Flag kann gesetzt werden, um die Ausführlichkeit Ihrer printk-Ausgaben zu steuern oder andere Debugging-Möglichkeiten einzuschalten. Auch wenn derzeit kein offizieller Treiber dieses Flag unterstützt, kann es doch von Benutzerprogrammen via ioctl gesetzt und gelesen und von Ihrem Treiber verwendet werden. Mit dem Programm misc-progs/netifdebug kann dieses Flag ein- und ausgeschaltet werden.

IFF_LOOPBACK

Dieses Flag sollte nur in Loopback-Schnittstellen gesetzt werden. Der Kernel überprüft dieses Flag anstelle des hartcodierten Namens lo.

IFF_POINTOPOINT

Dieses Flag kennzeichnet, daß die Schnittstelle mit einer Punkt-zu-Punkt-Verbindung verbunden ist. Es wird von ifconfig gesetzt, beispielsweise bei plip und dem PPP-Treiber.

IFF_NOARP

Dieses Flag besagt, daß die Schnittstelle kein ARP beherrscht. Beispielsweise verwenden Point-to-Point-Schnittstellen kein ARP, weil das nur zu zusätzlichem Datenverkehr führen würde, ohne nützliche Informationen zu bringen. snull hat keine ARP-Fähigkeiten, setzt also dieses Flag.

IFF_PROMISC

Dieses Flag wird gesetzt, um den sogenannten “Promiscuous”-Modus zu aktivieren. Default-mäßig verwenden Ethernet-Schnittstellen einen Hardware-Filter, der sicherstellt, daß sie nur Broadcast- und an sie selbst gerichtete Pakete empfangen. Paket-Überwachungsprogramme wie tcpdump schalten den “Promiscuous”-Modus ein, um alle Pakete zu bekommen, die über das Übertragungsmedium der Schnittstelle übertragen werden.

IFF_MULTICAST

Dieses Flag wird von solchen Schnittstellen gesetzt, die Multicast-Übertragungen beherrschen. ether_setup setzt dieses Flag default-mäßig; wenn Ihr Treiber also kein Multicasting unterstützt, dann müssen Sie bei der Initialisierung dieses Flag löschen.

IFF_ALLMULTI

Dieses Flag weist der Schnittstelle an, alle Multicast-Pakete zu empfangen. Der Kernel setzt es, wenn der Host ein Multicast-Routing durchführt, aber nur dann, wenn IFF_MULTICAST gesetzt ist. IFF_ALLMULTI kann von der Schnittstelle nur gelesen werden. Wir werden das im Abschnitt "the Section called Multicasting" weiter hinten in diesem Kapitel noch zeigen.

IFF_MASTER, IFF_SLAVE

Diese Flags werden vom Lastausgleichscode verwendet. Der Schnittstellen-Treiber muß sie nicht benutzen.

IFF_PORTSEL, IFF_AUTOMEDIA

Diese Flags signalisieren, daß das Gerät zwischen verschiedenen Medientypen wie etwa ungeschütztem Twisted Pair (UTP) und koaxialen Ethernet-Kabeln wechseln kann. Wenn IFF_AUTOMEDIA gesetzt ist, wählt das Gerät das korrekte Medium automatisch aus.

IFF_DYNAMIC

Dieses Flag zeigt an, daß sich die Adresse dieser Schnittstelle ändern kann; es wird bei Dialup-Geräten benutzt.

IFF_RUNNING

Dieses Flag zeigt an, daß die Schnittstelle betriebsbereit ist. Es existiert hauptsächlich für die BSD-Kompatibilität; der Kernel benutzt es kaum. Die meisten Netzwerk-Treiber müssen sich über IFF_RUNNING keine Gedanken machen.

IFF_NOTRAILERS

Dieses Flag wird unter Linux nicht verwendet und ist nur für die BSD-Kompatibilität vorhanden.

Wenn ein Programm IFF_UP ändert, wird die Gerätemethode open oder close aufgerufen. Wird IFF_UP oder irgendein anderes Flag geändert, dann wird die Methode set_multicast_list aufgerufen. Wenn der Treiber bei der Modifikation eines der Flags bestimmte Aufgaben durchführen muß, dann muß er dies in set_multicast_list tun. Beispielsweise muß der Hardware-Filter auf der Karte informiert werden, wenn IFF_PROMISC gesetzt oder zurückgesetzt wird. Die Aufgaben dieser Gerätemethode werden später im Abschnitt “the Section called Multicasting” beschrieben.

Die Gerätemethoden

Wie auch die Zeichen- und Block-Treiber deklariert jedes Netzwerk-Gerät die Funktionen, die auf ihm arbeiten. Die unten stehende Liste enthält die Operationen, die auf Netzwerk-Geräten definiert sind. Einige der Operationen können auch auf NULL gelassen werden, andere wiederum werden normalerweise nicht angefaßt, weil ether_setup bereits passende Methoden bereitstellt.

Die Gerätemethoden einer Netzwerk-Schnittstelle können in zwei Gruppen eingeteilt werden: fundamentale Methoden und optionale Methoden. Die fundamentalen Methoden sind diejenigen, die gebraucht werden, um die Schnittstelle zu benutzen; optionale Methoden implementieren fortgeschrittenere Funktionalitäten, die streng genommen nicht benötigt werden. Die folgenden Methoden sind fundamental:

int (*open)(struct net_device *dev);

Öffnen der Schnittstelle. Eine Schnittstelle wird geöffnet, wenn sie von ifconfig aktiviert wird. Die Methode sollte alle benötigten Systemressourcen (I/O-Ports, IRQ, DMA usw.) registrieren, die Hardware aktivieren und den Verwendungszähler des Moduls inkrementieren.

int (*stop)(struct net_device *dev);

Hält die Schnittstelle an. Das geschieht, wenn die Schnittstelle deaktiviert wird. Hier sollten alle beim Öffnen vorgenommenen Operationen rückgängig gemacht werden.

int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);

Diese Methode fordert die Übertragung eines Pakets an. Das vollständige Paket (mit Headern und allem) ist in einer Socket-Buffer- (sk_buff-)Struktur enthalten. Socket-Buffer werden weiter unten eingeführt.

int (*hard_header) (struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned len);

Diese Funktion baut den Hardware-Header aus den vorher geholten Quell- und Ziel-Hardware-Adressen auf. Sie hat die Aufgabe, die in den Argumenten übergebene Information in Form eines passenden, gerätespezifischen Hardware-Headers aufzubereiten. In Ethernet-ähnlichen Schnittstellen wird eth_header default-mäßig verwendet und von ether_setup entsprechend hier eingetragen.

int (*rebuild_header) (struct sk_buff *skb);

Diese Funktion wird verwendet, um den Hardware-Header neu aufzubauen, bevor ein Paket übertragen wird. Die Default-Funktion, die in Ethernet-Geräten benutzt wird, verwendet ARP, um die fehlende Information einzutragen. Die Methode rebuild_header wird im 2.4-Kernel selten verwendet, statt dessen wird hard_header benutzt.

void (*tx_timeout)(struct net_device *dev);

Diese Methode wird aufgerufen, wenn eine Paket-Übertragung innerhalb einer angemessenen Periode nicht durchgeführt werden kann. Dabei wird angenommen, daß ein Interrupt verpaßt wurde oder die Schnittstelle blockiert ist. Die Methode sollte das Problem beheben und die Paket-Übertragung fortsetzen.

struct net_device_stats* (*get_stats)(struct net_device *dev);

Immer wenn eine Applikation Statistiken über eine Schnittstelle benötigt, wird diese Methode aufgerufen. Das geschieht beispielsweise beim Aufruf von ifconfig und netstat –i. Eine Beispiel-Implementation für snull finden Sie weiter unten in “the Section called Statistische Informationen”.

int (*set_config)(struct net_device *dev, struct ifmap *map);

Ändert die Konfigurationsinformation. Diese Methode ist der Einsprungpunkt für die Konfiguration des Treibers. Die I/O-Adresse und die Interrupt-Nummer eines Geräts können zur Laufzeit mit set_config geändert werden. Diese Fähigkeit wird vom Systemadministrator verwendet, wenn das Gerät vom Treiber sonst nicht gefunden wird. Treiber für moderne Hardware müssen diese Methode normalerweise nicht implementieren.

Es verbleiben noch die Geräte-Operationen, die wir für optional halten:

int (*do_ioctl) (struct net_device *dev, struct ifreq *ifr, int cmd);

Führt schnittstellenspezifische ioctl-Befehle aus. Die Implementation dieser Befehle wird weiter unten in “the Section called Eigene ioctl-Befehle” beschrieben. Das entsprechende Feld in struct net_device kann auf NULL gelassen werden, wenn die Schnittstelle keine schnittstellenspezifischen Befehle kennt.

void (*set_multicast_list)(struct net_device *dev);

Diese Methode wird aufgerufen, wenn sich die Multicast-Liste des Geräts ändert oder die Flags sich ändern. In “the Section called Multicasting” finden Sie nähere Beispiele und eine Implementation.

int (*set_mac_address)(struct net_device *dev, void *addr);

Diese Funktion kann implementiert werden, wenn die Schnittstelle in der Lage ist, ihre Hardware-Adresse zu ändern. Viele Schnittstellen sind dazu nicht in der Lage. Andere verwenden die Default-Implementation eth_mac_addr (aus drivers/net/net_init.c). eth_mac_addr kopiert nur die neue Adresse nach dev->dev_addr, und das auch nur, wenn die Schnittstelle nicht in Betrieb ist. Treiber, die eth_mac_addr verwenden, sollten die Hardware-MAC-Adresse bei der Konfiguration anhand von dev->dev_addr setzen.

int (*change_mtu)(struct net_device *dev, int new_mtu);

Diese Funktion erledigt alles Notwendige, wenn sich die MTU (Maximum Transfer Unit) des Gerätes ändert. Falls der Treiber etwas Besonderes machen muß, wenn die MTU verändert wird, sollte er eine eigene Funktion deklarieren, ansonsten macht die Default-Version das Richtige. Wenn es Sie interessiert, können Sie ein Muster für diese Funktion in snull finden.

int (*header_cache) (struct neighbour *neigh, struct hh_cache *hh);

header_cache wird aufgerufen, um die hh_cache-Struktur mit dem Ergebnis einer ARP-Anfrage zu füllen. Fast alle Treiber können die Default-Implementation eth_header_cache verwenden.

int (*header_cache_update) (struct hh_cache *hh, struct net_device *dev, unsigned char *haddr);

Diese Methode aktualisiert nach einer Änderung die Zieladresse in der Struktur hh_cache. Ethernet-Geräte verwenden eth_header_cache_update.

int (*hard_header_parse) (struct sk_buff *skb, unsigned char *haddr);

> > Die Methode hard_header_parse extrahiert die Quell-Adresse des in skb enthaltenen Pakets und kopiert sie in einen Puffer ab haddr. Der Rückgabewert der Funktion ist die Länge dieser Adresse. Ethernet-Geräte verwenden normalerweise eth_header_parse.

Hilfsfelder

Die Datenfelder, die in struct net_device noch übrig sind, werden von der Schnittstelle zum Abspeichern von Status-Informationen verwendet. Einige dieser Felder werden von ifconfig und netstat verwendet, um dem Benutzer Informationen über die aktuelle Konfiguration zu liefern. Eine Schnittstelle sollte daher diesen Feldern passende Werte zuweisen.

unsigned long trans_start;, unsigned long last_rx;

Diese beiden Felder sollten einen Jiffy-Wert enthalten. Der Treiber muß diese Werte beim Start einer Übertragung und beim Empfang eines Paketes aktualisieren. Das Feld trans_start kann darüber hinaus vom Netzwerk-Subsystem verwendet werden, um Blockaden des Transmitters zu entdecken. last_rx wird derzeit nicht verwendet, der Treiber sollte dieses Feld aber trotzdem pflegen, um auf zukünftige Versionen vorbereitet zu sein.

int watchdog_timeo;

Die Minimumzeit (in Jiffies), die vergehen sollte, bevor die Netzwerk-Schicht entscheidet, daß ein Übertragungs-Timeout aufgetreten ist und die tx_timeout-Funktion des Treibers aufruft.

void *priv;

Das Äquivalent von filp->private_data. Der Treiber kann über diesen Zeiger nach Belieben verfügen. Üblicherweise enthält die private Datenstruktur ein struct net_device_stats-Element. Das Feld wird weiter unten in “the Section called Jedes Gerät initialisieren” beschrieben.

struct dev_mc_list *mc_list;, int mc_count;

Diese beiden Felder werden in Multicast-Übertragungen benutzt. mc_count enthält die Anzahl der Elemente in mc_list. Ausführliche Informationen finden Sie in in“the Section called Multicasting”.

spinlock_t xmit_lock;, int xmit_lock_owner;

Das xmit_lock wird verwendet, um mehrfache gleichzeitige Aufrufe der Funktion hard_start_xmit des Treibers zu verhindern. xmit_lock_owner ist die Nummer der CPU, die die Sperre xmit_lock hält. Der Treiber sollte diese Felder nicht verändern.

struct module *owner;

Das Modul, das diese Geräte-Struktur “besitzt”. Dieses Feld wird verwendet, um den Verwendungszähler des Moduls zu pflegen.

Es gibt noch weitere Felder in struct net_device, die aber nicht von Treibern verwendet werden.