2.6.24.4內(nèi)核網(wǎng)絡(luò)接收數(shù)據(jù)包分析
瀚海書(shū)香
在2.6.24.4中所有的網(wǎng)卡,不管是否支持napi,都是通過(guò)struct napi_struct結(jié)構(gòu)進(jìn)行。所有我們先說(shuō)一下這個(gè)結(jié)構(gòu)。
struct napi_struct{
struct list_head poll_list;
unsigned long state;
int weight;
int (*poll)(struct napi_struct *,int);
}
對(duì)應(yīng)支持napi的網(wǎng)卡,自己填充這個(gè)結(jié)構(gòu)體;而非napi網(wǎng)卡,則使用per cpu的softnet_data>backlog,這個(gè)結(jié)構(gòu)的初始化在net_dev_init()中完成。
我們先說(shuō)一下非napi機(jī)制的網(wǎng)卡:
網(wǎng)卡接收到數(shù)據(jù)包后dma到內(nèi)核空間,然后調(diào)用netif_rx()將數(shù)據(jù)包掛接到softnet_data>input_pkt_queue中,
如果backlog這個(gè)napi_struct沒(méi)有被調(diào)度,則napi_schedule(&backlog).napi_schedule()
會(huì)將backlog的poll_list掛接到softnet_data->poll_list上,同時(shí)出發(fā)軟中斷NET_RX_SOFTIRQ。
NET_RX_SOFTIRQ軟中斷,調(diào)用相應(yīng)的函數(shù)net_rx_action()。
對(duì)應(yīng)napi機(jī)制的網(wǎng)卡:
網(wǎng)卡初始化是會(huì)自己初始化一個(gè)自己的數(shù)據(jù)包接收隊(duì)列,當(dāng)有數(shù)據(jù)包到達(dá)時(shí),將數(shù)據(jù)包dma到自己的數(shù)據(jù)包隊(duì)列中,如果自己的napi沒(méi)有調(diào)度,則
napi_schedule(mynapi),這里的mynapi是網(wǎng)卡自己的napi_struct.napi_schedule()會(huì)將網(wǎng)卡自己的
poll_list掛接到softnet_data->poll_list上,同時(shí)出發(fā)軟中斷NET_RX_SOFTIRQ。
NET_RX_SOFTIRQ軟中斷,調(diào)用相應(yīng)的函數(shù)net_rx_action()。
net_rx_action():
首先獲取softnet_data->poll_list,通過(guò)遍歷poll_list,獲取每個(gè)poll_list對(duì)應(yīng)的napi_struct
結(jié)構(gòu)(container_of實(shí)現(xiàn)),然后根據(jù)napi_struct的weight調(diào)用poll函數(shù),如果是非napi網(wǎng)卡,這里的
napi_struct是backlog,所以poll函數(shù)就是process_backlog;如果是napi的網(wǎng)卡,則會(huì)使自己的poll函數(shù)。
napi網(wǎng)卡的poll函數(shù)就是從自己數(shù)據(jù)包隊(duì)列中dequeue出一個(gè)skb,然后調(diào)用netif_receive_skb().
非napi的process_backlog會(huì)獲取softnet_data->input_pkt_queue,然后對(duì)隊(duì)列input_pkt_queue進(jìn)行dequeue操作,獲得一個(gè)skb,之后調(diào)用netif_receive_skb(skb)。
netif_receive_skb():
對(duì)skb做一些準(zhǔn)備工作,例如設(shè)置mac_len等,調(diào)用deliver_skb()給所有的注冊(cè)ptype_all類(lèi)型的協(xié)議處理handle,然后是
網(wǎng)橋和VLAN的處理,之后會(huì)給注冊(cè)的相應(yīng)協(xié)議的ptype_base的handle。這里假設(shè)是ip協(xié)議,則會(huì)調(diào)用相應(yīng)的ip協(xié)議handle的處理函
數(shù)ip_rcv。
ip_rcv():
對(duì)skb做一些檢查工作,如果skb->users!=1,則clone一個(gè)skb,之后會(huì)轉(zhuǎn)入netfilter的
NF_IP_PRE_ROUTING的hook點(diǎn),調(diào)用所有在該點(diǎn)注冊(cè)的hook函數(shù)。比如說(shuō)如果開(kāi)啟了conntrack,則會(huì)在這里進(jìn)行數(shù)據(jù)包重組。
之后調(diào)用ip_rcv_finish().
ip_rcv_finish():
首先調(diào)用ip_route_input()決定數(shù)據(jù)包的路由,初始化skb->dst,調(diào)用dst_input(skb).
dst_input():
實(shí)際上是調(diào)用skb->dst->input(skb),對(duì)應(yīng)input的初始化在route.c中。如果是發(fā)往本地的數(shù)據(jù)包
dst->input=ip_local_deliver;如果是轉(zhuǎn)發(fā)的數(shù)據(jù)包dst->input=ip_forward;
本地流程:
ip_local_deliver():
首先是對(duì)分片的數(shù)據(jù)包重組,會(huì)轉(zhuǎn)入netfilter的NF_IP_LOCAL_IN的hook點(diǎn),調(diào)用所有在該點(diǎn)注冊(cè)的hook函數(shù)。之后會(huì)調(diào)用ip_local_deliver_finish(),之后就到第四層了。
轉(zhuǎn)發(fā)流程:
ip_forward():
做一些源路由等方面的檢查后,會(huì)轉(zhuǎn)入netfilter的NF_IP_FORWARD的hook點(diǎn),調(diào)用所有在該點(diǎn)注冊(cè)的hook函數(shù)。之后會(huì)調(diào)用ip_forward_finish().
ip_forward_finish():
調(diào)用dst_output().
dst_output():
skb->dst->output(skb).一般output=ip_output.
ip_output():
設(shè)置skb的dev為發(fā)包的dev,同時(shí)設(shè)置skb->protocol,會(huì)轉(zhuǎn)入netfilter的NF_IP_POST_ROUTING的hook點(diǎn),調(diào)用所有在該點(diǎn)注冊(cè)的hook函數(shù)。之后會(huì)調(diào)用ip_finish_output().
ip_finish_output():
檢查一下數(shù)據(jù)包是否需要分片,如果需要分片,則進(jìn)行ip_fragement(),之后調(diào)用ip_finish_output2().
ip_finish_output2():
根據(jù)neighbour,調(diào)用dst->neighbour->output.
到這為止,數(shù)據(jù)包會(huì)經(jīng)過(guò)dev_queue_xmit放入dev的qdisc中。之后就是流控出隊(duì)列。
出處:http://pengliang.cublog.cn