SKB包的接收,从网卡驱动到TCP层的处理流程

来源:互联网 发布:远光软件怎么样 编辑:程序博客网 时间:2024/06/05 22:47

在开发模块过程中,遇到一个问题:在NF_INET_LOCAL_IN钩子处截获数据包后,如果操作失败,还要把这些截获的数据包重新传递到TCP层处理。但是这个操作是在内核线程中完成,不知道会不会对正常的数据包接收过程产生影响?因此,需要知道数据包在从网络层传递到传输层时的上下文环境(指的是是否禁止内核抢占、是否需要获取锁等)。为了解决这个问题,决定将数据包的接收过程从驱动程序到TCP层的处理流程梳理了一遍。

  在文中的叙述过程中,将网卡驱动和网络层之间的部分,称之为网络核心层,如下图所示:


一、驱动程序

  为了找到skb包传递到传输层的上下文,肯定要从数据包接收的下半部,也就是数据接收的软中断中去找,但是既然要梳理,就要梳理的彻底一点,确保没有遗漏,因此从网卡的驱动程序开始。每个网卡都会有一个中断号,驱动程序中会有一个对应的中断处理函数。当数据包到达时,网卡会向CPU发送一个中断,然后会调用特定于网络的驱动程序,来接收数据包。选择的驱动程序是3c501网卡的驱动,该驱动比较简单,便于看出从驱动程序传递到网络核心层传输的过程。3c501网卡对应的中断处理函数el_interrupt(),源码如下(只列出关键的部分):

[cpp] view plain copy
print?
  1. static irqreturn_t el_interrupt(int irq, void *dev_id)  
  2. {  
  3.     struct net_device *dev = dev_id;  
  4.     struct net_local *lp;  
  5.     int ioaddr;  
  6.     int axsr;           /* Aux. status reg. */  
  7.   
  8.     ioaddr = dev->base_addr;  
  9.     lp = netdev_priv(dev);  
  10.   
  11.     spin_lock(&lp->lock);   
  12.     ……  
  13.   
  14.     if (lp->txing) {  
  15.       
  16.         ……  
  17.           
  18.     } else {  
  19.         /* 
  20.          *  In receive mode. 
  21.          */  
  22.   
  23.         int rxsr = inb(RX_STATUS);  
  24.           
  25.         …….  
  26.           
  27.         if (rxsr & RX_MISSED)  
  28.             dev->stats.rx_missed_errors++;  
  29.         else if (rxsr & RX_RUNT) {  
  30.             /* Handled to avoid board lock-up. */  
  31.             dev->stats.rx_length_errors++;  
  32.             if (el_debug > 5)  
  33.                 pr_debug(”%s: runt.\n”, dev->name);  
  34.         } else if (rxsr & RX_GOOD) {  
  35.             /* 
  36.              *  Receive worked. 
  37.              */  
  38.             el_receive(dev);  
  39.         } else {  
  40.             /* 
  41.              *  Nothing?  Something is broken! 
  42.              */  
  43.             if (el_debug > 2)  
  44.                 pr_debug(”%s: No packet seen, rxsr=%02x **resetting 3c501***\n”,  
  45.                     dev->name, rxsr);  
  46.             el_reset(dev);  
  47.         }  
  48.     }  
  49.   
  50.     /* 
  51.      *  Move into receive mode 
  52.      */  
  53.   
  54.     outb(AX_RX, AX_CMD);  
  55.     outw(0x00, RX_BUF_CLR);  
  56.     inb(RX_STATUS);     /* Be certain that interrupts are cleared. */  
  57.     inb(TX_STATUS);  
  58.     spin_unlock(&lp->lock);  
  59. out:  
  60.     return IRQ_HANDLED;  
  61. }  
