Abwärtskompatibilität

In der Version 2.3.43 des Kernels wurde das Netzwerk-Subsystem umfassend renoviert. Die neue “softnet”-Implementation war hinsichtlich Performance und sauberem Design eine große Verbesserung. Natürlich brachte dieser Umbau auch Änderungen an der Schnittstelle für Netzwerk-Treiber mit sich, aber weniger, als man vielleicht erwarten könnte.

Unterschiede in Linux 2.2

Zunächst einmal wurde in Linux 2.3.14 die Netzwerk-Geräte-Struktur, die früher immer struct device hieß, in struct net_device umbenannt. Der neue Name paßt natürlich besser, weil die Struktur nie Geräte im allgemeinen beschreiben sollte.

Vor Version 2.3.43 gab es die Funktionen netif_start_queue, netif_stop_queue und netif_wake_queue nicht. Die Übertragung von Paketen wurde statt dessen durch drei Felder in der Struktur device gesteuert. sysdep.h implementiert die drei Funktionen unter Verwendung der drei Felder, wenn für 2.2 oder 2.0 kompiliert wird.

unsigned char start;

Diese Variable zeigte an, daß die Schnittstelle betriebsbereit ist; sie wurde normalerweise in der open-Methode des Treibers auf 1 gesetzt. In der aktuellen Implementation wird statt dessen netif_start_queue aufgerufen.

unsigned long interrupt;

interrupt wurde verwendet, um anzuzeigen, daß das Gerät einen Interrupt bediente, und wurde daher zu Beginn eines Interrupt-Handlers auf 1 und vor dem Rücksprung auf 0 gesetzt. Dies war nie ein hinreichender Ersatz für korrektes Sperren und wurde daher durch interne Spinlocks ersetzt.

unsigned long tbusy;

Wenn diese Variable einen von Null verschiedenen Wert hatte, wurde damit angezeigt, daß das Gerät keine weiteren ausgehenden Pakete entgegennehmen konnte. Wo ein 2.4-Treiber netif_stop_queue aufruft, setzten ältere Treiber tbusy auf 1. Zum Neustarten der Warteschlange wurde tbusy zurück auf 0 gesetzt und mark_bh(NET_BH) aufgerufen.

Normalerweise war das Setzen von tbusy ausreichend, um sicherzustellen, daß die Methode hard_start_xmit des Treibers nicht aufgerufen wurde. Wenn das Netzwerk-System aber der Meinung war, daß eine Transmitter-Blockade eingetreten sein mußte, dann rief es diese Methode trotzdem auf. Es gab vor softnet keine tx_timeout-Methode. Daher mußten ältere Treiber explizit überprüfen, ob hard_start_xmit aufgerufen worden war, während tbusy gesetzt war, und sich entsprechend verhalten.

Der Typ des Feldes name in struct device war auch anders. In Version 2.2 war dies einfach:


char *name;

Der Speicherplatz für den Namen der Schnittstelle mußte also separat alloziert werden, und name mußte auf diesen Speicherplatz verweisen. Der Gerätename stand normalerweise in einer statischen Variable im Treiber. Die %d-Notation für dynamisch zugewiesene Schnittstellennamen gab es in 2.2 auch nicht. Statt dessen allozierte der Kernel den nächsten eth-Namen, wenn der Name mit einem Null-Byte oder einem Leerzeichen begann. Der 2.4-Kernel implementiert dieses Verhalten noch; es sollte aber nicht mehr genutzt werden. Ab 2.5 wird voraussichtlich nur noch das %d-Format unterstützt.

Das Feld owner (und das Makro SET_MODULE_OWNER) wurden im Kernel 2.4.0-test11, kurz vor dem offiziellen stabilen Release hinzugefügt. Davor mußten Netzwerktreiber-Module ihre Verwendungszähler selbst pflegen. sysdep.h definiert ein leeres SET_MODULE_OWNER für Kernel, die dieses Makro nicht haben; portabler Code sollte seinen Verwendungszähler weiterhin manuell pflegen (und das Netzwerk-System dieses auch tun lassen).

