閱讀992 返回首頁    go 阿裏雲 go 技術社區[雲棲]


網絡子係統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

  上一篇:go 核心編程隨筆1
  下一篇:go Java麵向對象基礎--代碼塊