static irqreturn_t el_interrupt(int irq, void *dev_id){    struct net_device *dev = dev_id;    struct net_local *lp;    int ioaddr;    int axsr;           /* Aux. status reg. */    ioaddr = dev->base_addr;    lp = netdev_priv(dev);    spin_lock(&lp->lock);     ......    if (lp->txing) {        ......    } else {        /*         *  In receive mode.         */        int rxsr = inb(RX_STATUS);        .......        if (rxsr & RX_MISSED)            dev->stats.rx_missed_errors++;        else if (rxsr & RX_RUNT) {            /* Handled to avoid board lock-up. */            dev->stats.rx_length_errors++;            if (el_debug > 5)                pr_debug("%s: runt.\n", dev->name);        } else if (rxsr & RX_GOOD) {            /*             *  Receive worked.             */            el_receive(dev);        } else {            /*             *  Nothing?  Something is broken!             */            if (el_debug > 2)                pr_debug("%s: No packet seen, rxsr=%02x **resetting 3c501***\n",                    dev->name, rxsr);            el_reset(dev);        }    }    /*     *  Move into receive mode     */    outb(AX_RX, AX_CMD);    outw(0x00, RX_BUF_CLR);    inb(RX_STATUS);     /* Be certain that interrupts are cleared. */    inb(TX_STATUS);    spin_unlock(&lp->lock);out:    return IRQ_HANDLED;}

中断处理中调用inb()来获取当前中断的结果,如果是RX_GOOD,则调用el_receive()(3c501的接收函数)来处理接收数据的工作。从el_interrupt()中可以看出el_receive()返回后,驱动程序中对中断的处理已经基本完成。因此,要继续从el_receive()函数中去找前面提出的问题的答案。

  el_receive()中的关键代码及分析如下:

[cpp] view plain copy
print?
  1. static void el_receive(struct net_device *dev)  
  2. {  
  3.     ……  
  4.       
  5.     outb(AX_SYS, AX_CMD);  
  6.     skb = dev_alloc_skb(pkt_len+2);  
  7.   
  8.     /* 
  9.      *  Start of frame 
  10.      */  
  11.   
  12.     outw(0x00, GP_LOW);  
  13.     if (skb == NULL) {  
  14.         pr_info(”%s: Memory squeeze, dropping packet.\n”, dev->name);  
  15.         dev->stats.rx_dropped++;  
  16.         return;  
  17.     } else {  
  18.         skb_reserve(skb, 2);    /* Force 16 byte alignment */  
  19.         /* 
  20.          *  The read increments through the bytes. The interrupt 
  21.          *  handler will fix the pointer when it returns to 
  22.          *  receive mode. 
  23.          */  
  24.         insb(DATAPORT, skb_put(skb, pkt_len), pkt_len);  
  25.         /* 
  26.          * 调用eth_type_trans()函数来获取数据帧承载的 
  27.          * 报文类型,并且将skb包中的数据起始位置 
  28.          * 移到数据帧中报文的起始位置。如果 
  29.          * 承载的是IP报文,则此时data指向的是IP首部的 
  30.          * 地址。 
  31.          */  
  32.         skb->protocol = eth_type_trans(skb, dev);  
  33.         /* 
  34.          * 调用netif_rx()将接收的数据包传递到 
  35.          * 网络核心层。 
  36.          */  
  37.         netif_rx(skb);  
  38.         dev->stats.rx_packets++;  
  39.         dev->stats.rx_bytes += pkt_len;  
  40.     }  
  41.     return;  
  42. }  
static void el_receive(struct net_device *dev){    ......    outb(AX_SYS, AX_CMD);    skb = dev_alloc_skb(pkt_len+2);    /*     *  Start of frame     */    outw(0x00, GP_LOW);    if (skb == NULL) {        pr_info("%s: Memory squeeze, dropping packet.\n", dev->name);        dev->stats.rx_dropped++;        return;    } else {        skb_reserve(skb, 2);    /* Force 16 byte alignment */        /*         *  The read increments through the bytes. The interrupt         *  handler will fix the pointer when it returns to         *  receive mode.         */        insb(DATAPORT, skb_put(skb, pkt_len), pkt_len);        /*         * 调用eth_type_trans()函数来获取数据帧承载的         * 报文类型,并且将skb包中的数据起始位置         * 移到数据帧中报文的起始位置。如果         * 承载的是IP报文,则此时data指向的是IP首部的         * 地址。         */        skb->protocol = eth_type_trans(skb, dev);        /*         * 调用netif_rx()将接收的数据包传递到         * 网络核心层。         */        netif_rx(skb);        dev->stats.rx_packets++;        dev->stats.rx_bytes += pkt_len;    }    return;}

el_receive()首先分配一个sk_buff缓冲区,然后从网卡中拷贝数据,之后调用netif_rx()将skb包传递到网络核心层,至此网卡驱动中所做的工作已经完成了。也就是说,当netif_rx()返回后,数据包接收的上半部,也就完成了。从这里开始我们就要开始进入网络核心层中的处理了。

