【協議森林】深入淺出理解skb_buff(一)

1.前言

在互聯網技術裏,有兩件事最爲重要,一個是 TCP/IP 協議,它是萬物互聯的事實標準;另一個是 Linux 操作系統,它是推動互聯網技術走向繁榮的基石。在 Linux內核的協議棧中的實現中,數據結構skb_buff是最關鍵和最核心的數據,它表示接收或發送數據包的包頭信息,幷包含很多成員變量供網絡代碼中的各子系統使用。本文以及後續關於skb_buff的介紹,均來源於經典著作《深入理解linux網絡技術內幕》和《linux內核源碼剖析:TCP/IP實現》。
在這裏插入圖片描述

2.skb_buff基本原理

內核中sk_buff結構體在各層協議之間傳輸不是用拷貝sk_buff結構體,而是通過增加協議頭和移動指針來操作的。如果是從L4傳輸到L2,則是通過往sk_buff結構體中增加該層協議頭來操作;如果是從L4到L2,則是通過移動sk_buff結構體中的data指針來實現,不會刪除各層協議頭,這樣方式極大的提高CPU工作效率。
在這裏插入圖片描述
sk_buff結構體是linux網絡代碼中最重要的數據結構,是整個網絡傳輸載體。所以sk_buff結構體裏面有很多關於其他功能的成員字段,比如:防火牆,子路由系統,多播等。這些字段並不是一定有的,只有在滿足特點條件纔有的。所以可以在需要時候再去關心這些成員字段,現在我們只來講解主要的成員字段。

3.skb_buff主要字段

爲了好理解結構中的一些成員字段,先把後面要講的內容提前說下。sk_buff結構體關聯多個其他結構體,主要可以分爲:
第一是數據區:由sk_buff中head和end指向的數據塊,用來存儲sk_buff結構的數據也即是存儲數據包的內容和各層協議頭。
第二是分片結構:用來表示IP分片的一個結構體,實則上是和sk_buff結構的數據區相連的,即是end指針的下一個字節開始就是分片結構。正因此,分片結構和sk_buff數據區內存分配及銷燬時都是一起的。
第三個是分片結構指向的數據區,即是IP分片內容。

struct sk_buff {
	/* These two members must be first. */
	struct sk_buff		*next;  //  因爲sk_buff結構體是雙鏈表,所以有前驅後繼。這是個指向後面的sk_buff結構體指針
	struct sk_buff		*prev;  //  這是指向前一個sk_buff結構體指針
	//老版本(2.6以前)應該還有個字段: sk_buff_head *list  //即每個sk_buff結構都有個指針指向頭節點
	struct sock		    *sk;  // 指向擁有此緩衝的套接字sock結構體,即:宿主傳輸控制模塊
	ktime_t			tstamp;  // 時間戳,表示這個skb的接收到的時間,一般是在包從驅動中往二層發送的接口函數中設置
	struct net_device	*dev;  // 表示一個網絡設備,當skb爲輸出/輸入時,dev表示要輸出/輸入到的設備
	unsigned long	_skb_dst;  // 主要用於路由子系統,保存路由有關的東西
	char			cb[48];  // 保存每層的控制信息,每一層的私有信息
	unsigned int		len,  // 表示數據區的長度(tail - data)與分片結構體數據區的長度之和。其實這個len中數據區長度是個有效長度,
                                      // 因爲不刪除協議頭,所以只計算有效協議頭和包內容。如:當在L3時,不會計算L2的協議頭長度。
				data_len;  // 只表示分片結構體數據區的長度,所以len = (tail - data) + data_len;
	__u16			mac_len,  // mac報頭的長度
				hdr_len;  // 用於clone時,表示clone的skb的頭長度
	// 接下來是校驗相關域,這裏就不詳細講了。
	__u32			priority;  // 優先級,主要用於QOS
	kmemcheck_bitfield_begin(flags1);
	__u8			local_df:1,  // 是否可以本地切片的標誌
				cloned:1,  // 爲1表示該結構被克隆,或者自己是個克隆的結構體;同理被克隆時,自身skb和克隆skb的cloned都要置1
				ip_summed:2, 
				nohdr:1,  // nohdr標識payload是否被單獨引用,不存在協議首部。                                                                                                      // 如果被引用,則決不能再修改協議首部,也不能通過skb->data來訪問協議首部。</span></span>
				nfctinfo:3;
	__u8			pkt_type:3,  // 標記幀的類型
				fclone:2,   // 這個成員字段是克隆時使用,表示克隆狀態
				ipvs_property:1,
				peeked:1,
				nf_trace:1;
	__be16			protocol:16;  // 這是包的協議類型,標識是IP包還是ARP包或者其他數據包。
	kmemcheck_bitfield_end(flags1);
	void	(*destructor)(struct sk_buff *skb);  // 這是析構函數,後期在skb內存銷燬時會用到
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
	struct nf_conntrack	*nfct;
	struct sk_buff		*nfct_reasm;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
	struct nf_bridge_info	*nf_bridge;
#endif
	int			iif;  // 接受設備的index
#ifdef CONFIG_NET_SCHED
	__u16			tc_index;	/* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
	__u16			tc_verd;	/* traffic control verdict */
#endif
#endif
	kmemcheck_bitfield_begin(flags2);
	__u16			queue_mapping:16;
#ifdef CONFIG_IPV6_NDISC_NODETYPE
	__u8			ndisc_nodetype:2;
#endif
	kmemcheck_bitfield_end(flags2);
	/* 0/14 bit hole */
#ifdef CONFIG_NET_DMA
	dma_cookie_t		dma_cookie;
#endif
#ifdef CONFIG_NETWORK_SECMARK
	__u32			secmark;
#endif
	__u32			mark;
	__u16			vlan_tci;
	sk_buff_data_t		transport_header;      // 指向四層幀頭結構體指針
	sk_buff_data_t		network_header;	       // 指向三層IP頭結構體指針
	sk_buff_data_t		mac_header;	       // 指向二層mac頭的頭
	/* These elements must be at the end, see alloc_skb() for details.  */
	sk_buff_data_t		tail;			  // 指向數據區中實際數據結束的位置
	sk_buff_data_t		end;			  // 指向數據區中結束的位置(非實際數據區域結束位置)
	unsigned char		*head,			  // 指向數據區中開始的位置(非實際數據區域開始位置)
				*data;			  // 指向數據區中實際數據開始的位置			
	unsigned int		truesize;		  // 表示總長度,包括sk_buff自身長度和數據區以及分片結構體的數據區長度
	atomic_t		users;                    // skb被克隆引用的次數,在內存申請和克隆時會用到
};   //end sk_buff

