1. 程式人生 > >Linux核心--網路棧實現分析(十)--網路層之IP協議(下)

Linux核心--網路棧實現分析(十)--網路層之IP協議(下)

本文分析基於Linux Kernel 1.2.13

作者:閆明

注:標題中的”(上)“,”(下)“表示分析過程基於資料包的傳遞方向:”(上)“表示分析是從底層向上分析、”(下)“表示分析是從上向下分析。

上篇博文分析傳輸層最終會呼叫函式ip_queue_xmit()函式,將傳送資料的任務交給網路層,下面就分析了下該函式:

該函式的主要函式呼叫關係圖如下:


/*
 * Queues a packet to be sent, and starts the transmitter
 * if necessary.  if free = 1 then we free the block after
 * transmit, otherwise we don't. If free==2 we not only
 * free the block but also don't assign a new ip seq number.
 * This routine also needs to put in the total length,
 * and compute the checksum
 */

void ip_queue_xmit(struct sock *sk, //傳送資料的佇列所對應的sock結構
					  struct device *dev,//傳送該資料包的網絡卡裝置
	      			  struct sk_buff *skb,//封裝好的sk_buff結構,要傳送的資料在該結構中
	      			  int free)//主要配合TCP協議使用,用於資料包的重發,UDP等協議呼叫是free=1
{
	struct iphdr *iph;//IP資料報首部指標
	unsigned char *ptr;

	/* Sanity check */
	if (dev == NULL)
	{
		printk("IP: ip_queue_xmit dev = NULL\n");
		return;
	}

	IS_SKB(skb);

	/*
	 *	Do some book-keeping in the packet for later
	 */


	skb->dev = dev;//進一步完整sk_buff的相應欄位
	skb->when = jiffies;//用於TCP協議的超時重傳

	/*
	 *	Find the IP header and set the length. This is bad
	 *	but once we get the skb data handling code in the
	 *	hardware will push its header sensibly and we will
	 *	set skb->ip_hdr to avoid this mess and the fixed
	 *	header length problem
	 */

	ptr = skb->data;//指標指向sk_buff中的資料部分
	ptr += dev->hard_header_len;//hard_header_len為硬體首部長度,在net_init.c的函式eth_setup()函式中設定的,dev->hard_header_len = ETH_HLEN; 乙太網首部長度為14
	iph = (struct iphdr *)ptr;//prt已經指向IP資料包的首部
	skb->ip_hdr = iph;
	iph->tot_len = ntohs(skb->len-dev->hard_header_len);//計算IP資料報的總長度

#ifdef CONFIG_IP_FIREWALL
	if(ip_fw_chk(iph, dev, ip_fw_blk_chain, ip_fw_blk_policy, 0) != 1)
		/* just don't send this packet */
		return;
#endif	

	/*
	 *	No reassigning numbers to fragments...
	 */

	if(free!=2)
		iph->id      = htons(ip_id_count++);
	else
		free=1;

	/* All buffers without an owner socket get freed */
	if (sk == NULL)
		free = 1;

	skb->free = free;//設定skb的free值,free=1,傳送後立即釋放;free=2,不但釋放快取,而且不分配新的序列號

	/*
	 *	Do we need to fragment. Again this is inefficient.
	 *	We need to somehow lock the original buffer and use
	 *	bits of it.
	 */
	//資料幀中的資料部分必須小於等於MTU
	if(skb->len > dev->mtu + dev->hard_header_len)//傳送的資料長度大於資料幀的資料部分和幀首部之和,則需要分片
	{
		ip_fragment(sk,skb,dev,0);//對資料報分片後繼續呼叫ip _queue_xmit()函式傳送資料
		IS_SKB(skb);
		kfree_skb(skb,FREE_WRITE);
		return;
	}

	/*
	 *	Add an IP checksum
	 */

	ip_send_check(iph);//IP資料報首部檢查

	/*
	 *	Print the frame when debugging
	 */

	/*
	 *	More debugging. You cannot queue a packet already on a list
	 *	Spot this and moan loudly.
	 */
	if (skb->next != NULL)//說明該資料包仍然存在於某個快取佇列
	{
		printk("ip_queue_xmit: next != NULL\n");
		skb_unlink(skb);//將其從快取連結串列中刪除,否則可能導致核心錯誤
	}

	/*
	 *	If a sender wishes the packet to remain unfreed
	 *	we add it to his send queue. This arguably belongs
	 *	in the TCP level since nobody else uses it. BUT
	 *	remember IPng might change all the rules.
	 */

	if (!free)//free=0
	{
		unsigned long flags;
		/* The socket now has more outstanding blocks */

		sk->packets_out++;

		/* Protect the list for a moment */
		save_flags(flags);
		cli();

		if (skb->link3 != NULL)//link3指向資料報道呃重發佇列
		{
			printk("ip.c: link3 != NULL\n");
			skb->link3 = NULL;
		}
		//sk中send_tail和send_head是使用者快取的單向連結串列表尾和表頭
		if (sk->send_head == NULL)
		{
			sk->send_tail = skb;
			sk->send_head = skb;
		}
		else
		{
			sk->send_tail->link3 = skb;//link3指標用於資料包的連線
			sk->send_tail = skb;
		}
		/* skb->link3 is NULL */

		/* Interrupt restore */
		restore_flags(flags);
	}
	else
		/* Remember who owns the buffer */
		skb->sk = sk;

	/*
	 *	If the indicated interface is up and running, send the packet.
	 */
	 
	ip_statistics.IpOutRequests++;
#ifdef CONFIG_IP_ACCT
	ip_acct_cnt(iph,dev, ip_acct_chain);
#endif	
	
#ifdef CONFIG_IP_MULTICAST	//這部分是IP資料報的多播處理

	/*
	 *	Multicasts are looped back for other local users
	 */
	 
	.......................................
#endif
	if((dev->flags&IFF_BROADCAST) && iph->daddr==dev->pa_brdaddr && !(dev->flags&IFF_LOOPBACK))//廣播資料包的處理
		ip_loopback(dev,skb);
		
	if (dev->flags & IFF_UP)//裝置狀態正常
	{
		/*
		 *	If we have an owner use its priority setting,
		 *	otherwise use NORMAL
		 */
		//呼叫裝置介面層函式傳送資料: dev_queue_xmit()函式
		if (sk != NULL)
		{
			dev_queue_xmit(skb, dev, sk->priority);
		}
		else
		{
			dev_queue_xmit(skb, dev, SOPRI_NORMAL);
		}
	}
	else//裝置狀態不正常
	{
		ip_statistics.IpOutDiscards++;
		if (free)
			kfree_skb(skb, FREE_WRITE);
	}
}

