大家好,我是皮哥!今天和大家深度理解一下在Linux下網(wǎng)絡(luò)包的接收過程,為了簡單起見,我們用udp來舉例,如下:
int?main(){
????int?serverSocketFd?=?socket(AF_INET,?SOCK_DGRAM,?0);
????bind(serverSocketFd,?...);
????char?buff[BUFFSIZE];
????int?readCount?=?recvfrom(serverSocketFd,?buff,?BUFFSIZE,?0,?...);
????buff[readCount]?=?'';
????printf("Receive?from?client:%sn",?buff);
}
上面代碼是一段udp server接收收據(jù)的邏輯。只要客戶端有對應(yīng)的數(shù)據(jù)發(fā)送過來,服務(wù)器端執(zhí)行recv_from后就能收到它,并把它打印出來。那么當(dāng)網(wǎng)絡(luò)包達到網(wǎng)卡,直到recvfrom收到數(shù)據(jù),這中間究竟都發(fā)生過什么?
Linux 網(wǎng)絡(luò)架構(gòu)
在Linux內(nèi)核實現(xiàn)中,鏈路層協(xié)議靠網(wǎng)卡驅(qū)動來實現(xiàn),內(nèi)核協(xié)議棧來實現(xiàn)網(wǎng)絡(luò)層和傳輸層。內(nèi)核對更上層的應(yīng)用層提供socket接口來供用戶進程訪問。我們用Linux的視角來看到的TCP/IP網(wǎng)絡(luò)分層模型應(yīng)該是下面這個樣子的。
Linux 網(wǎng)絡(luò)初始化
網(wǎng)絡(luò)設(shè)備子系統(tǒng)初始化
linux內(nèi)核通過調(diào)用subsys_initcall來初始化各個子系統(tǒng),其中網(wǎng)絡(luò)子系統(tǒng)的初始化會執(zhí)行到net_dev_init函數(shù):
//net/core/dev.c
static?int?__init?net_dev_init(void){
????......
????for_each_possible_cpu(i)?{
????????struct?softnet_data?*sd?=?&per_cpu(softnet_data,?i);
????????memset(sd,?0,?sizeof(*sd));
????????skb_queue_head_init(&sd->input_pkt_queue);
????????skb_queue_head_init(&sd->process_queue);
????????sd->completion_queue?=?NULL;
????????INIT_LIST_HEAD(&sd->poll_list);
????????......
????}
????......
????open_softirq(NET_TX_SOFTIRQ,?net_tx_action);
????open_softirq(NET_RX_SOFTIRQ,?net_rx_action);
}
subsys_initcall(net_dev_init);
首先為每個CPU都申請一個softnet_data數(shù)據(jù)結(jié)構(gòu),在這個數(shù)據(jù)結(jié)構(gòu)里的poll_list是等待驅(qū)動程序將其poll函數(shù)注冊進來,稍后網(wǎng)卡驅(qū)動初始化的時候我們可以看到這一過程。
然后 open_softirq 為每一種軟中斷都注冊一個處理函數(shù)。NET_TX_SOFTIRQ的處理函數(shù)為net_tx_action,NET_RX_SOFTIRQ的為net_rx_action。
//kernel/softirq.c
void?open_softirq(int?nr,?void?(*action)(struct?softirq_action?*)){
????softirq_vec[nr].action?=?action;
}
open_softirq 會把不同的 action 記錄在softirq_vec變量里的。后面ksoftirqd線程收到軟中斷的時候,也會使用這個變量來找到每一種軟中斷對應(yīng)的處理函數(shù)。
網(wǎng)卡驅(qū)動初始化
這里以 FSL 系列網(wǎng)卡為例,其驅(qū)動位于:drivers/net/ethernet/freescale/fec_main.c
static?struct?platform_driver?fec_driver?=?{
?.driver?=?{
??.name?=?DRIVER_NAME,
??.pm?=?&fec_pm_ops,
??.of_match_table?=?fec_dt_ids,
??.suppress_bind_attrs?=?true,
?},
?.id_table?=?fec_devtype,
?.probe?=?fec_probe,
?.remove?=?fec_drv_remove,
};
static?int
fec_probe(struct?platform_device?*pdev)
{
??fec_enet_clk_enable
??fec_reset_phy??????//使用gpio?復(fù)位phy?芯片
??fec_enet_init??????//設(shè)置netdev_ops、設(shè)置ethtool_ops
??for?(i?=?0;?i?<?irq_cnt;?i++)?{
????devm_request_irq(...,?irq,?fec_enet_interrupt,?...);
??}
??fec_enet_mii_init??//讀取dts?mdio節(jié)點下phy子節(jié)點,并注冊phy_device
??register_netdev????//注冊網(wǎng)絡(luò)設(shè)備
}
Linux 以太網(wǎng)驅(qū)動會向上層提供 net_device_ops ,方便應(yīng)用層控制網(wǎng)卡,比如網(wǎng)卡被啟動(例如,通過 ifconfig eth0 up)的時候會被調(diào)用 fec_enet_open,此外它還包含著網(wǎng)卡發(fā)包、設(shè)置 mac 地址等回調(diào)函數(shù)。
static?const?struct?net_device_ops?fec_netdev_ops?=?{
?.ndo_open??=?fec_enet_open,
?.ndo_stop??=?fec_enet_close,
?.ndo_start_xmit??=?fec_enet_start_xmit,
?.ndo_select_queue???????=?fec_enet_select_queue,
?.ndo_set_rx_mode?=?set_multicast_list,
?.ndo_validate_addr?=?eth_validate_addr,
?.ndo_tx_timeout??=?fec_timeout,
?.ndo_set_mac_address?=?fec_set_mac_address,
?.ndo_eth_ioctl??=?fec_enet_ioctl,
#ifdef?CONFIG_NET_POLL_CONTROLLER
?.ndo_poll_controller?=?fec_poll_controller,
#endif
?.ndo_set_features?=?fec_set_features,
?.ndo_bpf??=?fec_enet_bpf,
?.ndo_xdp_xmit??=?fec_enet_xdp_xmit,
};
此外,網(wǎng)卡驅(qū)動實現(xiàn)了 ethtool 所需要的接口,當(dāng) ethtool 發(fā)起一個系統(tǒng)調(diào)用之后,內(nèi)核會找到對應(yīng)操作的回調(diào)函數(shù)??梢钥吹?ethtool 這個命令之所以能查看網(wǎng)卡收發(fā)包統(tǒng)計、能修改網(wǎng)卡自適應(yīng)模式、能調(diào)整RX 隊列的數(shù)量和大小,是因為 ethtool 命令最終調(diào)用到了網(wǎng)卡驅(qū)動的相應(yīng)方法。
static?const?struct?ethtool_ops?fec_enet_ethtool_ops?=?{
?.supported_coalesce_params?=?ETHTOOL_COALESCE_USECS?|
?????????ETHTOOL_COALESCE_MAX_FRAMES,
?.get_drvinfo??=?fec_enet_get_drvinfo,
?.get_regs_len??=?fec_enet_get_regs_len,
?.get_regs??=?fec_enet_get_regs,
?.nway_reset??=?phy_ethtool_nway_reset,
?.get_link??=?ethtool_op_get_link,
?.get_coalesce??=?fec_enet_get_coalesce,
?.set_coalesce??=?fec_enet_set_coalesce,
#ifndef?CONFIG_M5272
?.get_pauseparam??=?fec_enet_get_pauseparam,
?.set_pauseparam??=?fec_enet_set_pauseparam,
?.get_strings??=?fec_enet_get_strings,
?.get_ethtool_stats?=?fec_enet_get_ethtool_stats,
?.get_sset_count??=?fec_enet_get_sset_count,
#endif
?.get_ts_info??=?fec_enet_get_ts_info,
?.get_tunable??=?fec_enet_get_tunable,
?.set_tunable??=?fec_enet_set_tunable,
?.get_wol??=?fec_enet_get_wol,
?.set_wol??=?fec_enet_set_wol,
?.get_eee??=?fec_enet_get_eee,
?.set_eee??=?fec_enet_set_eee,
?.get_link_ksettings?=?phy_ethtool_get_link_ksettings,
?.set_link_ksettings?=?phy_ethtool_set_link_ksettings,
?.self_test??=?net_selftest,
};
協(xié)議棧初始化
內(nèi)核實現(xiàn)了網(wǎng)絡(luò)層的 ip 協(xié)議,也實現(xiàn)了傳輸層的 tcp 協(xié)議和 udp 協(xié)議。這些協(xié)議對應(yīng)的實現(xiàn)函數(shù)分別是 ip_rcv(),tcp_v4_rcv()和udp_rcv()。
網(wǎng)絡(luò)協(xié)議棧是通過函數(shù) inet_init() 注冊的,通過inet_init,將這些函數(shù)注冊到了inet_protos和ptype_base數(shù)據(jù)結(jié)構(gòu)中了。如下圖:
相關(guān)代碼如下:
//net/ipv4/af_inet.c
static?struct?packet_type?ip_packet_type?__read_mostly?=?{
?.type?=?cpu_to_be16(ETH_P_IP),
?.func?=?ip_rcv,
?.list_func?=?ip_list_rcv,
};
static?const?struct?net_protocol?tcp_protocol?=?{
?.handler?=?tcp_v4_rcv,
?.err_handler?=?tcp_v4_err,
?.no_policy?=?1,
?.icmp_strict_tag_validation?=?1,
};
static?const?struct?net_protocol?udp_protocol?=?{
?.handler?=?udp_rcv,
?.err_handler?=?udp_err,
?.no_policy?=?1,
};
static?int?__init?inet_init(void){
????......
????if?(inet_add_protocol(&icmp_protocol,?IPPROTO_ICMP)?<?0)
????????pr_crit("%s:?Cannot?add?ICMP?protocoln",?__func__);
????if?(inet_add_protocol(&udp_protocol,?IPPROTO_UDP)?<?0)??//注冊?udp_rcv()
????????pr_crit("%s:?Cannot?add?UDP?protocoln",?__func__);
????if?(inet_add_protocol(&tcp_protocol,?IPPROTO_TCP)?<?0)??//注冊?tcp_v4_rcv()
????????pr_crit("%s:?Cannot?add?TCP?protocoln",?__func__);
????......
????dev_add_pack(&ip_packet_type);??/注冊?ip_rcv()
}
上面的代碼中我們可以看到,udp_protocol結(jié)構(gòu)體中的handler是udp_rcv,tcp_protocol結(jié)構(gòu)體中的handler是tcp_v4_rcv,通過inet_add_protocol被初始化了進來。
int?inet_add_protocol(const?struct?net_protocol?*prot,?unsigned?char?protocol){
????if?(!prot->netns_ok)?{
????????pr_err("Protocol?%u?is?not?namespace?aware,?cannot?register.n",
????????????protocol);
????????return?-EINVAL;
????}
????return?!cmpxchg((const?struct?net_protocol?**)&inet_protos[protocol],
????????????NULL,?prot)???0?:?-1;
}
inet_add_protocol函數(shù)將tcp和udp對應(yīng)的處理函數(shù)都注冊到了inet_protos數(shù)組中了。
再看dev_add_pack(&ip_packet_type);這一行,ip_packet_type結(jié)構(gòu)體中的type是協(xié)議名,func是ip_rcv函數(shù),在dev_add_pack中會被注冊到ptype_base哈希表中。
//net/core/dev.c
void?dev_add_pack(struct?packet_type?*pt){
????struct?list_head?*head?=?ptype_head(pt);
????......
}
static?inline?struct?list_head?*ptype_head(const?struct?packet_type?*pt){
????if?(pt->type?==?htons(ETH_P_ALL))
????????return?&ptype_all;
????else
????????return?&ptype_base[ntohs(pt->type)?&?PTYPE_HASH_MASK];
}
這里我們需要記住inet_protos記錄著udp,tcp的處理函數(shù)地址,ptype_base存儲著ip_rcv()函數(shù)的處理地址。后面我們會看到軟中斷中會通過ptype_base找到ip_rcv函數(shù)地址,進而將ip包正確地送到ip_rcv()中執(zhí)行。在ip_rcv中將會通過inet_protos找到tcp或者udp的處理函數(shù),再而把包轉(zhuǎn)發(fā)給udp_rcv()或tcp_v4_rcv()函數(shù)。
數(shù)據(jù)包的接收過程
硬中斷處理
首先當(dāng)數(shù)據(jù)幀從網(wǎng)線到達網(wǎng)卡,網(wǎng)卡在分配給自己的 ringBuffer 中尋找可用的內(nèi)存位置,找到后 DMA 會把數(shù)據(jù)拷貝到網(wǎng)卡之前關(guān)聯(lián)的內(nèi)存里。當(dāng) DMA 操作完成以后,網(wǎng)卡會向 CPU 發(fā)起一個硬中斷,通知 CPU 有數(shù)據(jù)到達。
中斷處理函數(shù)為:
//drivers/net/ethernet/freescale/fec_main.c
static?irqreturn_t
fec_enet_interrupt(int?irq,?void?*dev_id)
{
?struct?net_device?*ndev?=?dev_id;
?struct?fec_enet_private?*fep?=?netdev_priv(ndev);
?irqreturn_t?ret?=?IRQ_NONE;
?if?(fec_enet_collect_events(fep)?&&?fep->link)?{
??ret?=?IRQ_HANDLED;
??if?(napi_schedule_prep(&fep->napi))?{
???/*?Disable?interrupts?*/
???writel(0,?fep->hwp?+?FEC_IMASK);
???__napi_schedule(&fep->napi);
??}
?}
?return?ret;
}
//net/core/dev.c
__napi_schedule->____napi_schedule
static?inline?void?____napi_schedule(struct?softnet_data?*sd,
?????????????????????struct?napi_struct?*napi){
????list_add_tail(&napi->poll_list,?&sd->poll_list);
????__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}
這里我們看到,list_add_tail修改了CPU變量softnet_data里的poll_list,將驅(qū)動napi_struct傳過來的poll_list添加了進來。其中softnet_data中的poll_list是一個雙向列表,其中的設(shè)備都帶有輸入幀等著被處理。緊接著 __raise_softirq_irqoff 觸發(fā)了一個軟中斷 NET_RX_SOFTIRQ。
注意:當(dāng)RingBuffer滿的時候,新來的數(shù)據(jù)包將給丟棄。ifconfig查看網(wǎng)卡的時候,可以里面有個overruns,表示因為環(huán)形隊列滿被丟棄的包。如果發(fā)現(xiàn)有丟包,可能需要通過ethtool命令來加大環(huán)形隊列的長度。
ksoftirqd 軟中斷處理
接下來進入軟中斷處理函數(shù):
//kernel/softirq.c
static?void?run_ksoftirqd(unsigned?int?cpu){
????local_irq_disable();
????if?(local_softirq_pending())?{
????????__do_softirq();
????????rcu_note_context_switch(cpu);
????????local_irq_enable();
????????cond_resched();
????????return;
????}
????local_irq_enable();
}
asmlinkage?__visible?void?__softirq_entry?__do_softirq(void)
{
??while?((softirq_bit?=?ffs(pending)))?{
????h->action(h);
??}
}
在網(wǎng)絡(luò)設(shè)備子系統(tǒng)初始化中,講到為 NET_RX_SOFTIRQ 注冊了處理函數(shù) net_rx_action。所以 net_rx_action 函數(shù)就會被執(zhí)行到了。
//net/core/dev.c
static?__latent_entropy?void?net_rx_action(struct?softirq_action?*h)
{
??struct?softnet_data?*sd?=?this_cpu_ptr(&softnet_data);
??
??list_splice_init(&sd->poll_list,?&list);
??
??for?(;;)?{
????...
????n?=?list_first_entry(&list,?struct?napi_struct,?poll_list);
????budget?-=?napi_poll(n,?&repoll);
????...
??}
??...
}
napi_poll->__napi_poll->work?=?n->poll(n,?weight)
首先獲取到當(dāng)前CPU變量softnet_data,對其poll_list進行遍歷, 然后執(zhí)行到網(wǎng)卡驅(qū)動注冊到的 poll 函數(shù)。對于 FSL 網(wǎng)卡來說,其驅(qū)動對應(yīng)的 poll 函數(shù)就是 fec_enet_rx_napi。
//drivers/net/ethernet/freescale/fec_main.c
static?int?fec_enet_rx_napi(struct?napi_struct?*napi,?int?budget)
{
?struct?net_device?*ndev?=?napi->dev;
?struct?fec_enet_private?*fep?=?netdev_priv(ndev);
?int?done?=?0;
?do?{
??done?+=?fec_enet_rx(ndev,?budget?-?done);
??fec_enet_tx(ndev);
?}?while?((done?<?budget)?&&?fec_enet_collect_events(fep));
?if?(done?<?budget)?{
??napi_complete_done(napi,?done);
??writel(FEC_DEFAULT_IMASK,?fep->hwp?+?FEC_IMASK);
?}
?return?done;
}
fec_enet_rx->fec_enet_rx_queue
然后進入 GRO 處理,流程如下:
napi_gro_receive->napi_skb_finish->gro_normal_one->gro_normal_list->netif_receive_skb_list_internal
最終通過函數(shù) netif_receive_skb_list_internal() 進入內(nèi)核協(xié)議棧。
協(xié)議棧處理
static?int?__netif_receive_skb_core(struct?sk_buff?**pskb,?bool?pfmemalloc,
????????struct?packet_type?**ppt_prev)
{
??......
??//抓包
??list_for_each_entry_rcu(ptype,?&ptype_all,?list)?{
??????if?(pt_prev)
??????????ret?=?deliver_skb(skb,?pt_prev,?orig_dev);
??????pt_prev?=?ptype;
??}
??list_for_each_entry_rcu(ptype,?&skb->dev->ptype_all,?list)?{
??????if?(pt_prev)
??????????ret?=?deliver_skb(skb,?pt_prev,?orig_dev);
??????pt_prev?=?ptype;
??}
??......
??if?(likely(!deliver_exact))?{
??????deliver_ptype_list_skb(skb,?&pt_prev,?orig_dev,?type,
?????????????????????&ptype_base[ntohs(type)?&
?????????????????????????PTYPE_HASH_MASK]);
??}
??......
}
static?inline?void?deliver_ptype_list_skb(struct?sk_buff?*skb,
???????struct?packet_type?**pt,
???????struct?net_device?*orig_dev,
???????__be16?type,
???????struct?list_head?*ptype_list)
{
?struct?packet_type?*ptype,?*pt_prev?=?*pt;
?list_for_each_entry_rcu(ptype,?ptype_list,?list)?{
??if?(ptype->type?!=?type)
???continue;
??if?(pt_prev)
???deliver_skb(skb,?pt_prev,?orig_dev);
??pt_prev?=?ptype;
?}
?*pt?=?pt_prev;
}
函數(shù) deliver_ptype_list_skb 會從數(shù)據(jù)包中取出協(xié)議信息,然后遍歷注冊在這個協(xié)議上的回調(diào)函數(shù)列表。ptype_base 是一個 hash table,在協(xié)議初始化小節(jié)我們提到過,ip_rcv 函數(shù)地址就是存在這個 hash table中的。
//net/core/dev.c
static?inline?int?deliver_skb(struct?sk_buff?*skb,
??????????????????struct?packet_type?*pt_prev,
??????????????????struct?net_device?*orig_dev)
{
????......
????return?pt_prev->func(skb,?skb->dev,?pt_prev,?orig_dev);
}
pt_prev->func 這一行就調(diào)用到了協(xié)議層注冊的處理函數(shù)了。對于 ip 包來講,就會進入到 ip_rcv(如果是 arp 包的話,會進入到 arp_rcv)。
- IP協(xié)議層處理
//net/ipv4/ip_input.c
int?ip_rcv(struct?sk_buff?*skb,?struct?net_device?*dev,?struct?packet_type?*pt,
????struct?net_device?*orig_dev)
{
?struct?net?*net?=?dev_net(dev);
?skb?=?ip_rcv_core(skb,?net);
?if?(skb?==?NULL)
??return?NET_RX_DROP;
?return?NF_HOOK(NFPROTO_IPV4,?NF_INET_PRE_ROUTING,
?????????net,?NULL,?skb,?dev,?NULL,
?????????ip_rcv_finish);
}
這里NF_HOOK是一個鉤子函數(shù),當(dāng)執(zhí)行完注冊的鉤子后就會執(zhí)行到最后一個參數(shù)指向的函數(shù) ip_rcv_finish。
ip_rcv_finish->dst_input->ip_local_deliver->ip_local_deliver_finish
static?int?ip_local_deliver_finish(struct?net?*net,?struct?sock?*sk,?struct?sk_buff?*skb)
{
?skb_clear_delivery_time(skb);
?__skb_pull(skb,?skb_network_header_len(skb));
?rcu_read_lock();
?ip_protocol_deliver_rcu(net,?skb,?ip_hdr(skb)->protocol);
?rcu_read_unlock();
?return?0;
}
void?ip_protocol_deliver_rcu(struct?net?*net,?struct?sk_buff?*skb,?int?protocol)
{
??......
??ret?=?INDIRECT_CALL_2(ipprot->handler,?tcp_v4_rcv,?udp_rcv,
??????????skb);
??......
}
在這里 skb 包將會進一步被派送到更上層的協(xié)議中,udp 和 tcp。
-
- UDP協(xié)議層處理
- udp協(xié)議的處理函數(shù)是 udp_rcv。
//net/ipv4/udp.c
int?udp_rcv(struct?sk_buff?*skb)
{
?return?__udp4_lib_rcv(skb,?&udp_table,?IPPROTO_UDP);
}
應(yīng)用層處理
通過開頭的應(yīng)用程序,我們知道應(yīng)用層的數(shù)據(jù)接收函數(shù)是 recvfrom,recvfrom 是一個glibc的庫函數(shù),該函數(shù)在執(zhí)行后會將用戶進行陷入到內(nèi)核態(tài),進入到Linux實現(xiàn)的系統(tǒng)調(diào)用 sys_recvfrom。
在理解 sys_revvfrom之前,我們先來簡單看一下socket這個核心數(shù)據(jù)結(jié)構(gòu)。
socket數(shù)據(jù)結(jié)構(gòu)中的const struct proto_ops對應(yīng)的是協(xié)議的方法集合。每個協(xié)議都會實現(xiàn)不同的方法集,對于IPv4 Internet協(xié)議族來說,每種協(xié)議都有對應(yīng)的處理方法,如下。對于udp來說,是通過inet_dgram_ops來定義的,其中注冊了inet_recvmsg方法。
//net/ipv4/af_inet.c
const?struct?proto_ops?inet_stream_ops?=?{
????......
????.recvmsg???????=?inet_recvmsg,
????.mmap??????????=?sock_no_mmap,
????......
}
const?struct?proto_ops?inet_dgram_ops?=?{
????......
????.sendmsg???????=?inet_sendmsg,
????.recvmsg???????=?inet_recvmsg,
????......
}
socket數(shù)據(jù)結(jié)構(gòu)中的另一個數(shù)據(jù)結(jié)構(gòu)struct sock *sk是一個非常大,非常重要的子結(jié)構(gòu)體。其中的sk_prot又定義了二級處理函數(shù)。對于UDP協(xié)議來說,會被設(shè)置成UDP協(xié)議實現(xiàn)的方法集udp_prot。
//net/ipv4/udp.c
struct?proto?udp_prot?=?{
????.name??????????=?"UDP",
????.owner?????????=?THIS_MODULE,
????.close?????????=?udp_lib_close,
????.connect???????=?ip4_datagram_connect,
????......
????.sendmsg???????=?udp_sendmsg,
????.recvmsg???????=?udp_recvmsg,
????.sendpage??????=?udp_sendpage,
????......
}
看完了 socket 變量之后,我們再來看 sys_revvfrom 的實現(xiàn)過程。
總結(jié)
首先在開始收包之前,Linux要做許多的準備工作:
- 創(chuàng)建ksoftirqd線程,為它設(shè)置好它自己的線程函數(shù),后面指望著它來處理軟中斷呢協(xié)議棧注冊,linux要實現(xiàn)許多協(xié)議,比如arp,icmp,ip,udp,tcp,每一個協(xié)議都會將自己的處理函數(shù)注冊一下,方便包來了迅速找到對應(yīng)的處理函數(shù)網(wǎng)卡驅(qū)動初始化,每個驅(qū)動都有一個初始化函數(shù),內(nèi)核會讓驅(qū)動也初始化一下。在這個初始化過程中,把自己的DMA準備好,把NAPI的poll函數(shù)地址告訴內(nèi)核啟動網(wǎng)卡,分配RX,TX隊列,注冊中斷對應(yīng)的處理函數(shù)
當(dāng)上面都ready之后,就可以打開硬中斷,等待數(shù)據(jù)包的到來了:
- 網(wǎng)卡將數(shù)據(jù)幀DMA到內(nèi)存的RingBuffer中,然后向CPU發(fā)起中斷通知CPU響應(yīng)中斷請求,調(diào)用網(wǎng)卡啟動時注冊的中斷處理函數(shù)中斷處理函數(shù)幾乎沒干啥,就發(fā)起了軟中斷請求內(nèi)核線程ksoftirqd線程發(fā)現(xiàn)有軟中斷請求到來,先關(guān)閉硬中斷ksoftirqd線程開始調(diào)用驅(qū)動的poll函數(shù)收包poll函數(shù)將收到的包送到協(xié)議棧注冊的ip_rcv函數(shù)中ip_rcv函數(shù)再講包送到udp_rcv函數(shù)中(對于tcp包就送到tcp_rcv)