小结:在网卡驱动的中断处理函数中,也就是数据接收的上半部中,不可能存在和向传输层传递数据包相关的上下文。但是既然要梳理整个流程,就要彻底一些,以免漏掉什么东西。通过对驱动程序的研究,可以知道三层、四层中的skb是怎么来的,数据包是怎么从驱动程序传递到内核的协议栈中。当然还有skb中一些成员是如何设置的。

二、网络核心层

 从这里开始,将更多的注意力放在处理过程中锁的获取、中断的处理、以及内核抢占等同步手段的处理上,找出向传输层传递数据包时的上下文环境,也就是调用tcp_v4_rcv()开始传输层处理时的上下文环境。

  在3c501的网卡驱动程序中,看到将skb包传递到上层是通过netif_rx()函数来完成,每个网卡驱动程序在接收到一个包后,都会调用该接口来传递到上层。接下来看看这个接口的实现,源码及分析如下:

[cpp] view plain copy
print?
  1. /** 
  2.  *  netif_rx    -   post buffer to the network code 
  3.  *  @skb: buffer to post 
  4.  * 
  5.  *  This function receives a packet from a device driver and queues it for 
  6.  *  the upper (protocol) levels to process.  It always succeeds. The buffer 
  7.  *  may be dropped during processing for congestion control or by the 
  8.  *  protocol layers. 
  9.  * 
  10.  *  return values: 
  11.  *  NET_RX_SUCCESS  (no congestion) 
  12.  *  NET_RX_DROP     (packet was dropped) 
  13.  * 
  14.  */  
  15. /* 
  16.  * 数据到来时,会产生中断,首先执行特定网卡的中断 
  17.  * 处理程序,然后再执行接收函数分配新的套接字缓冲 
  18.  * 区,然后通过调用netif_rx来讲数据传到上层 
  19.  * 调用该函数标志着控制由特定于网卡的代码转移到了 
  20.  * 网络层的通用接口部分。该函数的作用是,将接收到 
  21.  * 的分组放置到一个特定于CPU的等待队列上,并退出中 
  22.  * 断上下文,使得CPU可以执行其他任务 
  23.  */  
  24. int netif_rx(struct sk_buff *skb)  
  25. {  
  26.     struct softnet_data *queue;  
  27.     unsigned long flags;  
  28.   
  29.     /* if netpoll wants it, pretend we never saw it */  
  30.     if (netpoll_rx(skb))  
  31.         return NET_RX_DROP;  
  32.   
  33.     /* 
  34.      * 如果没有设置数据包到达的时间, 
  35.      * 则获取当前的时钟时间设置到tstamp上 
  36.      */  
  37.     if (!skb->tstamp.tv64)  
  38.         net_timestamp(skb);  
  39.   
  40.     /* 
  41.      * The code is rearranged so that the path is the most 
  42.      * short when CPU is congested, but is still operating. 
  43.      */  
  44.     local_irq_save(flags);  
  45.     /* 
  46.      * 每个CPU都有一个softnet_data类型变量, 
  47.      * 用来管理进出分组的等待队列 
  48.      */  
  49.     queue = &__get_cpu_var(softnet_data);  
  50.   
  51.     /* 
  52.      * 记录当前CPU上接收的数据包的个数 
  53.      */  
  54.     __get_cpu_var(netdev_rx_stat).total++;  
  55.     if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {  
  56.         /* 
  57.          * 如果接受队列不为空,说明当前正在处理数据包, 
  58.          * 则不需要触发 
  59.          * 软中断操作,直接将数据包放到接收 
  60.          * 队列中,待前面的数据包处理之后,会立即处理 
  61.          * 当前的数据包。 
  62.          */  
  63.         if (queue->input_pkt_queue.qlen) {  
  64. enqueue:  
  65.             __skb_queue_tail(&queue->input_pkt_queue, skb);  
  66.             local_irq_restore(flags);  
  67.             /* 
  68.              * 至此,中断处理,也就是数据包接收的上半部已经 
  69.              * 已经基本处理完成,剩下的工作交给软中断来处理。 
  70.              */  
  71.             return NET_RX_SUCCESS;  
  72.         }  
  73.   
  74.         /* 
  75.          * 如果NAPI程序尚未运行,则重新调度使其开始 
  76.          * 轮询,并触发NET_RX_SOFTIRQ软中断。 
  77.          */  
  78.         napi_schedule(&queue->backlog);  
  79.         goto enqueue;  
  80.     }  
  81.   
  82.        /* 
  83.      * 如果当前CPU的接受队列已满,则丢弃数据包。 
  84.      * 并记录当前CPU上丢弃的数据包个数 
  85.      */  
  86.     __get_cpu_var(netdev_rx_stat).dropped++;  
  87.     local_irq_restore(flags);  
  88.   
  89.     kfree_skb(skb);  
  90.     return NET_RX_DROP;  
  91. }  
