網(wǎng)絡數(shù)據(jù)接收過程,,從數(shù)據(jù)包到達網(wǎng)卡的物理接口開始,然后由網(wǎng)卡的驅(qū)動程序交給網(wǎng)絡協(xié)議棧,,最后經(jīng)過協(xié)議棧的一層層處理之后交給應用程序,。大致上是這樣的過程,但實際上有更多的細節(jié),。本文中主要介紹第一個和第二個步驟,。 我們本文中依然以一個Realtek 8139網(wǎng)卡為例(驅(qū)動程序為/drivers/net/8139too.c)。請注意在內(nèi)核代碼中receive都是用rx簡寫的,。 (1)注冊與激活軟中斷 在生成net_device對象及初始化的函數(shù)rtl8139_init_one中已經(jīng)初始化dev->open方法為rtl8139_open函數(shù)(在本系列文章2:初始化中的net_device對象中已經(jīng)介紹,,點這里查看)。在rtl8139_open函數(shù)(這個函數(shù)在網(wǎng)卡啟動時被調(diào)用)中注冊了一個中斷函數(shù)rtl8139_interrupt: retval = request_irq (dev->irq, rtl8139_interrupt, SA_SHIRQ, dev->name, dev);所以只要當網(wǎng)卡開啟后(狀態(tài)為up),,當網(wǎng)絡數(shù)據(jù)包到達時,,都會產(chǎn)生一個硬件中斷(這不同于后面的軟中斷)。這個硬件中斷由內(nèi)核調(diào)用中斷處理程序rtl8139_interrupt函數(shù)處理,。這個函數(shù)比較重要,,網(wǎng)卡發(fā)送或者接收數(shù)據(jù)時內(nèi)核都會調(diào)用這個函數(shù)處理中斷,而中斷的類型是根據(jù)網(wǎng)卡狀態(tài)寄存器的不同而確定的,。本文中僅涉及接收數(shù)據(jù)的中斷,,因此只給出了接收的代碼: static irqreturn_t rtl8139_interrupt (int irq, void *dev_instance, struct pt_regs *regs){ if (status & RxAckBits){ if(netif_rx_schedule_prep(dev)) __netif_rx_schedule (dev); } } 主要函數(shù)為__netif_rx_schedule(函數(shù)名意為:network interface receive schedule,即網(wǎng)絡接口接收調(diào)度),,因為當網(wǎng)卡接收到數(shù)據(jù)包之后,,馬上告知CPU在合適的時間去啟動調(diào)度程序,輪詢(poll)網(wǎng)卡,。 請注意:Linux接收網(wǎng)絡數(shù)據(jù)實際上有兩種方式,。(a)中斷。每個數(shù)據(jù)包到達都會產(chǎn)生一個中斷,,然后由內(nèi)核調(diào)用中斷處理程序處理,。 (b)NAPI(New API)。Linux內(nèi)核2.6版本之后加入的新機制,,核心方法是:不采用中斷的方式讀取數(shù)據(jù),,而代之以首先采用中斷喚醒數(shù)據(jù)接收的服務程序,然后以POLL的方法來輪詢數(shù)據(jù),。 因此本文中只介紹NAPI的接收方式,。我們不再詳細介紹這種機制,網(wǎng)上可找到比較多的資料,可以參考IBM的技術(shù)文章:NAPI 技術(shù)在 Linux 網(wǎng)絡驅(qū)動上的應用和完善,。 __netif_rx_schedule函數(shù)的定義如下: static inline void __netif_rx_schedule(struct net_device *dev){ local_irq_save(flags);//disable interrupt //Add interface to tail of rx poll list list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list); //activate network rx softirq __raise_softirq_irqoff(NET_RX_SOFTIRQ); local_irq_restore(flags); } 這個函數(shù)最核心的就是三步: (a)local_irq_save:禁用中斷 (b)list_add_tail:將設(shè)備添加到softnet_data的poll_list中,。 (c)激活一個軟中斷NET_RX_SOFTIRQ。 ====================================== 說到這里我們必須介紹一個關(guān)鍵數(shù)據(jù)結(jié)構(gòu)softnet_data,,每個CPU都擁有一個這樣的網(wǎng)絡數(shù)據(jù)隊列(所以函數(shù)中使用了__get_cpu_var函數(shù)取得),定義如下: struct softnet_data{ int throttle; /*為 1 表示當前隊列的數(shù)據(jù)包被禁止*/ int cng_level; /*表示當前處理器的數(shù)據(jù)包處理擁塞程度*/ int avg_blog; /*某個處理器的平均擁塞度*/ struct sk_buff_head input_pkt_queue; /*接收緩沖區(qū)的sk_buff隊列*/ struct list_head poll_list; /*POLL設(shè)備隊列頭*/ struct net_device output_queue; /*網(wǎng)絡設(shè)備發(fā)送隊列的隊列頭*/ struct sk_buff completion_queue; /*完成發(fā)送的數(shù)據(jù)包等待釋放的隊列*/ struct net_device backlog_dev; /*表示當前參與POLL處理的網(wǎng)絡設(shè)備*/ }; 大致說明一下這個數(shù)據(jù)結(jié)構(gòu)的意義,。某個網(wǎng)卡產(chǎn)生中斷之后,,內(nèi)核就把這個網(wǎng)卡掛載到輪詢列表(poll_list)中。一個CPU會輪詢自己的列表中的每一個網(wǎng)卡,,看看它們是不是有新的數(shù)據(jù)包可以處理,。我們需要先用一個比喻說明這個數(shù)據(jù)結(jié)構(gòu)與輪詢的關(guān)系:網(wǎng)卡就是佃戶,CPU就是地主,。佃戶有自己種的糧食(網(wǎng)絡數(shù)據(jù)包),,但地主家也有糧倉(softnet_data)。地主要收糧的時候,,就會挨家挨戶的去催佃戶交糧,,放到自己的糧倉里。 ======================================= (2)軟中斷處理 我們知道:激活軟中斷之后,,并不是馬上會被處理的,。只有當遇到軟中斷的檢查點時,系統(tǒng)才會調(diào)用相應的軟中斷處理函數(shù),。 所有的網(wǎng)絡接收數(shù)據(jù)包的軟中斷處理函數(shù)都是net_rx_action,。這個函數(shù)的詳細注釋可以看IBM的那篇技術(shù)文章。其核心語句就是一個輪詢的函數(shù): dev->poll就調(diào)用了相應設(shè)備的poll函數(shù),。也就是說,,當CPU處理軟中斷時,才去輪詢網(wǎng)卡,,把數(shù)據(jù)放入softnet_data中,。 下面是整個中斷和輪詢過程的一個示意圖: 下面我們解釋一下poll函數(shù)具體干了什么事情。 而我們知道,,在Realtek 8139網(wǎng)卡的net_device對象中我們已經(jīng)注冊了一個poll函數(shù): dev->poll = rtl8139_poll那么一次poll就表示從網(wǎng)卡緩沖區(qū)取出一定量的數(shù)據(jù),。而rtl8139_poll函數(shù)中調(diào)用的主要函數(shù)就是rtl8139_rx函數(shù)。這個函數(shù)是完成從網(wǎng)卡取數(shù)據(jù),,分配skb緩沖區(qū)的核心函數(shù),。其核心代碼如下: static int rtl8139_rx(struct net_device *dev, struct rtl8139_private *tp, int budget){ skb = dev_alloc_skb (pkt_size + 2); eth_copy_and_sum (skb, &rx_ring[ring_offset + 4], pkt_size, 0);//memcpy skb->protocol =eth_type_trans (skb, dev); netif_receive_skb (skb); } 工作主要分為4部分: (a)給sk_buff數(shù)據(jù)結(jié)構(gòu)(skb)分配空間。 (b)從網(wǎng)卡的環(huán)形緩沖區(qū)rx_ring中拷貝出網(wǎng)絡數(shù)據(jù)包放到sk_buff對象skb中,。這個函數(shù)實質(zhì)上就是一個memcpy函數(shù),。 (c)在skb中標識其協(xié)議為以太網(wǎng)幀。 (d)調(diào)用netif_receice_skb函數(shù)。 { if (!ptype->dev || ptype->dev == skb->dev) { if (pt_prev) ret = deliver_skb(skb, pt_prev); pt_prev = ptype; } } list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) { if (ptype->type == type && (!ptype->dev || ptype->dev == skb->dev)) { if (pt_prev) ret = deliver_skb(skb, pt_prev); pt_prev =ptype; } } 兩個循環(huán)分別遍歷了兩個鏈表:ptype_all和ptype_base,。前者是內(nèi)核中注冊的sniffer,后者則是注冊到內(nèi)核協(xié)議棧中的網(wǎng)絡協(xié)議類型,。如果skb中的協(xié)議類型type與ptype_base中的類型一致,,那么使用deliver_skb函數(shù)發(fā)送給這個協(xié)議一份,定義如下: static __inline__ int deliver_skb(struct sk_buff *skb, struct packet_type *pt_prev){ atomic_inc(&skb->users); return pt_prev->func(skb, skb->dev, pt_prev); } 這個函數(shù)只是一個封裝函數(shù),,實際上調(diào)用了每個packet type結(jié)構(gòu)中注冊的處理函數(shù)func,。 struct packet_type {unsigned short type; struct net_device *dev; int (*func) (struct sk_buff *, struct net_device *, struct packet_type *); void *af_packet_priv; structlist_head list; }; 例如:IP包類型的處理函數(shù)就是ip_rcv(定義在/net/ipv4/ip_output.c文件中),定義如下: static struct packet_type ip_packet_type = {.type = __constant_htons(ETH_P_IP), .func =ip_rcv, }; 這個包的類型是在ip_init協(xié)議初始化時添加到全局的ptype_base哈希數(shù)組中的: void __init ip_init(void){ dev_add_pack(&ip_packet_type); } |
|
來自: kylin_1983 > 《Linux》