char cb[48];這個字段是skb信息控制塊,也就是存儲每層的一些協議信息,當數據包在哪一層時,存儲的就是哪一層協議信息。這個字段由數據包所在層使用和維護,如果要訪問本層協議信息,可以通過用一些宏來操作這個成員字段。如:#define TCP_SKB_CB(__skb) ((struct tcp_skb_cb *)&((__skb)->cb[0]))

_u8 fclone:2;這是個克隆狀態標誌,到sk_buff結構內存申請時會使用到。這裏提前講下:若fclone = SKB_FCLONE_UNAVAILABLE,則表明SKB未被克隆;若fclone = SKB_FCLONE_ORIG,則表明是從skbuff_fclone_cache緩存池(這個緩存池上分配內存時,每次都分配一對skb內存)中分配的父skb,可以被克隆;若fclone = SKB_FCLONE_CLONE,則表明是在skbuff_fclone_cache分配的子SKB,從父SKB克隆得到的;

atomic_t users;這是個引用計數,表明了有多少實體引用了這個skb。其作用就是在銷燬skb結構體時,先查看下users是否爲零,若不爲零,則調用函數遞減下引用計數users即可;當某一次銷燬時,users爲零才真正釋放內存空間。有兩個操作函數:atomic_inc()引用計數增加1;atomic_dec()引用計數減去1;

sk_buff->data_len:只計算分片中數據的長度,即是分片結構體中page指向的數據區長度。這個在分片結構體中會再詳細講解下。

sk_buff->len:表示當前緩衝區中數據塊的大小的總長度。它包括主緩衝中(即是sk_buff結構中指針data指向)的數據區的實際長度(data-tail)和分片中的數據長度。這個長度在數據包在各層間傳輸時會改變,因爲分片數據長度不變,從L2到L4時,則len要減去幀頭大小和網絡頭大小;從L4到L2則相反,要加上幀頭和網絡頭大小。所以:len = (data - tail) + data_len;

sk_buff->truesize:這是緩衝區的總長度,包括sk_buff結構和數據部分。如果申請一個len字節的緩衝區,alloc_skb函數會把它初始化成len+sizeof(sk_buff)。當skb->len變化時,這個變量也會變化。所以:truesize = len + sizeof(sk_buff) = (data - tail) + data_len + sizeof(sk_buff);

加入討論

在這裏插入圖片描述

發佈了164 篇原創文章 · 獲贊 85 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章