|
我看的內核版本是2.6.32.
在內核中sk_buff表示一個網(wǎng)絡數(shù)據(jù)包,它是一個雙向鏈表,而鏈表頭就是sk_buff_head,在老的內核里面sk_buff會有一個list域直接指向sk_buff_head也就是鏈表頭,現(xiàn)在在2.6.32里面這個域已經(jīng)被刪除了。 而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小巧的多。另外,相同的函數(shù)可以同樣應用于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;
//這個表示一個網(wǎng)絡設備,當skb為輸出時它表示skb將要輸出的設備,當接收時,它表示輸入設備。要注意,這個設備有可能會是虛擬設備(在3層以上看來)
struct net_device *dev;
///這里其實應該是dst_entry類型,不知道為什么內核要改為ul。這個域主要用于路由子系統(tǒng)。這個數(shù)據(jù)結構保存了一些路由相關信息
unsigned long _skb_dst;
#ifdef CONFIG_XFRM
struct sec_path *sp;
#endif
///這個域很重要,我們下面會詳細說明。這里只需要知道這個域是保存每層的控制信息的就夠了。
char cb[48];
///這個長度表示當前的skb中的數(shù)據(jù)的長度,這個長度即包括buf中的數(shù)據(jù)也包括切片的數(shù)據(jù),也就是保存在skb_shared_info中的數(shù)據(jù)。這個值是會隨著從一層到另一層而改變的。下面我們會對比這幾個長度的。
unsigned int len,
///這個長度只表示切片數(shù)據(jù)的長度,也就是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;
};
};
///優(yōu)先級,主要用于QOS。
__u32 priority;
kmemcheck_bitfield_begin(flags1);
///接下來是一些標志位。
//首先是是否可以本地切片的標志。
__u8 local_df:1,
///為1說明頭可能被clone。
cloned:1,
///這個表示校驗相關的一個標記,表示硬件驅動是否為我們已經(jīng)進行了校驗(前面的blog有介紹)
ip_summed:2,
///這個域如果為1,則說明這個skb的頭域指針已經(jīng)分配完畢,因此這個時候計算頭的長度只需要head和data的差就可以了。
nohdr:1,
///這個域不太理解什么意思。
nfctinfo:3;
///pkt_type主要是表示數(shù)據(jù)包的類型,比如多播,單播,回環(huán)等等。
__u8 pkt_type:3,
///這個域是一個clone標記。主要是在fast clone中被設置,我們后面講到fast clone時會詳細介紹這個域。
fclone:2,
///ipvs擁有的域。
ipvs_property:1,
///這個域應該是udp使用的一個域。表示只是查看數(shù)據(jù)。
peeked:1,
///netfilter使用的域。是一個trace 標記
nf_trace:1;
///這個表示L3層的協(xié)議。比如IP,IPV6等等。
__be16 protocol:16;
kmemcheck_bitfield_end(flags1);
///skb的析構函數(shù),一般都是設置為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;
///網(wǎng)絡層的頭
sk_buff_data_t network_header;
///鏈路層的頭。
sk_buff_data_t mac_header;
///接下來就是幾個操作skb數(shù)據(jù)的指針。下面會詳細介紹。
sk_buff_data_t tail;
sk_buff_data_t end;
unsigned char *head,
*data;
///這個表示整個skb的大小,包括skb本身,以及數(shù)據(jù)。
unsigned int truesize;
///skb的引用計數(shù)
atomic_t users;
};
我們來看前面沒有解釋的那些域。 先來看cb域,他保存了每層所獨自需要的內部數(shù)據(jù)。我們來看tcp的例子。 我們知道tcp層的控制信息保存在tcp_skb_cb中,因此來看內核提供的宏來存取這個數(shù)據(jù)結構:
#define TCP_SKB_CB(__skb) ((struct tcp_skb_cb *)&((__skb)->cb[0])) 在ip層的話,我們可能會用cb來存取切片好的幀。
#define FRAG_CB(skb) ((struct ipfrag_skb_cb *)((skb)->cb)) 到這里你可能會問如果我們想要在到達下一層后,還想保存當前層的私有信息怎么辦。這個時候我們就可以使用skb的clone了。也就是之只復制sk_buff結構。 然后我們來看幾個比較比較重要的域 len,data,tail,head,end。 這幾個域都很簡單,下面這張圖表示了buffer從tcp層到鏈路層的過程中l(wèi)en,head,data,tail以及end的變化,通過這個圖我們可以非常清晰的了解到這幾個域的區(qū)別。 可以很清楚的看到head指針為分配的buffer的起始位置,end為結束位置,而data為當前數(shù)據(jù)的起始位置,tail為當前數(shù)據(jù)的結束位置。len就是數(shù)據(jù)區(qū)的長度。 然后來看transport_header,network_header以及mac_header的變化,這幾個指針都是隨著數(shù)據(jù)包到達不同的層次才會有對應的值,我們來看下面的圖,這個圖表示了當從2層到達3層對應的指針的變化。 這里可以看到data指針會由于數(shù)據(jù)包到了三層,而跳過2層的頭。這里我們就可以得到data起始真正指的是本層的頭以及數(shù)據(jù)的起始位置。 然后我們來看skb的幾個重要操作函數(shù)。 首先是skb_put,skb_push,skb_pull以及skb_reserve這幾個最長用的操作data指針的函數(shù)。 這里可以看到內核skb_XXX都還有一個__skb_XXX函數(shù),這是因為前一個只是將后一個函數(shù)進行了一個包裝,加了一些校驗。 先來看__skb_put函數(shù)。 可以看到它只是將tail指針移動len個位置,然后len也相應的增加len個大小。
static inline unsigned char *__skb_put(struct sk_buff *skb, unsigned int len)
{
unsigned char *tmp = skb_tail_pointer(skb);
SKB_LINEAR_ASSERT(skb);
///改變相應的域。
skb->tail += len;
skb->len += len;
return tmp;
}
然后是__skb_push,它是將data指針向上移動len個位置,對應的len肯定也是增加len大小。
static inline unsigned char *__skb_push(struct sk_buff *skb, unsigned int len)
{
skb->data -= len;
skb->len += len;
return skb->data;
}
剩下的兩個就不貼代碼了,都是很簡單的函數(shù),__skb_pull是將data指針向下移動len個位置,然后len減小len大小。__skb_reserve是將整個數(shù)據(jù)區(qū),也就是data以及tail指針一起向下移動len大小。這個函數(shù)一般是用來對齊地址用的。 看下面的圖,描述了4個函數(shù)的操作: 接著是skb的alloc函數(shù)。 在內核中分配一個skb是在__alloc_skb中實現(xiàn)的,接下來我們就來看這個函數(shù)的具體實現(xiàn)。 這個函數(shù)起始可以看作三部分,第一部分是從cache中分配內存,第二部分是初始化分配的skb的相關域。第三部分是處理fclone。 還有一個要注意的就是這里__alloc_skb是被三個函數(shù)包裝后才能直接使用的,我們只看前兩個,一個是skb_alloc_skb,一個是alloc_skb_fclone函數(shù),這兩個函數(shù)傳遞進來的第三個參數(shù),也就是fclone前一個是0,后一個是1. 那么這個函數(shù)是什么意思呢,它和alloc_skb有什么區(qū)別的。 這個函數(shù)可以叫做Fast SKB cloning函數(shù),這個函數(shù)存在的主要原因是,以前我們每次skb_clone一個skb的時候,都是要調用kmem_cache_alloc從cache中alloc一塊新的內存。而現(xiàn)在當我們擁有了fast clone之后,通過調用alloc_skb_fclone函數(shù)來分配一塊大于sizeof(struct sk_buff)的內存,也就是在這次請求的skb的下方多申請了一些內存,然后返回的時候設置返回的skb的fclone標記為SKB_FCLONE_ORIG,而多申請的那塊內存的sk_buff的fclone為SKB_FCLONE_UNAVAILABLE,這樣當我們調用skb_clone克隆這個skb的時候看到fclone的標記就可以直接將skb的指針+1,而不需要從cache中取了。這樣的話節(jié)省了一次內存存取,提高了clone的效率,不過調用flcone 一般都是我們確定接下來這個skb會被clone很多次。 更詳細的fclone的介紹可以看這里: http:///Articles/140552/ 這樣我們先來看_alloc_skb,然后緊接著看skb_clone,這樣就能更好的理解這些。 這里fclone的多分配的內存部分,沒太弄懂從那里多分配的,自己對內核的內存子系統(tǒng)還是不太熟悉。覺得應該是skbuff_fclone_cache中會自動多分配些內存。
struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
int fclone, int node)
{
struct kmem_cache *cache;
struct skb_shared_info *shinfo;
struct sk_buff *skb;
u8 *data;
///這里通過fclone的值來判斷是要從fclone cache還是說從head cache中取。
cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;
///首先是分配skb,也就是包頭。
skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
if (!skb)
goto out;
///首先將size對齊,這里是按一級緩存的大小來對齊。
size = SKB_DATA_ALIGN(size);
///然后是數(shù)據(jù)區(qū)的大小,大小為size+ sizeof(struct skb_shared_info的大小。
data = kmalloc_node_track_caller(size + sizeof(struct skb_shared_info),
gfp_mask, node);
if (!data)
goto nodata;
///初始化相關域。
memset(skb, 0, offsetof(struct sk_buff, tail));
///這里truesize可以看到就是我們分配的整個skb+data的大小
skb->truesize = size + sizeof(struct sk_buff);
///users加一。
atomic_set(&skb->users, 1);
///一開始head和data是一樣大的。
skb->head = data;
skb->data = data;
///設置tail指針
skb_reset_tail_pointer(skb);
///一開始tail也就是和data是相同的。
skb->end = skb->tail + size;
kmemcheck_annotate_bitfield(skb, flags1);
kmemcheck_annotate_bitfield(skb, flags2);
#ifdef NET_SKBUFF_DATA_USES_OFFSET
skb->mac_header = ~0U;
#endif
///初始化shinfo,這個我就不介紹了,前面的blog分析切片時,這個結構很詳細的分析過了。
shinfo = skb_shinfo(skb);
atomic_set(&shinfo->dataref, 1);
shinfo->nr_frags = 0;
shinfo->gso_size = 0;
shinfo->gso_segs = 0;
shinfo->gso_type = 0;
shinfo->ip6_frag_id = 0;
shinfo->tx_flags.flags = 0;
skb_frag_list_init(skb);
memset(&shinfo->hwtstamps, 0, sizeof(shinfo->hwtstamps));
///fclone為1,說明多分配了一塊內存,因此需要設置對應的fclone域。
if (fclone) {
///可以看到多分配的內存剛好在當前的skb的下方。
struct sk_buff *child = skb + 1;
atomic_t *fclone_ref = (atomic_t *) (child + 1);
kmemcheck_annotate_bitfield(child, flags1);
kmemcheck_annotate_bitfield(child, flags2);
///設置標記。這里要注意,當前的skb和多分配的skb設置的fclone是不同的。
skb->fclone = SKB_FCLONE_ORIG;
atomic_set(fclone_ref, 1);
child->fclone = SKB_FCLONE_UNAVAILABLE;
}
out:
return skb;
nodata:
kmem_cache_free(cache, skb);
skb = NULL;
goto out;
}
下圖就是alloc_skb之后的skb的指針的狀態(tài)。這里忽略了fclone。 然后我們來看skb_clone函數(shù),clone的意思就是只復制skb而不復制data域。 這里它會先判斷將要被clone的skb的fclone段,以便與決定是否重新分配一塊內存來保存skb。 然后調用__skb_clone來初始化相關的域。
struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask)
{
struct sk_buff *n;
///n為skb緊跟著那塊內存,這里如果skb是通過skb_fclone分配的,那么n就是一個skb。
n = skb + 1;
///skb和n的fclone都要符合要求,可以看到這里的值就是我們在__alloc_skb中設置的值。
if (skb->fclone == SKB_FCLONE_ORIG &&
n->fclone == SKB_FCLONE_UNAVAILABLE) {
///到這里,就說明我們不需要alloc一個skb,直接取n就可以了,并且設置fclone的標記。并修改引用計數(shù)。
atomic_t *fclone_ref = (atomic_t *) (n + 1);
n->fclone = SKB_FCLONE_CLONE;
atomic_inc(fclone_ref);
} else {
///這里就需要從cache中取得一塊內存。
n = kmem_cache_alloc(skbuff_head_cache, gfp_mask);
if (!n)
return NULL;
kmemcheck_annotate_bitfield(n, flags1);
kmemcheck_annotate_bitfield(n, flags2);
///設置新的skb的fclone域。這里我們新建的skb,沒有被fclone的都是這個標記。
n->fclone = SKB_FCLONE_UNAVAILABLE;
}
return __skb_clone(n, skb);
}
這里__skb_clone就不介紹了,函數(shù)就是將要被clone的skb的域賦值給clone的skb。 下圖就是skb_clone之后的兩個skb的結構圖: 當一個skb被clone之后,這個skb的數(shù)據(jù)區(qū)是不能被修改的,這就意為著,我們存取數(shù)據(jù)不需要任何鎖??墒怯袝r我們需要修改數(shù)據(jù)區(qū),這個時候會有兩個選擇,一個是我們只修改linear段,也就是head和end之間的段,一種是我們還要修改切片數(shù)據(jù),也就是skb_shared_info. 這樣就有兩個函數(shù)供我們選擇,第一個是pskb_copy,第二個是skb_copy. 我們先來看pskb_copy,函數(shù)先alloc一個新的skb,然后調用skb_copy_from_linear_data來復制線性區(qū)的數(shù)據(jù)。
struct sk_buff *pskb_copy(struct sk_buff *skb, gfp_t gfp_mask)
{
/*
* Allocate the copy buffer
*/
struct sk_buff *n;
#ifdef NET_SKBUFF_DATA_USES_OFFSET
n = alloc_skb(skb->end, gfp_mask);
#else
n = alloc_skb(skb->end - skb->head, gfp_mask);
#endif
if (!n)
goto out;
/* Set the data pointer */
skb_reserve(n, skb->data - skb->head);
/* Set the tail pointer and length */
skb_put(n, skb_headlen(skb));
///復制線性數(shù)據(jù)段。
skb_copy_from_linear_data(skb, n->data, n->len);
///更新相關域
n->truesize += skb->data_len;
n->data_len = skb->data_len;
n->len = skb->len;
///下面只是復制切片數(shù)據(jù)的指針
if (skb_shinfo(skb)->nr_frags) {
int i;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
skb_shinfo(n)->frags[i] = skb_shinfo(skb)->frags[i];
get_page(skb_shinfo(n)->frags[i].page);
}
skb_shinfo(n)->nr_frags = i;
}
...............................
copy_skb_header(n, skb);
out:
return n;
}
然后是skb_copy,它是復制skb的所有數(shù)據(jù)段,包括切片數(shù)據(jù):
struct sk_buff *skb_copy(const struct sk_buff *skb, gfp_t gfp_mask)
{
int headerlen = skb->data - skb->head;
/*
* Allocate the copy buffer
*/
//先alloc一個新的skb
struct sk_buff *n;
#ifdef NET_SKBUFF_DATA_USES_OFFSET
n = alloc_skb(skb->end + skb->data_len, gfp_mask);
#else
n = alloc_skb(skb->end - skb->head + skb->data_len, gfp_mask);
#endif
if (!n)
return NULL;
/* Set the data pointer */
skb_reserve(n, headerlen);
/* Set the tail pointer and length */
skb_put(n, skb->len);
///然后復制所有的數(shù)據(jù)。
if (skb_copy_bits(skb, -headerlen, n->head, headerlen + skb->len))
BUG();
copy_skb_header(n, skb);
return n;
}
下面這張圖就表示了psb_copy和skb_copy調用后的內存模型,其中a是pskb_copy,b是skb_copy: 最后來看skb的釋放: 這里主要是判斷一個引用標記位users,將它減一,如果大于0則直接返回,否則釋放skb。
void kfree_skb(struct sk_buff *skb)
{
if (unlikely(!skb))
return;
if (likely(atomic_read(&skb->users) == 1))
smp_rmb();
///減一,然后判斷。
else if (likely(!atomic_dec_and_test(&skb->users)))
return;
trace_kfree_skb(skb, __builtin_return_address(0));
__kfree_skb(skb);
}
|
|
|
來自: omcc > 《網(wǎng)絡》