網絡子係統8_netpoll機製
// 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); }
最後更新:2017-04-03 15:22:09