网卡驱动5-做一个与外界交互的虚拟网卡4(netif_receive_skb和非napi分析)

看了网上的一些对此函数的解析,有些比较旧了。我在这分析一下linux-3.0.8的代码。

netif_receive_skb()中有RPS,我们不看了,直接看__netif_receive_skb()。


static int __netif_receive_skb(struct sk_buff *skb)
{
        struct packet_type *ptype, *pt_prev;
        rx_handler_func_t *rx_handler;
        struct net_device *orig_dev;
        struct net_device *null_or_dev;
        bool deliver_exact = false;
        int ret = NET_RX_DROP;
        __be16 type;

        // netdev_tstamp_prequeue设置为0,表示可能有些帧的延时;默认为1。
//net_timestamp_check()是设置skb的tstamp值,此值是记录接收包的时间
        if (!netdev_tstamp_prequeue)
                net_timestamp_check(skb);

        trace_netif_receive_skb(skb);

        /* netpoll 需要处理这个帧的话,会调用netpoll_rx 处理*/
        if (netpoll_receive_skb(skb))
                return NET_RX_DROP;

        //给设备的接口序号赋值
        if (!skb->skb_iif)
                skb->skb_iif = skb->dev->ifindex;
        orig_dev = skb->dev;

        //下面说的复位主要是校准对应的指针
        skb_reset_network_header(skb);//skb->network_header = skb->data - skb->head;网络层头
        skb_reset_transport_header(skb);// skb->transport_header = skb->data;传输层
        skb_reset_mac_len(skb);//skb->mac_len = skb->network_header - skb->mac_header;链路层头长,mac地址长度

        pt_prev = NULL;

        rcu_read_lock();

another_round:

        __this_cpu_inc(softnet_data.processed);

        if (skb->protocol == cpu_to_be16(ETH_P_8021Q)) {//如果是vlan要用的
                skb = vlan_untag(skb);//vlan头数据的处理
                if (unlikely(!skb))
                        goto out;
        }

//…
       //之前有说协议类型时有个ALL,这是给每个sniffer发一个
        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);
/*
deliver_skb()就认为是pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
还记得我们之前注册的.func=vnic_rvc程序吧,不过我没有加到ptype_all.
*/
                        pt_prev = ptype;
                }
        }

//…
        //rx_handler是特殊接收过程,例如网桥就利用这个。
        rx_handler = rcu_dereference(skb->dev->rx_handler);
        if (rx_handler) { 
                if (pt_prev) {
                        ret = deliver_skb(skb, pt_prev, orig_dev);
                        pt_prev = NULL;
                }
                switch (rx_handler(&skb)) {
                case RX_HANDLER_CONSUMED://不做进一步处理
                        goto out;
                case RX_HANDLER_ANOTHER://skb->dev被改变,进行新一轮接收处理
                        goto another_round;
                case RX_HANDLER_EXACT://强制传送
                        deliver_exact = true;
                case RX_HANDLER_PASS://什么都不做,过时的SKB,好像没有rx_handler
                        break;
                default:
                        BUG();
                }
        }

        if (vlan_tx_tag_present(skb)) {
        //vlan的不看了
        }

        null_or_dev = deliver_exact ? skb->dev : NULL;

        //下面用到了存储我的ETH_P_VNIC的ptype_base[],就在此调用我的vnic_rcv.
        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_dev || ptype->dev == skb->dev ||
                     ptype->dev == orig_dev)) {
                        if (pt_prev)
                                ret = deliver_skb(skb, pt_prev, orig_dev);
                        pt_prev = ptype;
                }
        }
/*上面deliver_skb()可能多次被调用,是因为可以在多个驱动中add,例如我的vnic想接收ARP,我就加上
static struct packet_type arp _pack_type __read_mostly =
{
    .type = cpu_to_be16(ETH_P_ARP),
    .func = arp_rcv,
}; 
…
dev_add_pack(&arp _pack_type);
这样系统接到arp包会给vnic也发一份。
*/
        if (pt_prev) {
                ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
        } else {
                atomic_long_inc(&skb->dev->rx_dropped);
                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;
}

在NAPI机制下,从硬件中断到vnic的vnic_rcv过程基本说完了。

下面我们看非NAPI。

 

DM9000 100M网卡

dm9000_interrupt是dm9000的中断函数,当中断发生时进入这里,根据ISR寄存器获取中断状态,如果为接受中断进调用dm9000_rx();

先看看mac帧格式

网卡驱动5-做一个与外界交互的虚拟网卡4(netif_receive_skb和非napi分析)_第1张图片

主要看dst_mac、src_mac、len、type、data

我们的类型就是ETH_P_VNIC.最后有4个字节表示fcs。Preamble\SFD一般由网卡自动生成。驱动代码不需要管。Fcs在接收时是外部发送过来的。

下面看dm9000_rx();

错误判断的代码省去了。

		/* Move data from DM9000 */
