1. 程式人生 > >資料包接收系列 — 上半部實現(網絡卡驅動)

資料包接收系列 — 上半部實現(網絡卡驅動)

本文主要內容:網路資料包接收的上半部實現,主要分析網絡卡驅動相關部分。

核心版本:2.6.37

Author:zhangskd @ csdn blog

網絡卡概述

(1) 網絡卡收包

網線上的物理幀首先被網絡卡晶片獲取,網絡卡晶片會檢查物理幀的CRC,保證完整性。

然後網絡卡晶片將物理幀頭去掉,得到MAC包。

網絡卡晶片會檢查MAC包內的目的MAC地址,如果和本網絡卡的MAC地址不一樣則丟棄(混雜模式除外)。

之後網絡卡晶片將MAC幀拷貝到網絡卡內部的緩衝區,觸發硬中斷。

網絡卡的驅動程式通過硬中斷處理函式,構建sk_buff,把它拷貝到記憶體中,接下來交給核心處理。

在這個過程中,網絡卡晶片對物理幀進行了MAC匹配過濾,以減小系統負荷。

(2) 網絡卡發包

網絡卡驅動程式將IP包新增14位元組的MAC頭,構成MAC包。

MAC包中含有傳送端和接收端的MAC地址,由於是驅動程式建立MAC頭,所以可以隨便輸入地址

進行主機偽裝。

驅動程式將MAC包拷貝到網絡卡晶片內部的緩衝區,接下來由網絡卡晶片處理。

網絡卡晶片將MAC包再次封裝為物理幀,新增頭部同步資訊和CRC校驗,然後丟到網線上,就完成

一個IP報的傳送了,所有接到網線上的網絡卡都可以看到該物理幀。

(3) 網絡卡資料結構

網絡卡用net_device來表示,用register_netdevice()註冊到系統中,註冊過的網絡卡可以通過

unregister_netdevice()登出掉。

struct net_device {
    char name[IFNAME]; /* 網絡卡名稱,如eth0 */
    ...
    unsigned long mem_end; /* shared mem end,共享記憶體結束地址 */
    unsigned long mem_start; /* shared mem start ,共享記憶體起始地址 */
    unsigned long base_addr; /* device I/O address,裝置記憶體對映到I/O記憶體的起始地址 */
    unsigned int irq; /* device IRQ number,硬中斷編號 */
    ...
    unsigned long state; /* 網絡卡的狀態 */
    ...
    struct list_head dev_list;
    struct list_head napi_list; /* NAPI使用 */
    ...
    unsigned long features; /* 網絡卡功能標識 */
    ...
    int ifindex; /* Interface index. Unique device identifier. 裝置ID */
    ...
    struct net_device_stats stats; /* 統計變數 */
    /* dropped packets by core network.
     * Do not use this in drivers.
     * 被核心丟棄的資料包個數。
     */
    atomic_long_t rx_dropped;
    ...
    /* Management operations */
    const struct net_device_ops *netdev_ops; /* 網絡卡的操作函式集 */
    const struct ethtool_ops *ethtool_ops; /* 配置網絡卡 */
    ...
    unsigned int promiscuity; /* 混雜模式計數器 */
    ...
    struct netdev_queue *ingress_queue;
    struct netdev_queue *_tx;
    ...
};

/* 網絡卡的操作函式集 */
struct net_device_ops {
    int (*ndo_init) (struct net_device *dev);
    void (*ndo_uninit) (struct net_device *dev);
    int (*ndo_open) (struct net_device *dev);
    int (*ndo_stop) (struct net_device *dev);
    /* Called when a packet needs to be transmitted */
    netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev);
    ...
};

(4) 網絡卡中斷處理函式

產生中斷的每個裝置都有一個相應的中斷處理程式,是裝置驅動程式的一部分。

每個網絡卡都有一箇中斷處理程式,用於通知網絡卡該中斷已經被接收了,以及把網絡卡緩衝區的

資料包拷貝到記憶體中。

當網絡卡接收來自網路的資料包時,需要通知核心資料包到了。網絡卡立即發出中斷:嗨,核心,

我這裡有最新的資料包了。核心通過執行網絡卡已註冊的中斷處理函式來做出應答。

中斷處理程式開始執行,通知硬體,拷貝最新的網路資料包到記憶體,然後讀取網絡卡更多的資料包。

