网卡驱动4-做一个与外界交互的虚拟网卡2(调用真实网卡接收数据以及napi使用)

//这是向内核提供一个自定义协议VNIC的接收函数
//vnic_rcv是接受vnic包的处理函数

static int vnic_rcv(struct sk_buff *skb, structnet_device *dev, struct packet_type *ptype, struct net_device *orig_dev)

{

    struct net_device *vnic_dev;

    struct vnic_pcpu_stats *rx_stats;

   u16 vnic_id =skb->data[1];//这部就相当于解析我们自己的头,还记得上次的协议结构吧,到这里data[0]是w,data[1]是我的vnic号。

 

#if 1

    u16 i;

    for (i = 0; i < skb->mac_len; i++)

    {

        printk("%x", skb->mac_header[i]);

    }

 

    printk("\n");

#endif

 

    vnic_dev =vnic_group_get_device(vnic_grp, vnic_id);//根据id获取设备结构体

    if (vnic_dev == NULL)

    {

        return -1;

    }

 

    //检查skb的应用计数是否大于,大于意味着内核的其他部分拥有对

    //该缓冲区的引用。如果大于,会自己建立一份缓冲区副本。

    skb =skb_share_check(skb, GFP_ATOMIC);

    if (unlikely(!skb))

    {

        return false;

    }

 

    skb->dev =vnic_dev;

    //PACKET_OTHERHOST表示L2目的地址和接收接口的地址不同

    //通常会被丢弃掉。如果网卡进入混杂模式,会接收所以包

    //这里我们就要自己比较一下。

    if (skb->pkt_type == PACKET_OTHERHOST) {

        if (!compare_ether_addr(eth_hdr(skb)->h_dest,vnic_dev->dev_addr))

            skb->pkt_type= PACKET_HOST;

    }

 

    //记录状态

    rx_stats = (struct vnic_pcpu_stats*)this_cpu_ptr(vnic_dev_info(vnic_dev)->vnic_pcpu_stats);

   

    u64_stats_update_begin(&rx_stats->syncp);

    rx_stats->rx_packets++;

    rx_stats->rx_bytes+= skb->len;

    if (skb->pkt_type == PACKET_MULTICAST)

        rx_stats->rx_multicast++;

    u64_stats_update_end(&rx_stats->syncp);

 

    return 0;

}

 

static struct packet_typevnic_pack_type __read_mostly =

{

    .type =cpu_to_be16(ETH_P_VNIC),

    .func = vnic_rcv,

};

再提一下,内核版本linux-3.0.8

struct packet_type在上上期就说过了。现在我们重点是看看内核接受到数据后到vnic_rcv的过程。接受的起点基本是从硬件中断开始。

之前有个表提到了netif_rx(),一般在100M或一下网卡中用这个。

我当前的1000M网卡用的是napi。

简介一下:

在NAPI中,中断收到数据包后调用__napi_schedule调度软中断,然后软中断处理函数中会调用注册的poll回掉函数中调用netif_receive_skb将数据包发送到3层,没有进行任何的软中断负载均衡。

在非NAPI中,中断收到数据包后调用netif_rx,这个函数会将数据包保存到input_pkt_queue,然后调度软中断,这里为了兼 容NAPI的驱动,他的poll方法默认是process_backlog,最终这个函数会从input_pkt_queue中取得数据包然后发送到3 层。

 

应该还记得网卡驱动第一期的snull吧,里面简单实现了这两种方法。在此我们看真实网卡中的应用。

NAPI:

首先说一下它出现的原因:

接收通过中断来进行,当系统有一个处理大流量的高速接口时, 会一直有更多的报文来处理. 在这种情况下没有必要中断处理器; 时常从接口收集新报文是足够的.

使用的条件:

接口必须能够存储几个报文( 要么在接口卡上, 要么在内存内 DMA 环).

接口应当能够禁止中断来接收报文, 却可以继续因成功发送或其他事件而中断.

 

//stmmac 1000M网卡(我当然使用网卡)

//stmmac_interrupt是它的中断函数。假设是网卡0

    if (int_status & TNK_INTR_STAT_GMAC0_DATA)// 如果中断状态为dma0中断

         stmmac_dma_interrupt(priv);

 

static void stmmac_dma_interrupt(structstmmac_priv *priv)

