网络子系统在链路层的收发过程剖析

由于太长, 这只是一部分内容,完整的文档在附件中。
有兴趣的请看看并帮忙指正,谢谢。


1),Skb_buff
/* To allow 64K frame to be packed as single skb without frag_list */
#define MAX_SKB_FRAGS (65536/PAGE_SIZE + 2)

typedef struct skb_frag_struct skb_frag_t;

struct skb_frag_struct {
        struct page *page;
        __u16 page_offset;
        __u16 size;
};

/* This data is invariant across clones and lives at
* the end of the header data, ie. at skb->end.
*/
struct skb_shared_info {
        atomic_t        dataref;
        unsigned short        nr_frags;
        unsigned short        gso_size;
        /* Warning: this field is not always filled in (UFO)! */
        unsigned short        gso_segs;
        unsigned short  gso_type;
        unsigned int    ip6_frag_id;
        struct sk_buff        *frag_list;
        skb_frag_t        frags[MAX_SKB_FRAGS];
};


Skb比较复杂的部分在于skb_shared_info部分,alloc_skb()在为数据分配空间的时候,会在这个数据的末尾加上一个skb_shared_info结构,这个结构就是用于scatter/gather IO的实现的。它主要用于提高性能,避免数据的多次拷贝。例如,当用户用sendmsg分送一个数组结构的数据时,这些数据在物理可能是不连续的(大多数情况),在不支持scatter/gather IO的网卡上,它只能通过重新拷贝,将它重装成连续的skb(skb_linearize),才可以进行DMA操作。而在支持S/G IO上,它就省去了这次拷贝。

4),数据包的接收

* Incoming packets are placed on per-cpu queues so that
* no locking is needed.
*/
struct softnet_data
{
        struct net_device        *output_queue;
        struct sk_buff_head        input_pkt_queue;
        struct list_head        poll_list;
        struct sk_buff                *completion_queue;

        struct net_device        backlog_dev;        /* Sorry.  */
#ifdef CONFIG_NET_DMA
        struct dma_chan                *net_dma;
#endif
};


这个数据结构同时用于接收与发送数据包,它为per_CPU结构,这样每个CPU有自己独立的信息,这样在SMP之间就避免了加锁操作,从而大大提高了信息处理的并行性。

struct net_device        *output_queue;         
struct sk_buff                *completion_queue;
这两个域用于发送数据,将在下一节中描述。

struct sk_buff_head         input_pkt_queue;
struct list_head        poll_list;
struct net_device        backlog_dev; 
这三个域用于接收数据,其中input_pkt_queue与backlog_dev仅用于non-NAPI的NIC,input_pkt_queue是接收到的数据队列头,它用于netif_rx()中,并最终由虚拟的poll函数process_backlog()处理这个SKB队列。
poll_list则是有数据包等待处理的NIC设备队列。对于non-NAPI驱动来说,它始终是backlog_dev。

接收过程:
   当一个数据包到来时,NIC会产生一个中断,这时,它会执行中断处理全程。
当网络适配器接收到数据帧时,就会触发一个中断,中断处理程序执行一些需要及时处理的任务,然后在下半部进行其它可以延迟的处理。中断处理程序主要进行以下一些操作:
(1)    分配sk_buff数据结构,并将接收到的数据帧从网络适配器I/O端口拷贝到sk_buff缓冲区中;
(2)    从数据帧中提取出一些信息,并设置sk_buff相应的参数,这些参数将被上层的网络协议使用,例如skb->protocol;
(3)    通过软中断NET_RX_SOFTIRQ通知内核接收到新的数据帧。



(1), NON-NAPI方式:
如3c59x中的vortex_interrupt(),它会判断寄存器的值作出相应的动作:

                if (status & RxComplete)
                        vortex_rx(dev);
如上,当中断指示,有数据包在等待接收,这时,中断例程会调用接收函数vortex_rx(dev)接收新到来的包(如下,只保留核心部分):