這個函式中對長度過長的資料包進行了分片,ip_fragment()函式,該函式沒有詳細分析。
void ip_fragment(struct sock *sk, struct sk_buff *skb, struct device *dev, int is_frag)
{
	struct iphdr *iph;
	unsigned char *raw;
	unsigned char *ptr;
	struct sk_buff *skb2;
	int left, mtu, hlen, len;
	int offset;
	unsigned long flags;

	/*
	 *	Point into the IP datagram header.
	 */

	raw = skb->data;
	iph = (struct iphdr *) (raw + dev->hard_header_len);

	skb->ip_hdr = iph;

	/*
	 *	Setup starting values.
	 */

	hlen = (iph->ihl * sizeof(unsigned long));
	left = ntohs(iph->tot_len) - hlen;	/* Space per frame */
	hlen += dev->hard_header_len;		/* Total header size */
	mtu = (dev->mtu - hlen);		/* Size of data space */
	ptr = (raw + hlen);			/* Where to start from */

	/*
	 *	Check for any "DF" flag. [DF means do not fragment]
	 */

	if (ntohs(iph->frag_off) & IP_DF)
	{
		/*
		 *	Reply giving the MTU of the failed hop.
		 */
		ip_statistics.IpFragFails++;
		icmp_send(skb,ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, dev->mtu, dev);
		return;
	}

	/*
	 *	The protocol doesn't seem to say what to do in the case that the
	 *	frame + options doesn't fit the mtu. As it used to fall down dead
	 *	in this case we were fortunate it didn't happen
	 */

	if(mtu<8)
	{
		/* It's wrong but it's better than nothing */
		icmp_send(skb,ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED,dev->mtu, dev);
		ip_statistics.IpFragFails++;
		return;
	}

	/*
	 *	Fragment the datagram.
	 */

	/*
	 *	The initial offset is 0 for a complete frame. When
	 *	fragmenting fragments it's wherever this one starts.
	 */

	if (is_frag & 2)
		offset = (ntohs(iph->frag_off) & 0x1fff) << 3;
	else
		offset = 0;


	/*
	 *	Keep copying data until we run out.
	 */

	while(left > 0)
	{
		len = left;
		/* IF: it doesn't fit, use 'mtu' - the data space left */
		if (len > mtu)
			len = mtu;
		/* IF: we are not sending upto and including the packet end
		   then align the next start on an eight byte boundary */
		if (len < left)
		{
			len/=8;
			len*=8;
		}
		/*
		 *	Allocate buffer.
		 */

		if ((skb2 = alloc_skb(len + hlen,GFP_ATOMIC)) == NULL)
		{
			printk("IP: frag: no memory for new fragment!\n");
			ip_statistics.IpFragFails++;
			return;
		}

		/*
		 *	Set up data on packet
		 */

		skb2->arp = skb->arp;
		if(skb->free==0)
			printk("IP fragmenter: BUG free!=1 in fragmenter\n");
		skb2->free = 1;
		skb2->len = len + hlen;
		skb2->h.raw=(char *) skb2->data;
		/*
		 *	Charge the memory for the fragment to any owner
		 *	it might possess
		 */

		save_flags(flags);
		if (sk)
		{
			cli();
			sk->wmem_alloc += skb2->mem_len;
			skb2->sk=sk;
		}
		restore_flags(flags);
		skb2->raddr = skb->raddr;	/* For rebuild_header - must be here */

		/*
		 *	Copy the packet header into the new buffer.
		 */

		memcpy(skb2->h.raw, raw, hlen);

		/*
		 *	Copy a block of the IP datagram.
		 */
		memcpy(skb2->h.raw + hlen, ptr, len);
		left -= len;

		skb2->h.raw+=dev->hard_header_len;

		/*
		 *	Fill in the new header fields.
		 */
		iph = (struct iphdr *)(skb2->h.raw/*+dev->hard_header_len*/);
		iph->frag_off = htons((offset >> 3));
		/*
		 *	Added AC : If we are fragmenting a fragment thats not the
		 *		   last fragment then keep MF on each bit
		 */
		if (left > 0 || (is_frag & 1))
			iph->frag_off |= htons(IP_MF);
		ptr += len;
		offset += len;

		/*
		 *	Put this fragment into the sending queue.
		 */

		ip_statistics.IpFragCreates++;

		ip_queue_xmit(sk, dev, skb2, 2);//還是呼叫ip_queue_xmit()函式來發送分片後的資料
	}
	ip_statistics.IpFragOKs++;
}