這些都是重要、緊迫而又與硬體相關的工作。核心通常需要快速的拷貝網路資料包到系統記憶體,

因為網絡卡上接收網路資料包的快取大小固定,而且相比系統記憶體也要小得多。所以上述拷貝動作

一旦被延遲,必然造成網絡卡快取溢位 - 進入的資料包占滿了網絡卡的快取,後續的包只能被丟棄。

當網路資料包被拷貝到系統記憶體後,中斷的任務算是完成了,這時它把控制權交還給被系統中斷

前執行的程式。處理和操作資料包的其他工作在隨後的下半部中進行。

上半部的實現

接收資料包的上半部處理流程為:

el_interrupt() // 網絡卡驅動

    |--> el_receive() // 網絡卡驅動

                |--> netif_rx() // 核心介面

                           |--> enqueue_to_backlog() // 核心介面

這裡以3c501網絡卡驅動為例來進行分析(這是個古董級網絡卡,實現簡單:)

el_interrupt

3c501的網絡卡中斷處理函式為el_interrupt(),呼叫inb()來獲取當前中斷處理結果,如果是RX_GOOD,

表明網絡卡成功接收了資料包,則呼叫el_receive()來進行接收處理。

/**
 * el_interrupt:
 * @irq: Interrupt number
 * @dev_id: The 3c501 that burped
 */

static irqreturn_t el_interrupt(int irq, void *dev_id)
{
    struct net_device *dev = dev_id;
    struct net_local *lp;
    int ioaddr;
    int axsr; /* Aux. status reg. */

    ioaddr = dev->base_addr; /* I/O對映地址 */
    lp = netdev_priv(dev); /* 網絡卡的私有資料 */
    spin_lock(&lp->lock); /* 上鎖 */

    /* What happened? */
    axsr = inb(AX_STATUS);

    /* log it */
    if (el_debug > 3)
        pr_debug("%s: el_interrupt() aux = %#02x\n", dev->name, axsr);
    if (lp->loading == 1 && ! lp->txing)
        pr_warning("%s: Inconsistent state loading while not in tx\n", dev->name);

    if (lp->txing) { /* 處於傳送模式,這裡不研究 */
        /* Board in transmit mode. */
        ...

    } else {
        /* In receive mode. 處於接收模式 */
        int rxsr = inb(RX_STATUS); /* 獲取中斷處理的結果 */
        if (el_debug > 5)
            pr_debug("%s: rxsr=%02x txsr=%02x rp=%04x\n", dev->name, rxsr,
                inb(TX_STATUS), inw(RX_LOW));

        /* Just reading rx_status fixes most errors. */
        if (rxsr & RX_MISSED) /* 沒有接收到資料包 */
            dev->stats.rx_missed_errors++;
        else if (rxsr & RX_RUNT) { /* 資料包長度錯誤 */
            /* Handled to avoid board lock-up. */
            dev->stats.rx_length_errors++;
            if (el_debug > 5)
                pr_debug("%s: runt.\n", dev->name);

        } else if (rxsr & RX_GOOD) {
            /* Receive worked, 成功接收資料包 */
            el_receive(dev); /* 接收函式 */

        } else {
            /* Nothing? Something is broken! */
            if (el_debug > 2)
                pr_debug("%s: No packet seen, rxsr=%02x **resetting 3c501***\n", dev->name, rxsr);

            el_reset(dev); /* 網絡卡出錯,重置 */
        }
    }

    /* Move into receive mode */
    outb(AX_RX, AX_CMD);
    outw(0x00, RX_BUF_CLR);
    inb(RX_STATUS);
    inb(TX_STATUS);
    spin_unlock(&lp->lock);
out:
    return IRQ_HANDLED;
}

網絡卡的私有資料

/* Board-specific info in netdev_priv(dev). */
struct net_local {
    int tx_pkt_start; /* The length of the current Tx packet. */
    int collisions; /* Tx collisions this packet */
    int loading; /* Spot buffer load collisions */
    int txing; /* True if card is in TX mode */
    spinlock_t lock; /* Serializing lock */
};

/**
 * netdev_priv - access network device private data
 * @dev: network device
 * Get network device private data
 */
static inline void *netdev_priv(const struct net_device *dev)
{
    return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN);
}

el_receive

el_receive()首先申請一個sk_buff,然後把網絡卡緩衝區中的資料包拷貝到skb->data。

