//这是向内核提供一个自定义协议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()过程,我们下次再说。