linux4.4的虚拟网卡驱动,网卡驱动之与外界交互的虚拟网卡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帧格式

b9449f3d784153a206b199a0e335ce83.png

主要看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_structbacklog;它的初始化如下:

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;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自动控制节水灌溉技术的高低代表着农业现代化的发展状况,灌溉系统自动化水平较低是制约我国高效农业发展的主要原因。本文就此问题研究了单片机控制的滴灌节水灌溉系统,该系统可对不同土壤的湿度进行监控,并按照作物对土壤湿度的要求进行适时、适量灌水,其核心是单片机和PC机构成的控制部分,主要对土壤湿度与灌水量之间的关系、灌溉控制技术及设备系统的硬件、软件编程各个部分进行了深入的研究。 单片机控制部分采用上下位机的形式。下位机硬件部分选用AT89C51单片机为核心,主要由土壤湿度传感器,信号处理电路,显示电路,输出控制电路,故障报警电路等组成,软件选用汇编语言编程。上位机选用586型以上PC机,通过MAX232芯片实现同下位机的电平转换功能,上下位机之间通过串行通信方式进行数据的双向传输,软件选用VB高级编程语言以建立友好的人机界面。系统主要具有以下功能:可在PC机提供的人机对话界面上设置作物要求的土壤湿度相关参数;单片机可将土壤湿度传感器检测到的土壤湿度模拟量转换成数字量,显示于LED显示器上,同时单片机可采用串行通信方式将此湿度值传输到PC机上;PC机通过其内设程序计算出所需的灌水量和灌水时间,且显示于界面上,并将有关的灌水信息反馈给单片机,若需灌水,则单片机系统启动鸣音报警,发出灌水信号,并经放大驱动设备,开启电磁阀进行倒计时定时灌水,若不需灌水,即PC机上显示的灌水量和灌水时间均为0,系统不进行灌水。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值