網路驅動接收到報文後,會初始化skb->protocol 字段。鏈路層的接收函式netif_receive_skb會根據該字段來確定把報文送給那個協議模組進一步處理。
乙太網的裝置呼叫eth_type_trans()來給skb->protocol賦值。
__be16eth_type_trans(struct sk_buff *skb,struct net_device *dev)
/*如果報文的目的mac不是到接收裝置的mac,設定skb->pkt_type*/
else if (1 /*dev->flags&iff_promisc */ )
/*marvell 交換晶元dsa 頭*/
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)
return eth->h_proto;
/*以下處理非乙太網的報文,不討論*/
rawp = skb->data;
if (*(unsigned short *)rawp == 0xffff)
return htons(eth_p_802_3);
/* real 802.2 llc*/
return htons(eth_p_802_2);
}網路裝置驅動在呼叫netif_receive_skb()或netif_rx()前呼叫 eth_type_trans():
skb->protocol = eth_type_trans(skb,dev);
每個網路層協議都會初始化乙個接收報文的函式。linux核心中使用資料結構 struct packet_type 來描述單個網路層協議。
struct packet_type
;linux 核心中定義了一張hash 表ptype_base,hash key 是struct packet_type 中的type欄位。表中的每個元素都指向乙個struct packet_type 的鍊錶。
同時還定義了乙個struct packet_type 的鍊錶ptype_all。這個鍊錶上的協議處理程式接收所以協議的報文,主要用於網路工具和網路嗅探器接收報文。比如tcpdump 抓包程式和原始套接字使用這種型別的packet_type結構。
/* 0800 ip
* 8100 802.1q vlan
* 0001 802.3
* 0002 ax.25
* 0004 802.2
* 8035 rarp
* 0005 snap
* 0805 x.25
* 0806 arp
* 8137 ipx
* 0009 localtalk
* 86dd ipv6
*/#define ptype_hash_size (16)
#define ptype_hash_mask (ptype_hash_size - 1)
static define_spinlock(ptype_lock);
static struct list_headptype_base[ptype_hash_size] __read_mostly;
static struct list_headptype_all__read_mostly; /* taps */
這一張hash 表,協議棧根據報文型別找到相應的處理函式。
linux 核心中使用如下函式進行協議的新增和刪除:
新增協議:
voiddev_add_pack(struct packet_type *pt)
spin_unlock_bh(&ptype_lock);
}如果協議型別定義為 eth_p_all,就是接收所有型別的報文。就把該packet_type加入到ptyte_all 鍊錶上。
每個協議要自己定義自己的paket_type變數,初始化自己的資料。然後自協議模組初始化時呼叫
dev_add_packet把自己的packet_type 加入到 packet_base hash表中。
協議棧的真正入口函式netif_receive_skb()
intnetif_receive_skb(struct sk_buff *skb)
/*增加收包統計計數*/
__get_cpu_var(netdev_rx_stat).total++;
/*初始化skb 中以下指標*/
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
skb->mac_len = skb->network_header - skb->mac_header;
pt_prev = null;
/*在ptype_base 和 ptype_base中查詢,要使用rcu 讀鎖保護臨界區*/
rcu_read_lock();
/*如果核心註冊了協議嗅探器,把skb 拷貝乙份傳給它進行處理。
注意:
經過這輪迴圈,最後乙個協議嗅探器的執行函式是沒有被呼叫的,
放在下面進行呼叫,因為報文處理的最後乙個處理函式被呼叫時
不需要進行skb user 引用計數的加 1,所以,下乙個處理函式會把
最後乙個ptype 傳遞進去,如果該函式要處理掉該skb時,應該先執行
該ptype 處理函式後再執行自己的處理程式*/
list_for_each_entry_rcu(ptype, &ptype_all, list)
}/*進入橋進行二層處理,如果返回skb == null,說明skb 被直接二層
**走了,不用再送網路層了,函式直接返回*/
skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
/*處理vlan*/
skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
/*經過以上處理,報文沒被消化掉,就在 ptype_base hash 表中
找到該報文的協議接收函式,送給相應協議處理*/
type = skb->protocol;
list_for_each_entry_rcu(ptype,
&ptype_base[ntohs(type) & ptype_hash_mask], list)
}if (pt_prev)
else
out:
rcu_read_unlock();
return ret;
}export_symbol(netif_receive_skb);
static inline intdeliver_skb(struct sk_buff *skb,
struct packet_type *pt_prev,
struct net_device *orig_dev)
Linux網路子系統中鏈路層中GRO的處理
根據上篇博文的介紹,gro需要支援gro的每種協議都要實現自己的報文匹配合併函式和合併完成函式。這裡我們先來看看鏈路層上 實現的自己的gro函式。鏈路層的接收匹配函式 napi gro receive napi,skb 該函式對報文進行匹配,並不合併報文。匹配規則 必須同時滿足以下兩個條件 1 兩個...
Linux 網路子系統底層機制分析 1
linux 網路子系統底層機制分析 1 網路子系統在linux中的地位非常重要。在如今這個嚴重依賴網際網路,強調協同工作的時代,乙個高效,穩定的網路處理系統是留住使用者群的基本手段。前段時間花了一部分時間學習了一下linux的網路子系統的源 以及一些處理機制。這部分是由於工作的原因,另一部分原因是想...
Linux網路子系統中報文的接收及NAPI的實現
報文的接收是整個協議棧的入口,負責從網絡卡中把報文接收並送往核心協議棧相應協議處理模組處理。一種是網絡卡產生中斷,通知核心進行接收報文。一次中斷接收乙個報文。在中斷處理程式中把報文從硬體快取中拷貝到記憶體中,並把報文加入到協議棧中對應的入口佇列中,中斷退出時呼叫收包軟中斷來從相應佇列來讀取報文進行處...