/** *  netif_rx    -   post buffer to the network code *  @skb: buffer to post * *  This function receives a packet from a device driver and queues it for *  the upper (protocol) levels to process.  It always succeeds. The buffer *  may be dropped during processing for congestion control or by the *  protocol layers. * *  return values: *  NET_RX_SUCCESS  (no congestion) *  NET_RX_DROP     (packet was dropped) * *//* * 数据到来时,会产生中断,首先执行特定网卡的中断 * 处理程序,然后再执行接收函数分配新的套接字缓冲 * 区,然后通过调用netif_rx来讲数据传到上层 * 调用该函数标志着控制由特定于网卡的代码转移到了 * 网络层的通用接口部分。该函数的作用是,将接收到 * 的分组放置到一个特定于CPU的等待队列上,并退出中 * 断上下文,使得CPU可以执行其他任务 */int netif_rx(struct sk_buff *skb){    struct softnet_data *queue;    unsigned long flags;    /* if netpoll wants it, pretend we never saw it */    if (netpoll_rx(skb))        return NET_RX_DROP;    /*     * 如果没有设置数据包到达的时间,     * 则获取当前的时钟时间设置到tstamp上     */    if (!skb->tstamp.tv64)        net_timestamp(skb);    /*     * The code is rearranged so that the path is the most     * short when CPU is congested, but is still operating.     */    local_irq_save(flags);    /*     * 每个CPU都有一个softnet_data类型变量,     * 用来管理进出分组的等待队列     */    queue = &__get_cpu_var(softnet_data);    /*     * 记录当前CPU上接收的数据包的个数     */    __get_cpu_var(netdev_rx_stat).total++;    if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {        /*         * 如果接受队列不为空,说明当前正在处理数据包,         * 则不需要触发         * 软中断操作,直接将数据包放到接收         * 队列中,待前面的数据包处理之后,会立即处理         * 当前的数据包。         */        if (queue->input_pkt_queue.qlen) {enqueue:            __skb_queue_tail(&queue->input_pkt_queue, skb);            local_irq_restore(flags);            /*             * 至此,中断处理,也就是数据包接收的上半部已经             * 已经基本处理完成,剩下的工作交给软中断来处理。             */            return NET_RX_SUCCESS;        }        /*         * 如果NAPI程序尚未运行,则重新调度使其开始         * 轮询,并触发NET_RX_SOFTIRQ软中断。         */        napi_schedule(&queue->backlog);        goto enqueue;    }       /*     * 如果当前CPU的接受队列已满,则丢弃数据包。     * 并记录当前CPU上丢弃的数据包个数     */    __get_cpu_var(netdev_rx_stat).dropped++;    local_irq_restore(flags);    kfree_skb(skb);    return NET_RX_DROP;}

每个CPU都有一个管理进出分组的softnet_data结构的实例,netif_rx()将skb包放在当前CPU的接收队列中,然后调用napi_schedule()来将设备放置在NAPI的轮询队列中,并触发NET_RX_SOFTIRQ软中断来进行数据包接收的下半部的处理,也就是在这个过程中找到在向传输层传递数据包时的上下文。

  NET_RX_SOFTIRQ软中断对应的处理函数是net_rx_action(),参见net_dev_init()。在解决开始时提到的问题之前,需要先找到软中断的处理函数被调用时的上下文。

有几种方法可开启软中断处理,但这些都归结为调用do_softirq()函数。其中一种方式就是在软中断守护进程(每个CPU都会有一个守护进程)中调用,就以此种方式为切入点来探究。软中断守护进程的处理函数时ksoftirqd(),其关键的代码如下:

[cpp] view plain copy
print?
  1. static int ksoftirqd(void * __bind_cpu)  
  2. {  
  3.     ……  
  4.   
  5.     while (!kthread_should_stop()) {  
  6.         preempt_disable();  
  7.           
  8.         ……  
  9.   
  10.         while (local_softirq_pending()) {  
  11.               
  12.             ……  
  13.               
  14.             do_softirq();  
  15.             preempt_enable_no_resched();  
  16.             cond_resched();  
  17.             preempt_disable();  
  18.               
  19.             ……  
  20.         }  
  21.         preempt_enable();  
  22.         ……  
  23.     }  
  24.       
  25.     ……  
  26. }  
static int ksoftirqd(void * __bind_cpu){    ......    while (!kthread_should_stop()) {        preempt_disable();        ......        while (local_softirq_pending()) {            ......            do_softirq();            preempt_enable_no_resched();            cond_resched();            preempt_disable();            ......        }        preempt_enable();        ......    }    ......}

[cpp] view plain copy
print?
  1. static int ksoftirqd(void * __bind_cpu)  
  2. {  
  3.     ……  
  4.   
  5.     while (!kthread_should_stop()) {  
  6.         preempt_disable();  
  7.           
  8.         ……  
  9.   
  10.         while (local_softirq_pending()) {  
  11.               
  12.             ……  
  13.               
  14.             do_softirq();  
  15.             preempt_enable_no_resched();  
  16.             cond_resched();  
  17.             preempt_disable();  
  18.               
  19.             ……  
  20.         }  
  21.         preempt_enable();  
  22.         ……  
  23.     }  
  24.       
  25.     ……  
  26. }  
static int ksoftirqd(void * __bind_cpu){    ......    while (!kthread_should_stop()) {        preempt_disable();        ......        while (local_softirq_pending()) {            ......            do_softirq();            preempt_enable_no_resched();            cond_resched();            preempt_disable();            ......        }        preempt_enable();        ......    }    ......}

从这里可以看出,在开始处理软中断之前,要先调用preempt_disable()来禁止内核抢占,这是我们找到的一个需要关注的上下文环境,也就是在协议层中接收数据时,首先要作的就是禁止内核抢占(当然是不是这样,还要看后面的处理,这里姑且这么认为吧)。

接下来看do_softirq()中的处理,源码如下:

[cpp] view plain copy
print?
  1. asmlinkage void do_softirq(void)  
  2. {  
  3.     __u32 pending;  
  4.     unsigned long flags;  
  5.   
  6.     /* 
  7.      * 确认当前不处于中断上下文中(当然,即不涉及 
  8.      * 硬件中断)。如果处于中断上下文,则立即结束。 
  9.      * 因为软中断用于执行ISR中非时间关键部分,所以 
  10.      * 其代码本身一定不能在中断处理程序内调用。 
  11.      */  
  12.     if (in_interrupt())  
  13.         return;  
  14.   
  15.     local_irq_save(flags);  
  16.   
  17.         /* 
  18.          *  确定当前CPU软中断位图中所有置位的比特位。 
  19.          */  
  20.     pending = local_softirq_pending();  
  21.   
  22.     /* 
  23.      * 如果有软中断等待处理,则调用__do_softirq()。 
  24.      */  
  25.     if (pending)  
  26.         __do_softirq();  
  27.   
  28.     local_irq_restore(flags);  
  29. }  
asmlinkage void do_softirq(void){    __u32 pending;    unsigned long flags;    /*     * 确认当前不处于中断上下文中(当然,即不涉及     * 硬件中断)。如果处于中断上下文,则立即结束。     * 因为软中断用于执行ISR中非时间关键部分,所以     * 其代码本身一定不能在中断处理程序内调用。     */    if (in_interrupt())        return;    local_irq_save(flags);        /*         *  确定当前CPU软中断位图中所有置位的比特位。         */    pending = local_softirq_pending();    /*     * 如果有软中断等待处理,则调用__do_softirq()。     */    if (pending)        __do_softirq();    local_irq_restore(flags);}

在开始调用__do_softirq()作进一步的处理之前,要先调用local_irq_save()屏蔽所有中断,并且保存当前的中断状态,这是第二个我们需要关注的上下文环境。接下来看__do_softirq(),关键代码如下:

