// 1.netpoll的作用: // 用于让内核在网络和I/O子系统尚不能完整可用时,依然能发送和接收数据包,主要用于网络控制台和远程。 // 2.netpoll机制需要驱动程序的支持: // 使外部通过软件方式调用驱动程序的中断处理程序。 // 大部分poll_controller定义如下: // void this_controller(struct net_device *dev) // { // disable_dev_interrupt(dev); // call_interrupt_handler(dev->irq, dev); // enable_device_interrupt(dev); // } // 3.netpoll运行条件: // 3.1 以太网介质 // 3.2 本机l2地址,或者l2广播地址 // 3.3 l3协议为ip协议 // 3.4 ip数据包没有分片,且有效 // 3.5 l4协议为udp协议,且有效 // 4.netpoll在协议栈中的切入点为netif_receive_skb // 5.static atomic_t trapped; // trapped用于指示在netpoll_rx执行之后,netif_receive_skb是否丢弃该封包。 // 函数任务: // 1. 如果入口封包为arp封包,且开启了trapped模式,则处理入口arp,并返回。 // 2. 检查入口skb是否可以被处理。 // 3. 在关中断的情况下,遍历已经注册的netpoll控制块,向其传递skb。 // 调用路径:netif_receive_skb->netpoll_rx 1.1 int netpoll_rx(struct sk_buff *skb) { int proto, len, ulen; struct iphdr *iph;//ip头 struct udphdr *uh;//udp头 struct netpoll *np;//netpoll描述符 struct list_head *p; unsigned long flags; //netpoll只适用以太网设备 if (skb->dev->type != ARPHRD_ETHER) goto out; //trapped被设置,则由netpoll机制处理入口arp if (skb->protocol == __constant_htons(ETH_P_ARP) && atomic_read(&trapped)) { arp_reply(skb); return 1; } proto = ntohs(eth_hdr(skb)->h_proto); //l3协议需要是ip协议 if (proto != ETH_P_IP) goto out; //应该为本机l2地址,或广播地址 if (skb->pkt_type == PACKET_OTHERHOST) goto out; //skb没有被其他部分引用 if (skb_shared(skb)) goto out; iph = (struct iphdr *)skb->data; //skb->data - tail之间满足20字节的ip头 if (!pskb_may_pull(skb, sizeof(struct iphdr))) goto out; //skb->data - tail之间满足完整的ip头 if (iph->ihl < 5 || iph->version != 4) goto out; if (!pskb_may_pull(skb, iph->ihl*4)) goto out; //检查ip报头校验和 if (ip_fast_csum((u8 *)iph, iph->ihl) != 0) goto out; //检查ip数据包是否完整 len = ntohs(iph->tot_len); if (skb->len < len || len < iph->ihl*4) goto out; //l4协议需要时udp协议 if (iph->protocol != IPPROTO_UDP) goto out; //跨越ip报头以及ip选项 len -= iph->ihl*4; //udp头部 uh = (struct udphdr *)(((char *)iph) + iph->ihl*4); ulen = ntohs(uh->len); //udp报头中指定长度与实际长度不等 if (ulen != len) goto out; //计算udp校验和 if (checksum_udp(skb, uh, ulen, iph->saddr, iph->daddr) < 0) goto out; //关中断的情况下,向控制块传递skb spin_lock_irqsave(&rx_list_lock, flags); list_for_each(p, &rx_list) { //遍历已经注册的netpoll控制块 np = list_entry(p, struct netpoll, rx_list); //比较匹配条件 if (np->dev && np->dev != skb->dev) continue; if (np->local_ip && np->local_ip != ntohl(iph->daddr)) continue; if (np->remote_ip && np->remote_ip != ntohl(iph->saddr)) continue; if (np->local_port && np->local_port != ntohs(uh->dest)) continue; spin_unlock_irqrestore(&rx_list_lock, flags); //调用控制块的回调函数 if (np->rx_hook) np->rx_hook(np, ntohs(uh->source), (char *)(uh+1), ulen - sizeof(struct udphdr)); return 1; } spin_unlock_irqrestore(&rx_list_lock, flags); out: return atomic_read(&trapped);//如果trapped非零,则netif_receive_skb会在netpoll_rx返回后,直接释放skb,跳过后续的执行。 } // netpoll使用专用的skb缓存,对入口arp响应。 // 在缓存链表中,获取一个空闲的skb // 函数主要任务: // 1.为skb缓存链表补充skb // 2.如果缓存链表有空闲skb,则更新缓存的数量,返回skb // 3.如果无法获取空闲的skb // 3.1 调用netpoll_poll,加快网卡设备的数据接收,希望释放空闲skb // 4.重复3,直到有空闲skb // 调用路径:arp_reply->find_skb 2.1 static struct sk_buff * find_skb(struct netpoll *np, int len, int reserve) { int once = 1, count = 0; unsigned long flags; struct sk_buff *skb = NULL; zap_completion_queue(); repeat: //skb缓存不足 if (nr_skbs < MAX_SKBS) refill_skbs(); //分配skb skb = alloc_skb(len, GFP_ATOMIC); //分配skb失败 if (!skb) { //关中断 spin_lock_irqsave(&skb_list_lock, flags); //从skb缓存链表取一个skb skb = skbs; //如果获取skb缓存成功 if (skb) skbs = skb->next;//更新skb缓存链表 skb->next = NULL; nr_skbs--;//递减计数器 spin_unlock_irqrestore(&skb_list_lock, flags); } //如果获取skb依然失败 if(!skb) { //递增失败次数 count++; //失败次数已经达到最大 if (once && (count == 1000000)) { printk("out of netpoll skbs!\n"); once = 0; } //加快网卡设备上的数据接收,以此来释放更多的空闲skb netpoll_poll(np); goto repeat; } //设置skb使用者的个数 atomic_set(&skb->users, 1); //预留skb头空间 skb_reserve(skb, reserve); return skb; } // 补充空闲skb后备链表 // 调用路径arp_reply->find_skb->refill_skbs 2.3 static void refill_skbs(void) { struct sk_buff *skb; unsigned long flags; //关中断,获取skb_list_lock spin_lock_irqsave(&skb_list_lock, flags); //当前可用skb的个数小于最大的skb数 while (nr_skbs < MAX_SKBS) { //分配skb skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC); if (!skb) break; //将skb添加到链表 skb->next = skbs; skbs = skb; nr_skbs++;//递增skb个数计数器 } spin_unlock_irqrestore(&skb_list_lock, flags); } // 加快dev上的数据接收 // 调用路径arp_reply->find_skb->netpoll_poll 2.4 void netpoll_poll(struct netpoll *np) { //netpoll没有指定设备,或者设备已经停止,或者设备没有提供poll控制器,则返回 if(!np->dev || !netif_running(np->dev) || !np->dev->poll_controller) return; //软件方式调用设备中断处理例程 np->dev->poll_controller(np->dev); //调度设备napi,完成数据接收 if (np->dev->poll) poll_napi(np); zap_completion_queue(); } // poll_napi调用驱动程序的poll函数,模拟net_rx_action // 函数主要任务: // 1.获取该cpu的softnet_data // 2.通过softnet_data->poll_list获取有入口数据的dev // 3.通过dev->poll接收数据 // 调用路径arp_reply->find_skb->netpoll_poll->poll_napi 2.5 static void poll_napi(struct netpoll *np) { int budget = 16; unsigned long flags; struct softnet_data *queue; //关中断,获取锁 spin_lock_irqsave(&netpoll_poll_lock, flags); queue = &__get_cpu_var(softnet_data);//获取per-cpu变量 if (test_bit(__LINK_STATE_RX_SCHED, &np->dev->state) &&//当前设备被调度,说明有数据等待接收 !list_empty(&queue->poll_list)) {//本cpu的接收队列上有等待poll的设备 np->dev->netpoll_rx |= NETPOLL_RX_DROP;// atomic_inc(&trapped);//递增trapped,使驱动程序的poll->netif_receive_skb->netpoll_rx之后,netif_receive_skb直接丢弃skb。 np->dev->poll(np->dev, &budget);//驱动程序提供的poll函数 atomic_dec(&trapped); np->dev->netpoll_rx &= ~NETPOLL_RX_DROP; } spin_unlock_irqrestore(&netpoll_poll_lock, flags); }