Die Verbindungszustandsfunktionen (netif_carrier_on und netif_carrier_off) gab es in 2.2 noch nicht. Der Kernel kam damals einfach ohne diese Informationen aus.

Weitere Unterschiede in Linux 2.0

Auch in der 2.1-Entwicklungsserie gab es eine Reihe von Änderungen an der Schnittstelle für Netzwerk-Treiber. Die meisten waren kleine Änderungen an Funktionsprototypen und nicht umfassende Änderungen des Netzwerk-Codes insgesamt.

Die Schnittstellen-Statistiken standen in einer Struktur namens struct enet_statistics, die in <linux/if_ether.h> definiert war. Selbst Nicht-Ethernet-Treiber verwendeten diese Struktur. Die Feldnamen waren die gleichen wie in der heutigen struct net_device_stats, die Felder rx_bytes und tx_bytes gab es aber noch nicht.

Der 2.0-Kernel behandelte Transmitter-Blockaden genauso wie der 2.2-Kernel. Es gab aber noch eine zusätzliche Funktion:


void dev_tint(struct device *dev);

Diese Funktion wurde vom Treiber aufgerufen, nachdem eine Blockade beseitigt worden war, und startete die Übertragung von Paketen neu.

Eine Reihe von Funktionen hatten andere Prototypen. dev_kfree_skb hatte ein zweites, ein Integer-Argument, das entweder FREE_READ für eingehende Pakete (also vom Treiber allozierte skbs) oder FREE_WRITE für ausgehende Pakete (also vom Netzwerk-Code allozierte skbs) war. Fast alle Aufrufe von dev_kfree_skb im Netzwerk-Treiber-Code verwendeten FREE_WRITE. Die nicht überprüfenden Versionen der skb-Funktionen (wie __skb_push) gab es noch nicht; sysdep.h im Beispiel-Code emuliert diese Funktionen unter 2.0.

Die Methode rebuild_header hatte andere Argumente:


int (*rebuild_header) (void *eth, struct device *dev,
unsigned long raddr, struct sk_buff *skb);

Der Linux-Kernel verwendete auch rebuild_header deutlich mehr; diese Funktion leistete einen Großteil der Arbeit, die hard_header heute erledigt. Wenn snull unter Linux 2.0 kompiliert wird, dann baut es die Hardware-Header folgendermaßen auf:


int snull_rebuild_header(void *buff, struct net_device *dev, unsigned long dst,
                    struct sk_buff *skb)
{
    struct ethhdr *eth = (struct ethhdr *)buff;

    memcpy(eth->h_source, dev->dev_addr, dev->addr_len);
    memcpy(eth->h_dest, dev->dev_addr, dev->addr_len);
    eth->h_dest[ETH_ALEN-1]   ⁁= 0x01;   /* dest sind wir xor 1 */
    return 0;
}

Die Gerätemethoden zum Cachen von Headern sahen in diesem Kernel auch deutlich anders aus. Wenn Ihr Treiber diese Funktionen direkt implementieren muß (das trifft nur auf wenige zu) und auch unter Linux 2.0 funktionieren soll, dann können Sie sich die Definitionen in <linux/netdevice.h> anschauen, um zu sehen, wie man das damals machte.

Suchen und HAVE_DEVLIST

Wenn Sie sich den Quellcode so ziemlich jedes Netzwerk-Treibers im Kernel anschauen, dann finden Sie ein Code-Muster, das etwa so aussieht:


#ifdef HAVE_DEVLIST
/*
 * Support for an alternate probe manager,
 * which will eliminate the boilerplate below.
 */
struct netdev_entry netcard_drv =
{cardname, netcard_probe1, NETCARD_IO_EXTENT, netcard_portlist};
#else
/* Regular probe routine defined here */

> > Diesen Code gibt es interessanterweise seit der 1.1-Entwicklungsserie, aber wir warten immer noch auf den alternativen Such-Manager. Man kann wahrscheinlich getrost davon ausgehen, daß man sich auf diese großartige Änderung nicht besonders vorbereiten muß, insbesondere weil die Ideen, wie das implementiert werden sollte, sich sicherlich inzwischen geändert haben.