int pkt_len = rx_status & 0x1fff;
                        struct sk_buff *skb;

                        skb = dev_alloc_skb(pkt_len + 5);
                
                        if (skb != NULL) {
                                skb->dev = dev;
                                skb_reserve(skb, 2);        /* Align IP on 16 byte boundaries */
                                /* 'skb_put()' points to the start of sk_buff data area. */
                                if (vp->bus_master &&
                                        ! (ioread16(ioaddr + Wn7_MasterStatus) & 0x8000)) {
                                        dma_addr_t dma = pci_map_single(VORTEX_PCI(vp), skb_put(skb, pkt_len),
                                                                           pkt_len, PCI_DMA_FROMDEVICE);
                                        iowrite32(dma, ioaddr + Wn7_MasterAddr);
                                        iowrite16((skb->len + 3) & ~3, ioaddr + Wn7_MasterLen);
                                        iowrite16(StartDMAUp, ioaddr + EL3_CMD);
                                        while (ioread16(ioaddr + Wn7_MasterStatus) & 0x8000)
                                                ;
                                        pci_unmap_single(VORTEX_PCI(vp), dma, pkt_len, PCI_DMA_FROMDEVICE);
                        
                                }
                                iowrite16(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */
                                skb->protocol = eth_type_trans(skb, dev);
                                netif_rx(skb);


                        
它首先为新到来的数据包分配一个skb结构及pkt_len+5大小的数据长度,然后便将接收到的数据从网卡复制到(DMA)这个SKB的数据部分中。最后,调用netif_rx(skb)进一步处理数据:

int netif_rx(struct sk_buff *skb)
{
        struct softnet_data *queue;
        unsigned long flags;

        /*
         * The code is rearranged so that the path is the most
         * short when CPU is congested, but is still operating.
         */
        local_irq_save(flags);
        queue = &__get_cpu_var(softnet_data);

        if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
                if (queue->input_pkt_queue.qlen) {
enqueue:
                        dev_hold(skb->dev);
                        __skb_queue_tail(&queue->input_pkt_queue, skb);
                        local_irq_restore(flags);
                        return NET_RX_SUCCESS;
                }

                netif_rx_schedule(&queue->backlog_dev);
                goto enqueue;
        }
}


这段代码关键是,将这个SKB加入到相应的input_pkt_queue队列中,并调用netif_rx_schedule(),而对于NAPI方式,它没有使用input_pkt_queue队列,而是使用私有的队列,所以它没有这一个步骤。至此,中断的上半部已经完成,以下的工作则交由中断的下半部来实现。
void __netif_rx_schedule(struct net_device *dev)
{
        unsigned long flags;

        local_irq_save(flags);
        dev_hold(dev);
        list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
        if (dev->quota < 0)
                dev->quota += dev->weight;
        else
                dev->quota = dev->weight;
        __raise_softirq_irqoff(NET_RX_SOFTIRQ);
        local_irq_restore(flags);
}

netif_rx_schedule()就是将有等待接收数据包的NIC链入softnet_data的poll_list队列,然后触发软中断,让后半部去完成数据的处理工作。

注意:这里是否调用netif_rx_schedule()是有条件的,即当queue->input_pkt_queue.qlen==0时才会调用,否则由于这个队列的长度不为0,这个中断下半部的执行已由先前的中断触发,它会断续处理余下来的数据包的接收,所以,这里就不必要再次触发它的执行了。

总之,NON-NAPI的中断上半部接收过程可以简单的描述为,它首先为新到来的数据帧分配合适长度的SKB,再将接收到的数据从NIC中拷贝过来,然后将这个SKB链入当前CPU的softnet_data中的链表中,最后进一步触发中断下半部发继续处理。


(2), NAPI方式:

static irqreturn_t e100_intr(int irq, void *dev_id)
{

        if(likely(netif_rx_schedule_prep(netdev))) {
                e100_disable_irq(nic);
                __netif_rx_schedule(netdev);
        }

        return IRQ_HANDLED;
}
     

   
可以看到,两种方式的不同之处在于,NAPI方式直接调用__netif_rx_schedule(),而非NAPI方式则要通过辅助函数netif_rx()设置好接收队列再调用netif_rx_schedule(),再者,在非NAPI方式中,提交的是netif_rx_schedule(&queue->backlog_dev),而NAPI中,提交的是__netif_rx_schedule(netdev),即是设备驱动的net_device结构,而不是queue中的backlog_dev。


(3),net_rx_action()
        netif_rx_schedule()触发中断下半部的执行,这个下半部将执行net_rx_action():

static void net_rx_action(struct softirq_action *h)
{
        struct softnet_data *queue = &__get_cpu_var(softnet_data);
        unsigned long start_time = jiffies;

        local_irq_disable();

        while (!list_empty(&queue->poll_list)) {
                struct net_device *dev;

                local_irq_enable();

                dev = list_entry(queue->poll_list.next,
                                 struct net_device, poll_list);

                if (dev->quota <= 0 || dev->poll(dev, &budget)) {
                        … //出错处理
                } else {
                        netpoll_poll_unlock(have);
                        dev_put(dev);
                        local_irq_disable();
                }
}

由上可以看到,下半部的主要工作是遍历有数据帧等待接收的设备链表,对于每个设备,执行它相应的poll函数。

(4),poll函数
NON—NAPI方式:
        这种方式对应该的poll函数为process_backlog:

        struct softnet_data *queue = &__get_cpu_var(softnet_data);
        for (; {

                local_irq_disable();
                skb = __skb_dequeue(&queue->input_pkt_queue);
                local_irq_enable();

                netif_receive_skb(skb);
}


它首先找到当前CPU的softnet_data结构,然后遍历其数据队SKB,并将数据上交netif_receive_skb(skb)处理。

NAPI方式:
        这种方式下,NIC驱动程序会提供自己的poll函数和私有接收队列。
如intel 8255x系列网卡程序e100,它有在初始化的时候首先分配一个接收队列,而不像以上那种方式在接收到数据帧的时候再为其分配数据空间。这样,NAPI的poll函数在处理接收的时候,它遍历的是自己的私有队列:
 
       static int e100_poll(struct net_device *netdev, int *budget)
{
        e100_rx_clean(nic, &work_done, work_to_do);
        ……
}

static void e100_rx_clean(struct nic *nic, unsigned int *work_done,
        unsigned int work_to_do)
{
        …….
        /* Indicate newly arrived packets */
        for(rx = nic->rx_to_clean; rx->skb; rx = nic->rx_to_clean = rx->next) {
                int err = e100_rx_indicate(nic, rx, work_done, work_to_do);
                if(-EAGAIN == err) {
                ……
        }
                ……
}

static int e100_rx_indicate(struct nic *nic, struct rx *rx,
        unsigned int *work_done, unsigned int work_to_do)
{
        struct sk_buff *skb = rx->skb;
        struct rfd *rfd = (struct rfd *)skb->data;
        rfd_status = le16_to_cpu(rfd->status);

        /* Get actual data size */
        actual_size = le16_to_cpu(rfd->actual_size) & 0x3FFF;

        /* Pull off the RFD and put the actual data (minus eth hdr) */
        skb_reserve(skb, sizeof(struct rfd));
        skb_put(skb, actual_size);
        skb->protocol = eth_type_trans(skb, nic->netdev);

        netif_receive_skb(skb);

        return 0;
}

主要工作在e100_rx_indicate()中完成,这主要重设SKB的一些参数,然后跟process_backlog(),一样,最终调用netif_receive_skb(skb)。


(5)netif_receive_skb函数
netif_receive_skb是链路层接收数据报的最后一站。它根据注册在全局数组ptype_all和ptype_base里的网络层数据报类型,把数据报递交给不同的网络层协议的接收函数(INET域中主要是ip_rcv和arp_rcv)。

//net/core/dev.c
int netif_receive_skb(struct sk_buff *skb)
{
    struct packet_type *ptype, *pt_prev;
    int ret = NET_RX_DROP;
    unsigned short type;

#ifdef CONFIG_NETPOLL
    if (skb->dev->netpoll_rx && skb->dev->poll && netpoll_rx(skb)) {
        kfree_skb(skb);
        return NET_RX_DROP;
    }
#endif

    if (!skb->stamp.tv_sec)
        net_timestamp(&skb->stamp);
    
    //将一组接口作为一个单独的接口对待
    skb_bond(skb);

    __get_cpu_var(netdev_rx_stat).total++;

    skb->h.raw = skb->nh.raw = skb->data;
    skb->mac_len = skb->nh.raw - skb->mac.raw;

    pt_prev = NULL;

    rcu_read_lock();

#ifdef CONFIG_NET_CLS_ACT
    if (skb->tc_verd & TC_NCLS) {
        skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
        goto ncls;
    }
#endif
    //ptype_all保存了将要接收所有输入sb_buff的三层协议
    list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if (!ptype->dev || ptype->dev == skb->dev) {
            if (pt_prev) 
                //该函数就是调用协议的接收函数处理该skb包,进入第三层网络层处理
                ret = deliver_skb(skb, pt_prev);
            pt_prev = ptype;
        }
    }

#ifdef CONFIG_NET_CLS_ACT
    if (pt_prev) {
        ret = deliver_skb(skb, pt_prev);
        pt_prev = NULL; /* noone else should process this after*/
    } else {
        skb->tc_verd = SET_TC_OK2MUNGE(skb->tc_verd);
    }

    ret = ing_filter(skb);

    if (ret == TC_ACT_SHOT || (ret == TC_ACT_STOLEN)) {
        kfree_skb(skb);
        goto out;
    }

    skb->tc_verd = 0;
ncls:
#endif

    handle_diverter(skb);

    if (handle_bridge(&skb, &pt_prev, &ret))
        goto out;

    type = skb->protocol;
    //将帧传递给skb->protocol协议处理,ptype_base管理所有的正常的第3层协议
    list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {
        if (ptype->type == type &&
            (!ptype->dev || ptype->dev == skb->dev)) {
            if (pt_prev) 
                //该函数就是调用协议的接收函数处理该skb包,进入第三层网络层处理
                ret = deliver_skb(skb, pt_prev);

            pt_prev = ptype;
        }
    }

    if (pt_prev) {
        ret = pt_prev->func(skb, skb->dev, pt_prev);
    } else {
        kfree_skb(skb);
        /* Jamal, now you will not able to escape explaining
         * me how you were going to use this. :-)
         */
        ret = NET_RX_DROP;
    }

out:
    rcu_read_unlock();
    return ret;
}


static __inline__ int deliver_skb(struct sk_buff *skb,
                  struct packet_type *pt_prev)
{
    atomic_inc(&skb->users);
    return pt_prev->func(skb, skb->dev, pt_prev);//调用3层协议的处理函数

该函数主要就是调用第三层协议的接收函数处理该skb包,进入第三层网络层处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值