{

   int status;

 

   status =

       priv->hw->dma->dma_interrupt(priv->dma_ioaddr, priv->dma_channel,

                                    &priv->xstats);

//上面函数会判断dma status寄存器的第6位(ri接收中断)和第0位(ti接收中断)是否为1,有一个为返回handle_tx_rx。

       if (likely(status == handle_tx_rx))

                _stmmac_schedule(priv);

//…

static void _stmmac_schedule(structstmmac_priv *priv)

{

       if (likely(stmmac_has_work(priv))) {

                stmmac_disable_irq(priv);//先关闭了中断

               napi_schedule(&priv->napi);

       }

}

//此网卡napi的初始化

netif_napi_add(dev, &priv->napi,stmmac_poll, 64);

void netif_napi_add(structnet_device *dev, struct napi_struct *napi,

            int(*poll)(struct napi_struct *, int), int weight);

weight在下面说,先看napi_schedule()。直接看这个

static inline int napi_schedule_prep(structnapi_struct *n)

{

    return !napi_disable_pending(n) &&

        !test_and_set_bit(NAPI_STATE_SCHED,&n->state);

}

 

static inline void napi_schedule(structnapi_struct *n)

{

    if (napi_schedule_prep(n))

        __napi_schedule(n);

}

__napi_schedule基本等价为____napi_schedule

static inline void____napi_schedule(struct softnet_data *sd,

                                     structnapi_struct *napi)

{

   list_add_tail(&napi->poll_list, &sd->poll_list);

   __raise_softirq_irqoff(NET_RX_SOFTIRQ);//唤醒软中断

}


在net/core/dev.c中

接受对应的中断注册

open_softirq(NET_RX_SOFTIRQ,net_rx_action);

还有一个net_tx_action,这个是唤醒队列发送的,上次没细说,现在也不说了。呵呵!

在此文件开头还定义了一个每cpu变量。

DEFINE_PER_CPU_ALIGNED(struct softnet_data,softnet_data);

struct softnet_data包涵了一个   structnapi_struct  backlog;它的初始化如下:

               sd->backlog.poll = process_backlog;

                sd->backlog.weight = weight_p;

               sd->backlog.gro_list = NULL;

               sd->backlog.gro_count = 0;

先记住backlog,这里不用。

 

现在我们跳到net_rx_action()

static void net_rx_action(structsoftirq_action *h)

{

       struct softnet_data *sd = &__get_cpu_var(softnet_data);//获取每cpu量

       unsigned long time_limit = jiffies + 2;//限制时间

       int budget = netdev_budget;// int netdev_budget__read_mostly = 300;处理包的预算值。

       void *have;

 

       local_irq_disable();//关闭中断

 

       // 检查POLL队列(poll_list)上是否有设备在准备等待轮询取得数据

        while(!list_empty(&sd->poll_list)) {

                struct napi_struct *n;

                int work, weight;

 

                /*

                在软中断中,我们允许运行最多2个jiffies。或者超过预算值。这里主要是怕软中断占用太多时间

                 */

                if (unlikely(budget <= 0 ||time_after(jiffies, time_limit)))

                        goto softnet_break;//看下面它还会重触发软中断

 

                local_irq_enable();//中断开启

                /*

                  从公共的 softnet_data 数据结构中的轮循队列上获得等待轮循的设备结构,虽然上面开启中断,但是中断只从list尾端加入,我们从头去。

                 */

                n =list_first_entry(&sd->poll_list, struct napi_struct, poll_list);

 

                have = netpoll_poll_lock(n);

 

                weight = n->weight;//这是我们驱动中赋值的,是64.

                /*weight是有多少流量可以从接口收到, 当资源紧张时.

如何设置 weight 参数没有严格的规则; 依照惯例, 10 MBps

 以太网接口设置 weight 为 16, 而快一些的接口使用 64 ,所

 以我的1000M,选择了64

*/           

                work = 0;

/*

                NAPI_STATE_SCHED判断是为了防止与netpoll冲突

                 */

                if (test_bit(NAPI_STATE_SCHED,&n->state)) {

                        work = n->poll(n,weight);//work由此返回,下面看poll()

                        trace_napi_poll(n);

                }

 

                WARN_ON_ONCE(work > weight);

 

                budget -= work;//减去

 

                local_irq_disable();

 

                /*

                 如果weight等于work就通知完成。接收可能没结束

                 */

                if (unlikely(work == weight)) {

                        // test_bit(NAPI_STATE_DISABLE, &n->state);

                        if (unlikely(napi_disable_pending(n))){

                               local_irq_enable();

                               napi_complete(n);//完成

                               local_irq_disable();

                        } else

                                list_move_tail(&n->poll_list,&sd->poll_list);

                }

 

                netpoll_poll_unlock(have);

       }

//…省去一些

       return;

 

softnet_break:

       sd->time_squeeze++;

       __raise_softirq_irqoff(NET_RX_SOFTIRQ);

       goto out;

}

 

下面本网卡的poll,看几句关键的

       //budget就是传入的weight

       work_done = stmmac_rx(priv, budget);//硬件接收

 

       if (work_done < budget) {//接收结束

                stm_proc.polls_done++;

                napi_complete(napi);//完成

                stmmac_enable_irq(priv);//启动中断

       }

 

可以看出napi只是接收事件队列。没有接收的数据。

在看stmmac_rx的,也看几句关键的:

                        skb->protocol =eth_type_trans(skb, priv->dev); //这个不细看了,除了取出skb中的类型外,还会对多播、混杂模式等进行判断

                        skb->dev =priv->dev;

 

                        if (unlikely(status ==csum_none)) {

                                /* always forthe old mac 10/100 */

                               skb_checksum_none_assert(skb);

                               netif_receive_skb(skb);//上报内核

                        } else {

                               skb->ip_summed = CHECKSUM_UNNECESSARY;

                               napi_gro_receive(&priv->napi, skb);//这个会根据summ去处理skb,如果正常还是会调用netif_receive_skb().

                        }


非napi和netif_receive_skb()到vnic_rcv()过程,我们下次再说。

网卡驱动4-做一个与外界交互的虚拟网卡2(调用真实网卡接收数据以及napi使用)

上一篇:【Java】对象内存解析


下一篇:C#判断字符串A是否包含字符串B--by winter