資料包接收系列 — 上半部實現(網絡卡驅動)
本文主要內容:網路資料包接收的上半部實現,主要分析網絡卡驅動相關部分。
核心版本: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);
}