看了网上的一些对此函数的解析,有些比较旧了。我在这分析一下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帧格式
主要看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; }