[cpp] view plain copy
print?
  1. asmlinkage void __do_softirq(void)  
  2. {  
  3.     ……  
  4.     __local_bh_disable((unsigned long)__builtin_return_address(0));   
  5.     ……  
  6.     local_irq_enable();  
  7.   
  8.     h = softirq_vec;  
  9.   
  10.     do {  
  11.         if (pending & 1) {  
  12.             ……  
  13.             h->action(h);  
  14.             ……  
  15.         }  
  16.         h++;  
  17.         pending >>= 1;  
  18.     } while (pending);  
  19.   
  20.     local_irq_disable();  
  21.     ……  
  22.     _local_bh_enable();  
  23. }  
asmlinkage void __do_softirq(void){    ......    __local_bh_disable((unsigned long)__builtin_return_address(0));     ......    local_irq_enable();    h = softirq_vec;    do {        if (pending & 1) {            ......            h->action(h);            ......        }        h++;        pending >>= 1;    } while (pending);    local_irq_disable();    ......    _local_bh_enable();}

__do_softirq()在调用软中断对应的action之前,会先调用__local_bh_disable()来和其他下半部操作互斥,然后调用local_irq_enable()来启用中断(注意这个操作和local_irq_restore()不一样),这是第三个我们需要关注的上文环境。在__do_softirq()中会调用到NET_RX_SOFTIRQ软中断对应的

处理函数net_rx_action()。接下来看net_rx_action()函数的处理

[cpp] view plain copy
print?
  1. static void net_rx_action(struct softirq_action *h)  
  2. {  
  3.     ……  
  4.     local_irq_disable();  
  5.   
  6.     while (!list_empty(list)) {  
  7.         ……  
  8.         local_irq_enable();  
  9.         ……  
  10.           
  11.         if (test_bit(NAPI_STATE_SCHED, &n->state)) {  
  12.             work = n->poll(n, weight);  
  13.             trace_napi_poll(n);  
  14.         }  
  15.           
  16.         ……  
  17.         local_irq_disable();  
  18.         ……  
  19.     }  
  20. out:  
  21.     local_irq_enable();  
  22.     ……  
  23. }  
static void net_rx_action(struct softirq_action *h){    ......    local_irq_disable();    while (!list_empty(list)) {        ......        local_irq_enable();        ......        if (test_bit(NAPI_STATE_SCHED, &n->state)) {            work = n->poll(n, weight);            trace_napi_poll(n);        }        ......        local_irq_disable();        ......    }out:    local_irq_enable();    ......}

net_rx_action()中进入循环前调用local_irq_disable()来关闭软中断,但是在调用NAPI轮询队列上设备的poll函数前又调用local_irq_enable()来开启中断,因此在net_rx_action()中poll函数的执行上下文(指锁、中断等同步手段的环境)中和__do_softirq()中保持一致,没有发生变化。接下来要关注的是net_rx_action()中调用poll接口,默认情况下调用的函数是process_backlog(),源码如下:

[cpp] view plain copy
print?
  1. static int process_backlog(struct napi_struct *napi, int quota)  
  2. {  
  3.     int work = 0;  
  4.     struct softnet_data *queue = &__get_cpu_var(softnet_data);  
  5.     unsigned long start_time = jiffies;  
  6.   
  7.     napi->weight = weight_p;  
  8.     do {  
  9.         struct sk_buff *skb;  
  10.   
  11.         local_irq_disable();  
  12.               /* 
  13.                * 从当前CPU的接收队列中取出一个SKB包。 
  14.                */  
  15.         skb = __skb_dequeue(&queue->input_pkt_queue);  
  16.         /* 
  17.          * 如果所有的数据已处理完成,则调用 
  18.          * __napi_complete()来将当前设备移除轮询队列。 
  19.          */  
  20.         if (!skb) {  
  21.             __napi_complete(napi);  
  22.             local_irq_enable();  
  23.             break;  
  24.         }  
  25.         local_irq_enable();  
  26.   
  27.         netif_receive_skb(skb);  
  28.     /* 
  29.      * 如果当前的处理次数小于设备的权重,并且 
  30.      * 处理时间不超过1个jiffies时间(如果HZ为1000,则 
  31.      * 相当于是1毫秒),则处理下一个SKB包。 
  32.      */  
  33.     } while (++work < quota && jiffies == start_time);  
  34.   
  35.     return work;  
  36. }  
