//这是向内核提供一个自定义协议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使用)
原文:http://blog.csdn.net/xxxxxlllllxl/article/details/19042463