Paket-Empfang

Das Empfangen von Daten aus dem Netzwerk ist schwieriger als die Übertragung, weil ein sk_buff im Interrupt-Handler alloziert und an die oberen Schichten hochgereicht werden muß, denn die beste Möglichkeit, ein Paket zu empfangen, ist in einem Interrupt-Handler — es sei denn, es handelt sich um eine reine Software-Schnittstelle wie snull oder loopback. Es ist zwar möglich, einen Treiber zu schreiben, der immer wieder fragt, ob Daten vorliegen, und es gibt auch einige davon im offiziellen Kernel, aber eine Interrupt-gesteuerte Operation ist viel besser, weil sie höhere Datendurchsätze erreicht und die CPU weniger belastet. Da die meisten Netzwerk-Schnittstellen Interrupt-gesteuert arbeitet, werden wir hier nicht auf die andere Implementation eingehen, die einfach nur Kernel-Timer benutzt.

Die Implementation von snull trennt die Hardware-Details von der geräteunabhängigen Verwaltung. Daher wird die Funktion snull_rx aufgerufen, nachdem die Hardware ein Paket empfangen hat und wenn sich dieses Paket bereits im Speicher des Computers befindet. snull_rx bekommt daher einen Zeiger auf die Daten sowie die Länge der Daten übergeben. Diese Funktion hat nur die eine Aufgabe, das Paket zusammen mit einigen zusätzlichen Informationen an die oberen Schichten des Netzwerk-Codes weiterzuleiten. Der Code ist unabhängig davon, wie man an den Zeiger auf die Daten und deren Länge herankommt.


 
void snull_rx(struct net_device *dev, int len, unsigned char *buf)
{
    struct sk_buff *skb;
    struct snull_priv *priv = (struct snull_priv *) dev->priv;

    /*
     * Das Paket wurde vom Uebertragungsmedium entgegengenommen. Einen
     * skb dazu aufbauen, damit die oberen Schichten etwas damit
     * anzufangen wissen.
     */
    skb = dev_alloc_skb(len+2);
    if (!skb) {
        printk("snull rx: low on mem - packet dropped\n");
        priv->stats.rx_dropped++;
        return;
    }
    memcpy(skb_put(skb, len), buf, len);

    /* Metadaten schreiben und an die empfangende Ebene uebergeben */
    skb->dev = dev;
    skb->protocol = eth_type_trans(skb, dev);
    skb->ip_summed = CHECKSUM_UNNECESSARY; /* keine Pruefsumme */
    priv->stats.rx_packets++;
    priv->stats.rx_bytes += len;
    netif_rx(skb);
    return;
}

Diese Funktion ist allgemein genug, um als Vorlage für jeden Netzwerk-Treiber dienen zu können, aber trotzdem müssen wir einiges erklären, bevor Sie dieses Code-Fragment sicher verwenden können.

Als erstes muß ein Puffer für das Paket alloziert werden. Beachten Sie, daß die Funktion zum Allozieren des Puffers (dev_alloc_skb) die Datenlänge wissen möchte. Diese Information wird von der Funktion verwendet, um Platz für den Puffer zu allozieren. Die Allokationsfunktion kmalloc wird von dev_alloc_skb mit atomarer Priorität aufgerufen und kann daher sicher zur Interrupt-Zeit benutzt werden. Der Kernel bietet auch noch andere Schnittstellen zur Allokation von Socket-Buffern an, aber es lohnt sich nicht, diese hier einzuführen; wir erklären Socket-Buffer detaillierter in “the Section called Die Socket-Buffer” weiter unten in diesem Kapitel.

Wenn wir einmal einen gültigen skb-Zeiger haben, werden die Paketdaten mit memcpy in den Puffer kopiert; dabei aktualisiert die Funktion skb_put den Datenende-Zeiger im Puffer und gibt einen Zeiger auf den neu erzeugten Platz zurück.

Wenn Sie einen hochleistungsfähigen Treiber für eine Schnittstelle schreiben, die vollständig Busmastering-fähig ist, gibt es eine mögliche Optimierung, die sich anzuschauen lohnt. Manche Treiber allozieren Socket-Buffer für eingehende Pakete vor dem Empfang und weisen dann die Schnittstelle an, die Paketdaten direkt in den Platz des Socket-Buffers zu schreiben. Die Netzwerk-Schicht kooperiert mit dieser Strategie, indem sie alle Socket-Buffer in DMA-fähigem Speicher alloziert. Dadurch wird eine separate Kopieroperation zum Füllen des Socket-Buffers vermieden, aber man muß vorsichtig mit den Puffer-Größen sein, weil man nicht im voraus weiß, wie groß das eingehende Paket ist. Die Implementation einer change_mtu-Methode ist in dieser Situation ebenfalls wichtig, weil sie dem Treiber erlaubt, auf die Änderung der maximalen Paketgröße zu reagieren.

Die Netzwerk-Schicht muß einige Informationen haben, bevor sie mit dem Paket etwas anfangen kann. Die Felder dev und protocol müssen daher gefüllt werden, bevor der Puffer nach oben weitergereicht wird. Anschließend müssen wir angeben, ob und wie Prüfsummen über das Paket gebildet werden sollen (snull benutzt keine Prüfsummen). Die möglichen Werte für skb->ip_summed sind:

CHECKSUM_HW

Die Netzwerkkarte berechnet die Prüfsumme in der Hardware. Ein Beispiel wäre die HME-Schnittstelle auf SPARCs.

CHECKSUM_NONE

Prüfsummen müssen noch überprüft werden, dies ist die Aufgabe der System-Software. Dies ist der Default in neu allozierten Puffern.

CHECKSUM_UNNECESSARY

Keine Prüfsummen berechnen. Dies ist die Policy bei snull und loopback.

Schließlich aktualisiert der Treiber seine Statistik-Zähler, um festzuhalten, daß ein Paket empfangen worden ist. Die Statistik-Struktur besteht aus mehreren Feldern, deren wichtigste rx_packets, rx_bytes, tx_packets und tx_bytes sind. Diese Felder enthalten die Anzahl der empfangenen beziehungsweise versendeten Pakete und die Gesamtanzahl der übertragenen Oktette. Alle Felder werden noch ausführlich im Abschnitt “the Section called Statistische Informationen” beschrieben.

Der letzte Schritt beim Empfangen eines Paketes ist der Aufruf von netif_rx, mit dem der Socket-Buffer an die oberen Schichten übergeben wird.