static int process_backlog(struct napi_struct *napi, int quota){    int work = 0;    struct softnet_data *queue = &__get_cpu_var(softnet_data);    unsigned long start_time = jiffies;    napi->weight = weight_p;    do {        struct sk_buff *skb;        local_irq_disable();              /*               * 从当前CPU的接收队列中取出一个SKB包。               */        skb = __skb_dequeue(&queue->input_pkt_queue);        /*         * 如果所有的数据已处理完成,则调用         * __napi_complete()来将当前设备移除轮询队列。         */        if (!skb) {            __napi_complete(napi);            local_irq_enable();            break;        }        local_irq_enable();        netif_receive_skb(skb);    /*     * 如果当前的处理次数小于设备的权重,并且     * 处理时间不超过1个jiffies时间(如果HZ为1000,则     * 相当于是1毫秒),则处理下一个SKB包。     */    } while (++work < quota && jiffies == start_time);    return work;}

process_backlog()中首先从CPU的接收队列上,然后调用netif_receive_skb()将SKB包传递到网络层,所以netif_receive_skb()函数就是skb包在网络核心层的最后一次处理。netif_receive_skb()的关键代码如下:
[cpp] view plain copy
print?
  1. int netif_receive_skb(struct sk_buff *skb)  
  2. {  
  3.     ……  
  4.     rcu_read_lock();  
  5.     ……  
  6.       
  7.     type = skb->protocol;  
  8.     list_for_each_entry_rcu(ptype,  
  9.             &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {  
  10.         if (ptype->type == type &&  
  11.             (ptype->dev == null_or_orig || ptype->dev == skb->dev ||  
  12.              ptype->dev == orig_dev)) {  
  13.             if (pt_prev)  
  14.                 ret = deliver_skb(skb, pt_prev, orig_dev);  
  15.             pt_prev = ptype;  
  16.         }  
  17.     }  
  18.   
  19.     if (pt_prev) {  
  20.         ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);  
  21.     } else {  
  22.         ……  
  23.     }  
  24.   
  25. out:  
  26.     rcu_read_unlock();  
  27.     return ret;  
  28. }  
int netif_receive_skb(struct sk_buff *skb){    ......    rcu_read_lock();    ......    type = skb->protocol;    list_for_each_entry_rcu(ptype,            &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {        if (ptype->type == type &&            (ptype->dev == null_or_orig || ptype->dev == skb->dev ||             ptype->dev == orig_dev)) {            if (pt_prev)                ret = deliver_skb(skb, pt_prev, orig_dev);            pt_prev = ptype;        }    }    if (pt_prev) {        ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);    } else {        ......    }out:    rcu_read_unlock();    return ret;}

netif_receive_skb()中在调用三层的接收函数之前,需要调用rcu_read_lock()进入读临界区,这是第四个我们需要关注的上文环境。如果skb的三层协议类型是IP协议,则pt_prev->func()调用的就是ip_rcv()。


小结:经过上面的分析,有必要总结一下从软中断守护进程的处理函数ksoftirqd()到netif_receive_skb()中调用ip_rcv()将skb包传递到IP层时,ip_rcv()函数所处的上下文环境,下面的图列出了所处理的环境(图中只包含获取锁或禁止中断等进入保护区的操作,释放的操作相应地一一对应,不在图中列出):

三、网络层
  网络层中主要关注IPv4协议,其接收函数时ip_rcv()。ip_rcv()中首先判断skb包是否是发送给本机,如果是发送给其他机器,则直接丢弃,然后检查IP数据包是否是正常的IP包,如果是正常的数据包,则调用ip_rcv_finish()继续处理(忽略钩子的处理),如下所示:
[cpp] view plain copy
print?
  1. int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)  
  2. {  
  3.     ……  
  4. <span style=”white-space:pre”>    </span>return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,  
  5.                ip_rcv_finish);    
  6.     ……  
  7. }  
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev){    ......<span style="white-space:pre">    </span>return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,               ip_rcv_finish);      ......}
在ip_rcv()中没有类似互斥锁或中断相关的同步操作,继续看ip_rcv_finish()中的处理。
  ip_rcv_finish()中首先判断skb包中是否设置路由缓存,如果没有设置,调用ip_route_input()来查找路由项,然后调用dst_input()来处理skb包。在 ip_rcv_finish()函数中也没有类似获取锁或中断相关的同步操作,继续看dst_input()函数。