網路層的傳送函式呼叫了裝置介面層,相當於網路模型的鏈路層的傳送函式dev_queue_xmit()

該函式的呼叫關係如下:


/*
 *	Send (or queue for sending) a packet. 
 *
 *	IMPORTANT: When this is called to resend frames. The caller MUST
 *	already have locked the sk_buff. Apart from that we do the
 *	rest of the magic.
 */

void dev_queue_xmit(struct sk_buff *skb, struct device *dev, int pri)
{
	unsigned long flags;
	int nitcount;
	struct packet_type *ptype;
	int where = 0;		/* used to say if the packet should go	*/
				/* at the front or the back of the	*/
				/* queue - front is a retransmit try	*/
				/* where=0 表示是剛從上層傳遞的新資料包;where=1 表示從硬體佇列中取出的資料包*/

	if (dev == NULL) 
	{
		printk("dev.c: dev_queue_xmit: dev = NULL\n");
		return;
	}
	
	if(pri>=0 && !skb_device_locked(skb))//鎖定該skb再進行操作,避免造成核心的不一致情況
		skb_device_lock(skb);	/* Shove a lock on the frame */
#ifdef CONFIG_SLAVE_BALANCING
	save_flags(flags);
	cli();
	if(dev->slave!=NULL && dev->slave->pkt_queue < dev->pkt_queue &&
				(dev->slave->flags & IFF_UP))
		dev=dev->slave;
	restore_flags(flags);
#endif		
#ifdef CONFIG_SKB_CHECK 
	IS_SKB(skb);
#endif    
	skb->dev = dev;

	/*
	 *	This just eliminates some race conditions, but not all... 
	 */

	if (skb->next != NULL) //這種條件似乎永遠不能成立,因為傳送資料包前,資料包已經從快取佇列摘下
	{//以防核心程式碼有BUG
		/*
		 *	Make sure we haven't missed an interrupt. 
		 */
		printk("dev_queue_xmit: worked around a missed interrupt\n");
		start_bh_atomic();
		dev->hard_start_xmit(NULL, dev);
		end_bh_atomic();
		return;
  	}

	/*
	 *	Negative priority is used to flag a frame that is being pulled from the
	 *	queue front as a retransmit attempt. It therefore goes back on the queue
	 *	start on a failure.
	 */
	 
  	if (pri < 0) //優先順序小於0表示是從硬體佇列中取出的資料包
  	{
		pri = -pri-1;
		where = 1;
  	}

	if (pri >= DEV_NUMBUFFS) 
	{
		printk("bad priority in dev_queue_xmit.\n");
		pri = 1;
	}

	/*
	 *	If the address has not been resolved. Call the device header rebuilder.
	 *	This can cover all protocols and technically not just ARP either.
	 */
	 
	if (!skb->arp && dev->rebuild_header(skb->data, dev, skb->raddr, skb)) {//用於ARP協議,並重建MAC幀首部
		return;
	}

	save_flags(flags);
	cli();	
	if (!where) {//表示是新資料包,需要將其加入裝置佇列中
#ifdef CONFIG_SLAVE_BALANCING	
		skb->in_dev_queue=1;//該資料包在裝置佇列
#endif		
		skb_queue_tail(dev->buffs + pri,skb);//將傳送資料包加入硬體佇列
		skb_device_unlock(skb);		/* Buffer is on the device queue and can be freed safely */
		skb = skb_dequeue(dev->buffs + pri);//從硬體佇列中取出一個數據包
		skb_device_lock(skb);		/* New buffer needs locking down */
#ifdef CONFIG_SLAVE_BALANCING		
		skb->in_dev_queue=0;
#endif		
	}
	restore_flags(flags);

	/* copy outgoing packets to any sniffer packet handlers */
	if(!where)//對於新的資料包,則遍歷網路層協議佇列,核心支援混雜模式
	{
		for (nitcount= dev_nit, ptype = ptype_base; nitcount > 0 && ptype != NULL; ptype = ptype->next) 
		{
			/* Never send packets back to the socket
			 * they originated from - MvS (
[email protected]
) */ if (ptype->type == htons(ETH_P_ALL) && (ptype->dev == dev || !ptype->dev) && ((struct sock *)ptype->data != skb->sk)) { struct sk_buff *skb2; if ((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL) break; /* * The protocol knows this has (for other paths) been taken off * and adds it back. */ skb2->len-=skb->dev->hard_header_len; ptype->func(skb2, skb->dev, ptype);//IP層函式對應func為ip_rcv(),將傳送的資料回送一份給對應的網路層協議 nitcount--;//用於及時退出迴圈 } } } start_bh_atomic();//開始原子操作 if (dev->hard_start_xmit(skb, dev) == 0) {//呼叫硬體的傳送函式傳送資料 end_bh_atomic();//結束原子操作 /* * Packet is now solely the responsibility of the driver */ return;//到這裡說明資料包成功傳送 } //資料包沒有成功傳送,進行處理,將資料包從新加入硬體佇列 end_bh_atomic(); /* * Transmission failed, put skb back into a list. Once on the list it's safe and * no longer device locked (it can be freed safely from the device queue) */ cli(); #ifdef CONFIG_SLAVE_BALANCING skb->in_dev_queue=1; dev->pkt_queue++; #endif skb_device_unlock(skb);//對SKB解鎖 skb_queue_head(dev->buffs + pri,skb);//這次採用頭插法插入硬體傳送佇列 restore_flags(flags); }

具體的硬體傳送函式dev->hard_start_xmit的實現將做下篇博文中分析。

相關推薦

Linux核心--網路實現分析--網路IP協議

本文分析基於Linux Kernel 1.2.13作者:閆明注:標題中的”(上)“,”(下)“表示分析過程基於資料包的傳遞方向:”(上)“表示分析是從底層向上分析、”(下)“表示分析是從上向下分析。上篇博文分析傳輸層最終會呼叫函式ip_queue_xmit()函式,將傳送資料

Linux核心--網路實現分析--資料包的傳遞過程

本文分析基於Linux Kernel 1.2.13作者:閆明注:標題中的”(上)“,”(下)“表示分析過程基於資料包的傳遞方向:”(上)“表示分析是從底層向上分析、”(下)“表示分析是從上向下分析。上一篇博文中我們從巨集觀上分析了Linux核心中網路棧的初始化過程,這裡我們再

網路IP協議

TCP/IP的核心是網際網路層,這一層類比於OSI參考模型中的第三層,即網路層。該層成熟的技術主要有IP和ICMP兩種協議。 IP協議共有兩個版本,分別為IPv4和IPv6. 網路層的主要作用是實現終端節點之間的通訊,這種端點之間的通訊業稱為點對點通訊(end to end

Wireshark實戰分析IP協議

(1)什麼是IP資料報         TCP/IP協議定義了一個在區域網上傳輸的包,稱為IP資料報(IP Datagram)。IP資料報由首部和資料兩部分組成,首部部分包括版本,長度,IP地址等資訊。資料部分一般用來傳輸其他的協議,如TCP,UDP和ICMP協議等。 (2

Linux核心設計與實現》讀書筆記- 程序地址空間(kernel 2.6.32.60)

程序地址空間也就是每個程序所使用的記憶體,核心對程序地址空間的管理,也就是對使用者態程式的記憶體管理。 主要內容: 地址空間(mm_struct) 虛擬記憶體區域(VMA) 地址空間和頁表 1. 地址空間(mm_struct) 地址空間就是每個程序所能訪問的記憶體地址範圍。 這個地址

Linux核心設計與實現》讀書筆記- 可移植性

linux核心的移植性非常好, 目前的核心也支援非常多的體系結構(有20多個). 但是剛開始時, linux也只支援 intel i386 架構, 從 v1.2版開始支援 Digital Alpha, Intel x86, MIPS和SPARC(雖然支援的還不是很完善). 從 v2.0版本開始加入了對 M

Linux核心設計與實現》讀書筆記- 核心除錯

核心除錯的難點在於它不能像使用者態程式除錯那樣打斷點,隨時暫停檢視各個變數的狀態。 也不能像使用者態程式那樣崩潰後迅速的重啟,恢復初始狀態。 使用者態程式和核心互動,使用者態程式的各種狀態,錯誤等可以由核心來捕獲並顯示。 而核心是直接和硬體互動的,核心出錯之後整個系統就無法正常運行了,所以要想熟練的

Linux核心設計與實現》讀書筆記- 塊I/O

最近太忙,居然過了2個月才更新第十四章。。。。 主要內容: 塊裝置簡介 核心訪問塊裝置的方法 核心I/O排程程式 1. 塊裝置簡介 I/O裝置主要有2類: 字元裝置:只能順序讀寫裝置中的內容,比如 串列埠裝置,鍵盤 塊裝置:能夠隨機讀寫裝置中的內容,比如 硬碟,U盤 字元

Linux核心設計與實現》讀書筆記- 裝置與模組

本章主要討論與linux的裝置驅動和裝置管理的相關的4個核心成分,裝置型別,模組,核心物件,sysfs。 主要內容: 裝置型別 核心模組 核心物件 sysfs 總結 1. 裝置型別 linux中主要由3種類型的裝置,分別是: 裝置型別 代表裝置

Linux核心設計與實現》讀書筆記- 補丁, 開發和社群

linux最吸引我的地方之一就是它擁有一個高手雲集的社群, 還有就是如果能=為linux核心中貢獻程式碼, 一定是一件令人自豪的事情. 下面主要總結一些和貢獻程式碼相關的主要內容. 加入社群 編碼風格 提交補丁 總結 1. 加入社群 如果想為linux貢獻程式碼, 那麼加入linux

Linux核心設計與實現》讀書筆記- 頁快取記憶體和頁回寫

好久沒有更新了。。。 主要內容: 快取簡介 頁快取記憶體 頁回寫 1. 快取簡介 在程式設計中,快取是很常見也很有效的一種提高程式效能的機制。 linux核心也不例外,為了提高I/O效能,也引入了快取機制,即將一部分磁碟上的資料快取到記憶體中。 1.1 原理 之所以通過快取能

Linux核心設計與實現1--核心開發的特點

1. 核心程式設計時既不能訪問C庫也不能訪問標準的C標頭檔案        其中的原因有很多種。其一,C標準庫的很多函式實現都是基於核心實現的,這核心編譯的時候都還沒有核心,所以就不存在這些函式,這個就是先有雞還是先有蛋這個悖論。其二,其主主要的的

Linux核心設計與實現 總結筆記第二章

一、Linux核心中的一些基本概念 核心空間:核心可獨立於普通應用程式,它一般處於系統態,擁有受保護的記憶體空間和訪問硬體裝置的所有許可權。這種系統態和被保護起來的記憶體空間,稱為核心空間。 程序上下文:當應用程式執行一條系統呼叫,通過系統呼叫執行在核心空間,而核心被稱為執行在程序上下文中。  

Linux核心設計與實現 總結筆記第五章系統呼叫

系統呼叫 核心提供了使用者程序和核心互動的介面,使得應用程式可以受限制的訪問硬體裝置。 提供這些介面主要是為了保證系統穩定可靠,避免應用程式恣意妄行。   一、核心通訊 系統呼叫在使用者空間程序和硬體裝置之間新增中間才能。作用有三: 為使用者空間提供一種硬體的抽象介面。無需理會物理

Linux核心設計與實現 總結筆記第六章核心資料結構

核心資料結構 Linux核心實現了這些通用資料結構,而且提倡大家在開發時重用。 核心開發者應該儘可能地使用這些資料結構,而不要自作主張的山寨方法。 通用的資料結構有以下幾種:連結串列、佇列、對映和二叉樹   一、連結串列 1.1 單向連結串列和雙向連結串列   1.2 環形

例項:tasklet實現軟中斷學習《Linux核心設計與實現》記錄

tasklet是通過軟中斷實現的,tasklet本身也是軟中斷。 關於tasklet更詳細的知識,還是建議看一下《Linux核心設計與實現》 本貼子只介紹一下具體的流程。 驅動程式原始碼: #include <linux/init.h> #include <linu

例項:基於4412-實現新增自己的系統呼叫函式學習《Linux核心設計與實現》 記錄

學習筆記: 在學習《linux核心設計與實現》過程中,瞭解到: 在Linux中,系統呼叫是使用者空間訪問核心的唯一手段(除異常和陷入之外)。 系統呼叫主要有三個作用: ①:為使用者空間提供一個硬體的抽象介面。 ②:系統呼叫保證了系統的穩定和安全。 ③:為了實現多工和虛擬記憶體(應用程

7.Linux核心設計與實現 P69---深入分析 Linux 核心連結串列(轉)

連結串列是一種常用的組織有序資料的資料結構,它通過指標將一系列資料節點連線成一條資料鏈,是線性表的一種重要實現方式。相對於陣列,連結串列具有更好的動態性,建立連結串列時無需預先知道資料總量,可以隨機分配空間,可以高效地在連結串列中的任意位置實時插入或刪除資料。連結串列的開銷主要是訪問的順序性和組織鏈的空間

5.Linux核心設計與實現 P39---linux2.6 CFS排程演算法分析(轉)

1.概述      CFS(completely fair schedule)是最終被核心採納的排程器。它從RSDL/SD中吸取了完全公平的思想,不再跟蹤程序的睡眠時間,也不再企圖區分互動式程序。它將所有的程序都統一對待,這就是公平的含義。CFS的演算法和實現都相當簡單,眾多的測試表明其效能也非常優越。   

Linux核心設計與實現》讀書筆記十三- 虛擬檔案系統

虛擬檔案系統(VFS)是linux核心和具體I/O裝置之間的封裝的一層共通訪問介面,通過這層介面,linux核心可以以同一的方式訪問各種I/O裝置。 虛擬檔案系統本身是linux核心的一部分,是純軟體的東西,並不需要任何硬體的支援。 主要內容: 虛擬檔案系統的作用 虛擬檔案系統的4個主要物件