我看的Linux內核版本是2.6.32.
在內核中sk_buff表示一個網絡數據包,它是一個雙向鏈表,而鏈表頭就是sk_buff_head,在老的內核裡面sk_buff會有一個list域直接指向sk_buff_head也就是鏈表頭,現在在2.6.32裡面這個域已經被刪除了。
而sk_buff的內存布局可以分作3個段,第一個就是sk_buff自身,第二個是linear-data buff,第三個是paged-data buff(也就是skb_shared_info)。
ok.我們先來看sk_buff_head的結構。它也就是所有sk_buff的頭。
struct sk_buff_head {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
__u32 qlen;
spinlock_t lock;
};
這裡可以看到前兩個域是和sk_buff一致的,而且內核的注釋是必須放到最前面。這裡的原因是:
這使得兩個不同的結構可以放到同一個鏈表中,盡管sk_buff_head要比sk_buff小巧的多。另外,相同的函數可以同樣應用於sk_buff和sk_buff_head。
然後qlen域表示了當前的sk_buff鏈上包含多少個skb。
lock域是自旋鎖。
然後我們來看sk_buff,下面就是skb的結構:
我這裡注釋了一些簡單的域,復雜的域下面會單獨解釋。
struct sk_buff {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
//表示從屬於那個socket,主要是被4層用到。
struct sock *sk;
//表示這個skb被接收的時間。
ktime_t tstamp;
//這個表示一個網絡設備,當skb為輸出時它表示skb將要輸出的設備,當接收時,它表示輸入設備。要注意,這個設備有可能會是虛擬設備(在3層以上看來)
struct net_device *dev;
///這裡其實應該是dst_entry類型,不知道為什麼內核要改為ul。這個域主要用於路由子系統。這個數據結構保存了一些路由相關信息
unsigned long _skb_dst;
#ifdef CONFIG_XFRM
struct sec_path *sp;
#endif
///這個域很重要,我們下面會詳細說明。這裡只需要知道這個域是保存每層的控制信息的就夠了。
char cb[48];
///這個長度表示當前的skb中的數據的長度,這個長度即包括buf中的數據也包括切片的數據,也就是保存在skb_shared_info中的數據。這個值是會隨著從一層到另一層而改變的。下面我們會對比這幾個長度的。
unsigned int len,
///這個長度只表示切片數據的長度,也就是skb_shared_info中的長度。
data_len;
///這個長度表示mac頭的長度(2層的頭的長度)
__u16 mac_len,
///這個主要用於clone的時候,它表示clone的skb的頭的長度。
hdr_len;
///接下來是校驗相關的域。
union {
__wsum csum;
struct {
__u16 csum_start;
__u16 csum_offset;
};
};
///優先級,主要用於QOS。
__u32 priority;
kmemcheck_bitfield_begin(flags1);
///接下來是一些標志位。
//首先是是否可以本地切片的標志。
__u8 local_df:1,
///為1說明頭可能被clone。
cloned:1,
///這個表示校驗相關的一個標記,表示硬件驅動是否為我們已經進行了校驗(前面的blog有介紹)
ip_summed:2,
///這個域如果為1,則說明這個skb的頭域指針已經分配完畢,因此這個時候計算頭的長度只需要head和data的差就可以了。
nohdr:1,
///這個域不太理解什麼意思。
nfctinfo:3;
///pkt_type主要是表示數據包的類型,比如多播,單播,回環等等。
__u8 pkt_type:3,
///這個域是一個clone標記。主要是在fast clone中被設置,我們後面講到fast clone時會詳細介紹這個域。
fclone:2,
///ipvs擁有的域。
ipvs_property:1,
///這個域應該是udp使用的一個域。表示只是查看數據。
peeked:1,
///netfilter使用的域。是一個trace 標記
nf_trace:1;
///這個表示L3層的協議。比如IP,IPV6等等。
__be16 protocol:16;
kmemcheck_bitfield_end(flags1);
///skb的析構函數,一般都是設置為sock_rfree或者sock_wfree.
void (*destructor)(struct sk_buff *skb);
///netfilter相關的域。
#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
///接收設備的index。
int iif;
///流量控制的相關域。
#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
///skb的標記。
__u32 mark;
///vlan的控制tag。
__u16 vlan_tci;
///傳輸層的頭
sk_buff_data_t transport_header;
///網絡層的頭
sk_buff_data_t network_header;
///鏈路層的頭。
sk_buff_data_t mac_header;
///接下來就是幾個操作skb數據的指針。下面會詳細介紹。
sk_buff_data_t tail;
sk_buff_data_t end;
unsigned char *head,
*data;
///這個表示整個skb的大小,包括skb本身,以及數據。
unsigned int truesize;
///skb的引用計數
atomic_t users;
};