图解Linux网络包接收过程( 六 )

函数开头的time_limit和budget是用来控制net_rx_action函数主动退出的,目的是保证网络包的接收不霸占CPU不放 。等下次网卡再有硬中断过来的时候再处理剩下的接收数据包 。其中budget可以通过内核参数调整 。这个函数中剩下的核心逻辑是获取到当前CPU变量softnet_data,对其poll_list进行遍历, 然后执行到网卡驱动注册到的poll函数 。对于igb网卡来说,就是igb驱动力的igb_poll函数了 。
static int igb_poll(struct napi_struct *napi, int budget){    ...    if (q_vector->tx.ring)        clean_complete = igb_clean_tx_irq(q_vector);    if (q_vector->rx.ring)        clean_complete &= igb_clean_rx_irq(q_vector, budget);    ...}在读取操作中,igb_poll的重点工作是对igb_clean_rx_irq的调用 。
static bool igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget){    ...    do {        /* retrieve a buffer from the ring */        skb = igb_fetch_rx_buffer(rx_ring, rx_desc, skb);        /* fetch next buffer in frame if non-eop */        if (igb_is_non_eop(rx_ring, rx_desc))            continue;        }        /* verify the packet layout is correct */        if (igb_cleanup_headers(rx_ring, rx_desc, skb)) {            skb = NULL;            continue;        }        /* populate checksum, timestamp, VLAN, and protocol */        igb_process_skb_fields(rx_ring, rx_desc, skb);        napi_gro_receive(&q_vector->napi, skb);}igb_fetch_rx_buffer和igb_is_non_eop的作用就是把数据帧从RingBuffer上取下来 。为什么需要两个函数呢?因为有可能帧要占多多个RingBuffer,所以是在一个循环中获取的,直到帧尾部 。获取下来的一个数据帧用一个sk_buff来表示 。收取完数据以后,对其进行一些校验,然后开始设置sbk变量的timestamp, VLAN id, protocol等字段 。接下来进入到napi_gro_receive中:
//file: net/core/dev.cgro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb){    skb_gro_reset_offset(skb);    return napi_skb_finish(dev_gro_receive(napi, skb), skb);}dev_gro_receive这个函数代表的是网卡GRO特性,可以简单理解成把相关的小包合并成一个大包就行,目的是减少传送给网络栈的包数,这有助于减少 CPU 的使用量 。我们暂且忽略,直接看napi_skb_finish, 这个函数主要就是调用了netif_receive_skb 。
//file: net/core/dev.cstatic gro_result_t napi_skb_finish(gro_result_t ret, struct sk_buff *skb){    switch (ret) {    case GRO_NORMAL:        if (netif_receive_skb(skb))            ret = GRO_DROP;        break;    ......}在netif_receive_skb中,数据包将被送到协议栈中 。声明,以下的3.3, 3.4, 3.5也都属于软中断的处理过程,只不过由于篇幅太长,单独拿出来成小节 。
3.3 网络协议栈处理netif_receive_skb函数会根据包的协议,假如是udp包,会将包依次送到ip_rcv(),udp_rcv()协议处理函数中进行处理 。

图解Linux网络包接收过程

文章插图
 
图10 网络协议栈处理
//file: net/core/dev.cint netif_receive_skb(struct sk_buff *skb){    //RPS处理逻辑,先忽略    ......    return __netif_receive_skb(skb);}static int __netif_receive_skb(struct sk_buff *skb){    ......      ret = __netif_receive_skb_core(skb, false);}static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc){    ......    //pcap逻辑,这里会将数据送入抓包点 。tcpdump就是从这个入口获取包的    list_for_each_entry_rcu(ptype, &ptype_all, list) {        if (!ptype->dev || ptype->dev == skb->dev) {            if (pt_prev)                ret = deliver_skb(skb, pt_prev, orig_dev);            pt_prev = ptype;        }    }    ......    list_for_each_entry_rcu(ptype,            &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {        if (ptype->type == type &&            (ptype->dev == null_or_dev || ptype->dev == skb->dev ||             ptype->dev == orig_dev)) {            if (pt_prev)                ret = deliver_skb(skb, pt_prev, orig_dev);            pt_prev = ptype;        }    }}


推荐阅读