dst_input()源码如下:

[cpp] view plain copy
print?
  1. static inline int dst_input(struct sk_buff *skb)  
  2. {  
  3.     return skb_dst(skb)->input(skb);  
  4. }  
static inline int dst_input(struct sk_buff *skb){    return skb_dst(skb)->input(skb);}

其中skb_dst(skb)是获取skb的路由缓存项,如果数据包是发送到本地,input接口会设置为ip_local_deliver();如果需要转发,则设置的是ip_forward()。因为要研究的是传送到传输层时的上下文,因此假设这里设置的ip_local_deliver()。

 ip_local_deliver()首先检查是否需要组装分片,如果需要组装分片,则调用ip_defrag()来重新组合各个分片,最后经过钩子处理后,调用ip_local_deliver_finish()来将skb包传递到传输层,如下所示:

[cpp] view plain copy
print?
  1. int ip_local_deliver(struct sk_buff *skb)  
  2. {  
  3.     /* 
  4.      *  Reassemble IP fragments. 
  5.      */  
  6.     if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {  
  7.         if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))  
  8.             return 0;  
  9.     }  
  10.   
  11.     return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL,  
  12.                ip_local_deliver_finish);  
  13. }  
int ip_local_deliver(struct sk_buff *skb){    /*     *  Reassemble IP fragments.     */    if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {        if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))            return 0;    }    return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL,               ip_local_deliver_finish);}

ip_local_deliver()中同样没有使用内核中的同步手段,也就是没有我们关心的上下文环境(获取锁、开启或禁止中断等),接下来就剩下ip_local_deliver_finish()函数了。

ip_local_deliver_finish()中关键代码如下所示:

[cpp] view plain copy
print?
  1. static int ip_local_deliver_finish(struct sk_buff *skb)  
  2. {  
  3.     ……  
  4.     rcu_read_lock();  
  5.     {  
  6.         ……  
  7.     resubmit:  
  8.         raw = raw_local_deliver(skb, protocol);  
  9.   
  10.         hash = protocol & (MAX_INET_PROTOS - 1);  
  11.         ipprot = rcu_dereference(inet_protos[hash]);  
  12.         if (ipprot != NULL) {  
  13.             …….  
  14.               
  15.             ret = ipprot->handler(skb);  
  16.             if (ret < 0) {  
  17.                 protocol = -ret;  
  18.                 goto resubmit;  
  19.             }  
  20.             IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);  
  21.         } else {  
  22.             ……  
  23.         }  
  24.     }  
  25.  out:  
  26.     rcu_read_unlock();  
  27.   
  28.     return 0;  
  29. }  
static int ip_local_deliver_finish(struct sk_buff *skb){    ......    rcu_read_lock();    {        ......    resubmit:        raw = raw_local_deliver(skb, protocol);        hash = protocol & (MAX_INET_PROTOS - 1);        ipprot = rcu_dereference(inet_protos[hash]);        if (ipprot != NULL) {            .......            ret = ipprot->handler(skb);            if (ret < 0) {                protocol = -ret;                goto resubmit;            }            IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);        } else {            ......        }    } out:    rcu_read_unlock();    return 0;}

ip_local_deliver_finish()首先根据IP包承载的报文协议类型找到对应的net_protocol实例,然后调用其handler接口。ip_local_deliver_finish()中有我们关心的上下文操作,也就是对rcu_read_lock()的调用。如果是TCP协议,则handler为tcp_v4_rcv()。tcp_v4_rcv()是TCP协议的接收函数,该函数被调用时的上下文就是我们一直在探究的向TCP层传送数据包时的上下文。至此,我们完成了从网络驱动到向TCP传输数据包的过程的梳理,及tcp_v4_rcv()执行时的上下文。

四、总结
  tcp_v4_rcv()执行时的上下文,就是在图1-1中所示的ip_rcv()的执行上下文中再添加上rcu_read_lock()(ip_local_deliver_finish()中调用)的处理,上面已经说得很清楚了,就不再画图了。

最后把从网卡驱动到TCP层的接收处理的流程列出来,如下图所示:








阅读全文
0 0
原创粉丝点击