| 數(shù)據(jù)報(bào)的接收過程詳解---從網(wǎng)卡到L3層(非NAPI,即接收數(shù)據(jù)采用中斷方式) |
|
|
| 來源: ChinaUnix博客 日期: 2008.12.17 13:07 (共有0條評(píng)論) 我要評(píng)論 |
| |
剛來實(shí)驗(yàn)室的時(shí)候主要看的就是數(shù)據(jù)報(bào)在協(xié)議棧的具體傳輸過程,當(dāng)時(shí)有過記錄,但是很凌亂,最近又回頭看了看相關(guān)知識(shí)和內(nèi)核源代碼,算是理清了思路,特整理在此.本篇筆記寫的是2.4中數(shù)據(jù)報(bào)的接收過程,從網(wǎng)卡到網(wǎng)絡(luò)層的具體路線,2.4中大部分網(wǎng)卡采用的是中斷的方式接收數(shù)據(jù)(好像是從2.5以后開始支持NAPI的,不太確定),本篇筆記總結(jié)的是非NAPI,即采用中斷接受數(shù)據(jù)的路線.ok,開始進(jìn)入主題.
當(dāng)網(wǎng)卡接收到一個(gè)數(shù)據(jù)報(bào)之后,產(chǎn)生一個(gè)中斷通知內(nèi)核,然后內(nèi)核會(huì)調(diào)用相關(guān)的中斷處理函數(shù).一般,中斷處理程序做如下工作: 1,把數(shù)據(jù)報(bào)拷貝到一個(gè)sk_buff中. 2,初始化sk_buff中的一些成員變量,為以后傳輸?shù)缴蠈佑?特別是skb->protocol,標(biāo)示了上層的具體協(xié)議,后面會(huì)調(diào)用相應(yīng)的接收函數(shù). 3,更新網(wǎng)卡的狀態(tài). 4,調(diào)用netif_rx將數(shù)據(jù)報(bào)送往上層(采用NAPI時(shí),此處將調(diào)用netif_rx_schdule).
看netif_rx源代碼之前首先要了解一下softnet_data結(jié)構(gòu).此結(jié)構(gòu)是基于cpu的而不是device,即每個(gè)cpu對(duì)應(yīng)一個(gè)softnet_data. struct softnet_data { /*throttle用于擁塞控制,當(dāng)擁塞時(shí)被設(shè)置,此后來的數(shù)據(jù)包都被丟棄*/ int throttle; /*netif_rx返回的擁塞級(jí)別*/ int cng_level; int avg_blog; /*input_pkt_queue是skb的隊(duì)列,接收到的skb全都進(jìn)入到此隊(duì)列等待后續(xù)處理*/ struct sk_buff_head input_pkt_queue; /*poll_list是一個(gè)雙向鏈表,鏈表的成員是有接收數(shù)據(jù)等待處理的device*/ struct list_head poll_list; /*net_device鏈表,成員為有數(shù)據(jù)報(bào)要發(fā)送的device*/ struct net_device *output_queue; /*完成發(fā)送的數(shù)據(jù)包等待釋放的隊(duì)列*/ struct sk_buff *completion_queue; /*注意,backlog_dev不是一個(gè)指針,而是一個(gè)net_device實(shí)體,代表了調(diào)用net_rx_action時(shí)的device*/ struct net_device backlog_dev; };
ok,了解了softnet_data結(jié)構(gòu)體后接下來看netif_rx的源代碼.
/* *主要工作: *1,初始化sk_buff的一些域值,比如數(shù)據(jù)報(bào)接收的時(shí)間截. *2,把接收到的數(shù)據(jù)報(bào)入input_pkt_queue接收隊(duì)列,并且通知內(nèi)核然后觸發(fā)相應(yīng)的軟中斷,即NET_RX_SOFTIRQ. * 2.1:當(dāng)隊(duì)列為空時(shí),調(diào)用netif_rx_schedule,觸發(fā)軟中斷. * 2.2:當(dāng)隊(duì)列非空時(shí),直接將數(shù)據(jù)報(bào)入隊(duì)列,因?yàn)榇藭r(shí)已經(jīng)調(diào)用了軟中斷,所以無需再調(diào)用. *3,更新相關(guān)狀態(tài)信息. *執(zhí)行完后,流程來到net_rx_action */ int netif_rx(struct sk_buff *skb) { int this_cpu = smp_processor_id(); struct softnet_data *queue; unsigned long flags; //如果接收到的數(shù)據(jù)包時(shí)間截未設(shè)置,設(shè)置時(shí)間截 if (skb->stamp.tv_sec == 0) do_gettimeofday(&skb->stamp); queue = &softnet_data[this_cpu]; local_irq_save(flags); //disable irqs on local cpu netdev_rx_stat[this_cpu].total++; if (queue->input_pkt_queue.qlen = netdev_max_backlog) { if (queue->input_pkt_queue.qlen) { if (queue->throttle) goto drop; enqueue: dev_hold(skb->dev); //即automic_inc(&(dev)->refcnt)累加設(shè)備引入計(jì)數(shù)器 __skb_queue_tail(&queue->input_pkt_queue,skb); //把skb入input_pkt_queue隊(duì)列,此處只是指針的指向,而不是數(shù)據(jù)報(bào)的拷貝,目的是節(jié)省時(shí)間. local_irq_restore(flags); //eable irqs on local cpu #ifndef OFFLINE_SAMPLE get_sample_stats(this_cpu); #endif return queue->cng_level;//返回?fù)砣燃?jí) } //驅(qū)動(dòng)程序不斷的調(diào)用netif_rx,將數(shù)據(jù)包入隊(duì)操作,當(dāng)qlen==0時(shí),執(zhí)行下面代碼 //如果設(shè)置了擁塞位,將其設(shè)為0 if (queue->throttle) { queue->throttle = 0; #ifdef CONFIG_NET_HW_FLOWCONTROL if (atomic_dec_and_test(&netdev_dropping)) netdev_wakeup(); #endif } /* netif_rx_schedule主要完成兩件事 1):將接收skb的device加入"處理數(shù)據(jù)包的設(shè)備"的鏈表當(dāng)中 2):觸發(fā)軟中斷函數(shù),進(jìn)行數(shù)據(jù)包接收處理,接收軟中斷的處理函數(shù)為net_rx_action */ netif_rx_schedule(&queue->backlog_dev); //只有當(dāng)input_pkt_queue為空時(shí)才調(diào)用,非空時(shí)只是將數(shù)據(jù)報(bào)入隊(duì)列,因?yàn)槿绻?duì)列非空,則已經(jīng)調(diào)用了NET_RX_SOFTIRQ軟中斷,所以沒必要在執(zhí)行netif_rx_schedule去調(diào)用軟中斷了. goto enqueue; } /*如果隊(duì)列無空閑空間,設(shè)置擁塞位*/ if (queue->throttle == 0) { queue->throttle = 1; netdev_rx_stat[this_cpu].throttled++; #ifdef CONFIG_NET_HW_FLOWCONTROL atomic_inc(&netdev_dropping); #endif } drop: netdev_rx_stat[this_cpu].dropped++; local_irq_restore(flags); kfree_skb(skb); return NET_RX_DROP; }
看完netif_rx后有一個(gè)問題,當(dāng)隊(duì)列為空的時(shí)候會(huì)調(diào)用netif_rx_schedule,此函數(shù)將會(huì)把接收skb的device連接到poll_list鏈表,但如果隊(duì)列非空時(shí),為什么直接把數(shù)據(jù)放到input_pkt_queue里了? 想了想,應(yīng)該是因?yàn)檫@樣:因?yàn)椴捎肗API技術(shù)的網(wǎng)卡接收到數(shù)據(jù)報(bào)后不會(huì)調(diào)用netif_rx,而直接調(diào)用netif_rx_schedule,所以調(diào)用netif_rx的都是非NAPI的網(wǎng)卡,那么默認(rèn)的包處理函數(shù)都是process_backlog,也就是說,所有數(shù)據(jù)報(bào)的處理函數(shù)是一樣的,所以當(dāng)隊(duì)列非空時(shí),直接將接收到的skb放入接收隊(duì)列即可. 以上純粹是個(gè)人的理解,不知道對(duì)不對(duì),如果不對(duì),有知道的朋友希望告訴下,謝謝.
netif_rx返回后,由netif_rx_schedule調(diào)用軟中斷處理函數(shù),所以控制權(quán)轉(zhuǎn)交到net_rx_action.
/* *接收軟中斷NET_RX_SOFTIRQ的處理函數(shù) *當(dāng)調(diào)用poll_list中的device時(shí),如果網(wǎng)卡采用的是NAPI技術(shù),則調(diào)用網(wǎng)卡自定義的poll函數(shù),如果是非NAPI(目前大多數(shù)網(wǎng)卡都是采用此技術(shù)),則調(diào)用默認(rèn)的process_backlog函數(shù). *由于此筆記研究的是非NAPI,所以控制權(quán)轉(zhuǎn)交到process_backlog函數(shù). */ static void net_rx_action(struct softirq_action *h) { int this_cpu = smp_processor_id(); struct softnet_data *queue = &softnet_data[this_cpu]; //獲得與cpu相關(guān)的softnet_data,因?yàn)槊總€(gè)cpu只有一個(gè)softnet_data unsigned long start_time = jiffies; int budget = netdev_max_backlog; //默認(rèn)值為300,系統(tǒng)每次從隊(duì)列中最多取出300個(gè)skb處理 br_read_lock(BR_NETPROTO_LOCK); local_irq_disable(); while (!list_empty(&queue->poll_list)) { struct net_device *dev; //當(dāng)處理時(shí)間持續(xù)超過一個(gè)時(shí)鐘滴答時(shí),會(huì)再出發(fā)一個(gè)中斷NET_RX_SOFTIRQ if (budget = 0 || jiffies - start_time > 1) goto softnet_break; local_irq_enable(); //取得poll_list鏈表中的設(shè)備 dev = list_entry(queue->poll_list.next, struct net_device, poll_list); //調(diào)用設(shè)備的poll函數(shù),處理接收數(shù)據(jù)包,采用輪尋技術(shù)的網(wǎng)卡,將調(diào)用它真實(shí)的poll函數(shù) //而對(duì)于采用傳統(tǒng)中斷處理的設(shè)備,它們調(diào)用的都將是backlog_dev的process_backlog函數(shù) //如果一次poll未處理完全部數(shù)據(jù)報(bào),則將device移至鏈表尾部,等待下一次調(diào)用. if (dev->quota = 0 || dev->poll(dev, &budget)) { //由于要對(duì)softnet_data進(jìn)行操作,則必須禁止中斷. local_irq_disable(); //把device從表頭移除,移到鏈表尾 list_del(&dev->poll_list); list_add_tail(&dev->poll_list, &queue->poll_list); if (dev->quota 0) dev->quota += dev->weight; else dev->quota = dev->weight; } else { dev_put(dev); //當(dāng)device中的數(shù)據(jù)報(bào)被處理完畢(網(wǎng)卡采用NAPI技術(shù)時(shí)),遞減device的引用計(jì)數(shù) dcreases the reference count . local_irq_disable(); } } local_irq_enable(); br_read_unlock(BR_NETPROTO_LOCK); return; softnet_break: netdev_rx_stat[this_cpu].time_squeeze++; //如果此時(shí)又有中斷發(fā)生,出發(fā)NET_RX_SOFTIRQ中斷,再此調(diào)用net_rx_action函數(shù). __cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ); local_irq_enable(); br_read_unlock(BR_NETPROTO_LOCK); }
軟中斷處理函數(shù)的主體既是調(diào)用數(shù)據(jù)報(bào)的處理函數(shù),網(wǎng)卡采用NAPI技術(shù)的情況下,則調(diào)用device本身定義的poll函數(shù),如果是非NAPI,則調(diào)用的是默認(rèn)的process_backlog函數(shù),既然本筆記研究的是非NAPI的情況,那么下面來研究下process_backlog函數(shù).
/* *把數(shù)據(jù)報(bào)從input_pkt_queue中去出來后,交由netif_receive_skb處理,netif_receive_skb為真正的包處理函數(shù). *注意,無論是基于NAPI還是非NAPI,最后的包處理函數(shù)都是netif_receive_skb. */ static int process_backlog(struct net_device *blog_dev, int *budget) { int work = 0; //quota為一次處理數(shù)據(jù)包的數(shù)量,blog_dev->quota的值由netif_rx_schedule初始化為全局變量weight_p的值,默認(rèn)值為64 int quota = min(blog_dev->quota, *budget); int this_cpu = smp_processor_id(); struct softnet_data *queue = &softnet_data[this_cpu]; unsigned long start_time = jiffies; //循環(huán)取出skb,交由netif_receive_skb處理,直至隊(duì)列為空 for (;;) { struct sk_buff *skb; struct net_device *dev; local_irq_disable(); skb = __skb_dequeue(&queue->input_pkt_queue); if (skb == NULL) goto job_done; local_irq_enable(); dev = skb->dev; //注意此處,取出數(shù)據(jù)報(bào)后,直接交由netif_receive_skb處理. netif_receive_skb(skb); dev_put(dev); //dev引用計(jì)數(shù)-1 work++; if (work >= quota || jiffies - start_time > 1) break; #ifdef CONFIG_NET_HW_FLOWCONTROL if (queue->throttle && queue->input_pkt_queue.qlen no_cong_thresh ) { if (atomic_dec_and_test(&netdev_dropping)) { queue->throttle = 0; netdev_wakeup(); break; } } #endif } //更新quota blog_dev->quota -= work; *budget -= work; return -1; job_done: blog_dev->quota -= work; *budget -= work; list_del(&blog_dev->poll_list); clear_bit(__LINK_STATE_RX_SCHED, &blog_dev->state); if (queue->throttle) { queue->throttle = 0; #ifdef CONFIG_NET_HW_FLOWCONTROL if (atomic_dec_and_test(&netdev_dropping)) netdev_wakeup(); #endif } local_irq_enable(); return 0; } 注意,就像注釋中所說,無論NAPI還是非NAPI,最后的包處理函數(shù)都是netif_receive_skb.所以此函數(shù)比較重要.下面來分析下此函數(shù). /* netif_receive_skb作用: 對(duì)每一個(gè)接收到的skb,到已注冊(cè)的協(xié)議類型中去匹配,先是匹配ptype_all鏈表,ptype_all中注冊(cè)的struct packet_type表示要接收處理所有協(xié)議的數(shù)據(jù),對(duì)于 匹配到的struct packet_tpye結(jié)構(gòu)(dev為NULL或者dev等于skb的dev),調(diào)用其func成員,把skb傳遞給它處理. 匹配完ptype_all后,再匹配ptype_base數(shù)組中注冊(cè)的協(xié)議類型,skb有一個(gè)成員protocol,其值即為以太網(wǎng)首部中的幀類型,在ptype_base中匹配到協(xié)議相同,并且 dev符合要求的,調(diào)用其func成員即可. 此函數(shù)中數(shù)據(jù)報(bào)的流向:sniffer(如果有)->Diverter(分流器)->bridge(如果有)->l3協(xié)議的處理函數(shù)(比如ip協(xié)議的ip_rcv) */ int netif_receive_skb(struct sk_buff *skb) { //packet_type結(jié)構(gòu)體見下面 struct packet_type *ptype, *pt_prev; int ret = NET_RX_DROP; unsigned short type = skb->protocol; //如果數(shù)據(jù)包沒設(shè)置時(shí)間截,設(shè)置之 if (skb->stamp.tv_sec == 0) do_gettimeofday(&skb->stamp); skb_bond(skb); //使skb->dev指向主設(shè)備,多個(gè)interfaces可以在一起集中管理,這時(shí)候要有個(gè)頭頭管理這些接口,如果skb對(duì)應(yīng)的device來自這樣一個(gè)group, //則傳遞到L3層之前應(yīng)使skb->dev指向master netdev_rx_stat[smp_processor_id()].total++; #ifdef CONFIG_NET_FASTROUTE if (skb->pkt_type == PACKET_FASTROUTE) { netdev_rx_stat[smp_processor_id()].fastroute_deferred_out++; return dev_queue_xmit(skb); } #endif skb->h.raw = skb->nh.raw = skb->data; pt_prev = NULL; //ptype_all是雙向鏈表,ptpye_base是一個(gè)哈希表 //初始化時(shí)協(xié)議類型為ETH_P_ALL時(shí),將packet_type結(jié)構(gòu)加入到ptype_all列表中 //如果不是,模15后加到ptype_base數(shù)組中,此數(shù)組相當(dāng)于hash鏈表 //這里針對(duì)協(xié)議類型為ETH_P_ALL的情況進(jìn)行處理,對(duì)于ip協(xié)議來說 //類型定義為ETH_P_IP,因此不在這里處理 for (ptype = ptype_all; ptype; ptype = ptype->next) { //處理器處理所有的網(wǎng)絡(luò)設(shè)備接收的包或找到設(shè)備匹配的包處理器 if (!ptype->dev || ptype->dev == skb->dev) { if (pt_prev) { //老版本內(nèi)核時(shí) if (!pt_prev->data) { /* Deliver skb to an old protocol, which is not threaded well or which do not understand shared skbs. */ ret = deliver_to_old_ones(pt_prev, skb, 0); } else { //發(fā)給相應(yīng)的處理函數(shù),下面兩條相當(dāng)于deliver_skb //有協(xié)議對(duì)skb處理,所以u(píng)se加1 atomic_inc(&skb->users); //傳遞的是skb的指針,所以可以看出是原數(shù)據(jù)報(bào)本身.即如果在此修改skb,則會(huì)影響到后面的數(shù)據(jù)流向. ret = pt_prev->func(skb, skb->dev, pt_prev); } } pt_prev = ptype; } } #ifdef CONFIG_NET_DIVERT /*如果配置有DIVERT(分流器),則交由分流器處理*/ if (skb->dev->divert && skb->dev->divert->divert) ret = handle_diverter(skb); #endif /* CONFIG_NET_DIVERT */ /*如果配置有BRIDGE或者有BRIDGE模塊,則交由橋處理*/ #if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) if (skb->dev->br_port != NULL && br_handle_frame_hook != NULL) { return handle_bridge(skb, pt_prev); } #endif //這里針對(duì)各種協(xié)議進(jìn)行處理,eg:ip包的類型為ETH_P_IP,因此在這里處理 //&15的意思是模15 for (ptype=ptype_base[ntohs(type)&15];ptype;ptype=ptype->next) { if (ptype->type == type && (!ptype->dev || ptype->dev == skb->dev)) { if (pt_prev) { //pt_prev指向具體的協(xié)議類型 if (!pt_prev->data) { ret = deliver_to_old_ones(pt_prev, skb, 0); } else { atomic_inc(&skb->users); ret = pt_prev->func(skb, skb->dev, pt_prev); } } pt_prev = ptype; } } //1:當(dāng)上面的兩個(gè)數(shù)組只有一個(gè)元素時(shí) //2:訪問上面兩個(gè)數(shù)組的最后一個(gè)ptype_type,執(zhí)行下面的語句 if (pt_prev) { if (!pt_prev->data) { ret = deliver_to_old_ones(pt_prev, skb, 1); } else { //當(dāng)只有一個(gè)協(xié)議的時(shí)候,user不加1 ret = pt_prev->func(skb, skb->dev, pt_prev); } } else { ///////表示搜索完ptype_all和ptype_base后沒找到匹配的,free掉skb kfree_skb(skb); /* Jamal, now you will not able to escape explaining * me how you were going to use this. :-) */ ret = NET_RX_DROP; } return ret; } 其中packet_type的定義如下: struct packet_type { unsigned short type; //一般的dev設(shè)置為NULL,表示將接收從任何設(shè)備接收的數(shù)據(jù)包 struct net_device *dev; int (*func)(struct sk_buff*,strcut net_device*, struct packet_type *); //接收處理函數(shù) void *data;//private to the packet type struct packet_type *next; }; 注意,在網(wǎng)絡(luò)初始化代碼里有這么一條語句:struct packet_type * ptype_all = NULL.就是說ptype_all在初始化的時(shí)候?yàn)榭?用于指向Eth_P_ALL類型的packet_type結(jié)構(gòu),注冊(cè)在這里的函數(shù),可以接收到所有的數(shù)據(jù)報(bào).包括輸出的數(shù)據(jù)包,看代碼可知,由于傳遞的是一個(gè)指針,所以在此修改skb的一切操作將會(huì)影響后面的處理過程. 如果想要在數(shù)據(jù)包傳遞到網(wǎng)絡(luò)層之前對(duì)數(shù)據(jù)報(bào)進(jìn)行處理,則可以考慮的地點(diǎn)可以有如下幾個(gè): sniffer,diverter,bridge. 其中sniffer本人已經(jīng)親自實(shí)現(xiàn)過,即在ptype_all中注冊(cè)自己的函數(shù),可以接收到所有出去或者進(jìn)入的數(shù)據(jù)包. 最后,數(shù)據(jù)包會(huì)傳到l3層,如果是ip協(xié)議,則相應(yīng)的處理函數(shù)為ip_rcv,到此數(shù)據(jù)報(bào)從網(wǎng)卡到l3層的接收過程已經(jīng)完畢.即總的路線是:netif_rx-->net_rx_action-->process_backlog-->netif_receive_skb-->sniffer(如果有)-->diverter(如果有)-->bridge(如果有)-->ip_rcv(或者其他的l3層協(xié)議處理函數(shù)) ps1:弄了好幾天,終于算把這個(gè)數(shù)據(jù)包的接收過程系統(tǒng)的過了一遍,看過源代碼,感覺收獲不小,但是覺得知道的還不是很透徹,其中的設(shè)計(jì)理念等以后有機(jī)會(huì)一定要好好的研究研究.由于本次涉及到的東西相對(duì)來說比較多,而且涉及到內(nèi)核網(wǎng)絡(luò)部分,本人很菜,難免有理解錯(cuò)誤的地方,如果有錯(cuò)誤,請(qǐng)看到的朋友能夠及時(shí)給予指正,不勝感激. ps2:從在網(wǎng)上查找資料,看相關(guān)書籍到通讀這一過程的源代碼,本人花費(fèi)了很多時(shí)間,因此對(duì)本篇筆記很是喜愛和重視,如果有需要轉(zhuǎn)載的朋友,請(qǐng)注明來自hmily8023.cublog.cn,謝謝.
|
|