呼叫eth_type_trans()來判斷資料包使用的三層協議(skb->protocol)、資料包的型別(skb->pkt_type)。

最後呼叫核心入口函式 — netif_rx()繼續處理。

/**
 * el_receive:
 * @dev: Device to pull the packets from
 *
 * We have a good packet. Well, not really "good", just mostly not broken.
 * We must check everything to see if it is good. In particular we occasionally
 * get wild packet sizes from the card. If the packet seems sane we PIO it off
 * the card and queue it for the protocol layers.
 */ 

static void el_receive(struct net_device *dev)
{
    int ioaddr = dev->base_addr;
    int pkt_len;
    struct sk_buff *skb;

    pkt_len = inw(RX_LOW);
    if (el_debug > 4)
        pr_debug("el_receive %d.\n", pkt_len);

    if (pkt_len < 60 || pkt_len > 1536) { /* 資料包長度錯誤 */
        if (el_debug)
            pr_debug("%s: bogus packet, length=%d\n", dev->name, pkt_len);
        dev->stats.rx_over_errors++;
        return;
    }

    /* Command mode so we can empty the buffer */
    outb(AX_SYS, AX_CMD);

    skb = dev_alloc_skb(pkt_len + 2); /* 申請一個skb,2位元組用於對齊IP */

    /* Start of frame */
    outw(0x00, GP_LOW);

    if (skb == NULL) {
        pr_info("%s: Memory squeeze, dropping packet.\n", dev->name);
        dev->stats.rx_dropped++;
        return;

    } else {
        /* 因為mac頭是14位元組,預留2位元組可使IP以16位元組對齊 */
        skb_reserve(skb, 2); 

        /* 擴充套件skb的data room,並把網絡卡快取的資料包拷貝到skb->data中 */
        insb(DATAPORT, skb_put(skb, pkt_len), pkt_len);

        /* 使用哪種三層協議,一般是IP協議 */
        skb->protocol = eth_type_trans(skb, dev);

        netif_rx(skb); /* 舊的接收處理函式 */

        dev->stats.rx_packets++;
        dev->stats.rx_bytes += pkt_len;
    }

}

乙太網協議頭

#define ETH_ALEN 6 /* Octets in one ethernet addr */
#define ETH_HLEN 14 /* Total octects in header */

struct ethhdr {
    unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
    unsigned char h_source[ETH_ALEN]; /* source ether addr */
    unsigned short h_proto; /* packet type ID field */
};

eth_type_trans()進行二層協議的簡單處理,主要是判斷資料包的型別(skb->pkt_type)

是單播/廣播/多播,返回三層協議型別(skb->protocol),一般是IP協議。

/**
 * eth_type_trans - determine the packet's protocol ID.
 * @skb: received socket data
 * @dev: receiving network device
 * The rule here is that we assume 802.3 if the type field is short enough to be a length.
 * This is normal practice and works for any now in use protocol.
 */

__be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev)
{
    struct ethhdr *eth;
    skb->dev = dev; /* 記錄接收網絡卡裝置 */
    skb_reset_mac_header(skb); /* 賦值skb->mac_header */

    /* skb->data指向三層協議頭,skb->len -= 14 */
    skb_pull_inline(skb, ETH_HLEN);
    eth = eth_hdr(skb);

    /* 記錄資料包型別skb->pkt_type */  
    if (unlikely(is_multicast_ether_addr(eth->h_dest))) { /* 是否為多播 */
        if (! compare_ether_addr_64bits(eth->h_dest, dev->broadcast)) /* 是否為廣播 */
            skb->pkt_type = PACKET_BROADCAST;
        else
            skb->pkt_type = PACKET_MULTICAST;

    } else if (unlikely(compare_ether_addr_64bits(eth->h_dest, dev->dev_addr)))
        skb->pkt_type = PACKET_OTHERHOST; /* 其它網絡卡的 */

    /* 判斷使用的三層協議 */
    if (netdev_uses_dsa_tags(dev))
        return htons(ETH_P_DSA);
    if (netdev_uses_trailer_tags(dev))
        return htons(ETH_P_TRAILER);

    if (ntohs(eth->h_proto) >= 1536) /* 大於1536即為協議ID */
        return eth->h_proto;

    if (skb->len >= 2 && *(unsigned short *)(skb->data) == 0xFFFF)
        return htons(ETH_P_802_3);
    return htons(ETH_P_802_2);
}