//dev_alloc_skb()分配空间。+4就是指fcs。
		if (GoodPacket &&
		    ((skb = dev_alloc_skb(RxLen + 4)) != NULL)) {
//skb没有保存Preamble\SFD和length。所以dst_mac\src_mac和type总长度为14,所以为了16位对齐要移动2个字节。
			skb_reserve(skb, 2);
//长度为RxLen-4,即忽略掉FCS。
			rdptr = (u8 *) skb_put(skb, RxLen - 4);

			/* Read received packet from RX SRAM */

			(db->inblk)(db->io_data, rdptr, RxLen);//读数据
			dev->stats.rx_bytes += RxLen;//接收字节计数

			/* Pass to upper layer */
			skb->protocol = eth_type_trans(skb, dev);//上次说过 
			if (dev->features & NETIF_F_RXCSUM) {//要接收校验就校验,不细看了
				if ((((rxbyte & 0x1c) << 3) & rxbyte) == 0)
					skb->ip_summed = CHECKSUM_UNNECESSARY;
				else
					skb_checksum_none_assert(skb);
			}
			netif_rx(skb);//上报,下面看
			dev->stats.rx_packets++;//接收包计数

		}


你可以先看看这篇文章

http://simohayha.iteye.com/blog/720850

介绍了rps对多cpu进行负载均衡的原理。我们就不考虑这个rps了。

 

下面看netif_rx.

int netif_rx(struct sk_buff *skb)
{
        int ret;

        /* if netpoll wants it, pretend we never saw it */
        if (netpoll_rx(skb))//netpoll不管了
                return NET_RX_DROP;

        if (netdev_tstamp_prequeue)
                net_timestamp_check(skb);//接收包时间戳

        trace_netif_rx(skb);
#ifdef CONFIG_RPS
//RPS略去
#else
        {
                unsigned int qtail;
                ret = enqueue_to_backlog(skb, get_cpu(), &qtail);
                put_cpu();
        }
#endif
        return ret;
}
enqueue_to_backlog主要代码如下:(我们不考虑多cpu的情况了,如果你要看就看看上面的链接)
        struct softnet_data *sd;
        //…
        sd = &per_cpu(softnet_data, cpu);
/*
在此文件开头还定义了一个每cpu变量。
DEFINE_PER_CPU_ALIGNED(struct softnet_data, softnet_data);
struct softnet_data包涵了一个	struct napi_struct	backlog;它的初始化如下:
                sd->backlog.poll = process_backlog;
                sd->backlog.weight = weight_p;
                sd->backlog.gro_list = NULL;
                sd->backlog.gro_count = 0;
*/
        //…
        //如果队列还没有超过netdev_max_backlog
        if (skb_queue_len(&sd->input_pkt_queue) <= netdev_max_backlog) {
                //如果当前的队列长度不为空,说明input_pkt_queue有没处理skb,软中断已触发。就不用再触发,只要把skb加入input_pkt_queue就可以了。
                if (skb_queue_len(&sd->input_pkt_queue)) {
enqueue:
                        //skb加入input_pkt_queue中
                        __skb_queue_tail(&sd->input_pkt_queue, skb);
                        input_queue_tail_incr_save(sd, qtail);//rps有关
                        rps_unlock(sd);
                        local_irq_restore(flags);
                        return NET_RX_SUCCESS;
                }

                if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
                        if (!rps_ipi_queued(sd))
                                ____napi_schedule(sd, &sd->backlog);//这里会调度net_rx_action.所谓的非napi,还是有napi的存在
                }
                goto enqueue;//跳到上面的enqueue,可以看到那里把skb加入到了input_pkt_queue.
        }

net_rx_action之前说过了,现在我们看它调用的poll(),在此就是process_backlog

static int process_backlog(struct napi_struct *napi, int quota)
{
        int work = 0;
        struct softnet_data *sd = container_of(napi, struct softnet_data, backlog);

#ifdef CONFIG_RPS
//…
#endif
        napi->weight = weight_p;//64
        local_irq_disable();
        while (work < quota) {//在此你可以看看上一篇对千兆网卡中napi的介绍
                struct sk_buff *skb;
                unsigned int qlen;
                //__skb_dequeue()从队列中取出skb,在下面会把input_pkt_queue加入process_queue.
                while ((skb = __skb_dequeue(&sd->process_queue))) {
                        local_irq_enable();
                        __netif_receive_skb(skb);//上面说过,调用它,我们就结束了
                        local_irq_disable();
                        input_queue_head_incr(sd);//rps有关
                        if (++work >= quota) {//超过最大流量
                                local_irq_enable();
                                return work;
                        }
                }

                rps_lock(sd);
                qlen = skb_queue_len(&sd->input_pkt_queue);
                if (qlen)
                        //连接两个SKB列表和重新初始化清空列表,把skb加入到process_queue,并把input_pkt_queue清空
                        skb_queue_splice_tail_init(&sd->input_pkt_queue,
                                                   &sd->process_queue);

                if (qlen < quota - work) {
                //…省去了
                }
                rps_unlock(sd);
        }
        local_irq_enable();

        return work;
}




你可能感兴趣的:(linux,内核,网卡,linux内核)