skb_copy v.s. skb_clone

skb_clone 和 skb_copy 有甚麼不一樣?

從字面上來看,差不多啊,但實際上就是有差,先來看看 Man 吧~

struct sk_buff * skb_clone (struct sk_buff * skb, int gfp_mask);

Duplicate an &sk_buff. The new one is not owned by a socket. Both copies share the same packet data but not structure. The new buffer has a reference count of 1. If the allocation fails the function returns NULL otherwise the new buffer is returned.

If this function is called from an interrupt gfp_mask must be GFP_ATOMIC.

struct sk_buff * skb_copy (const struct sk_buff * skb, int gfp_mask);

Make a copy of both an &sk_buff and its data. This is used when the caller wishes to modify the data and needs a private copy of the data to alter. Returns NULL on failure or the pointer to the buffer on success. The returned buffer has a reference count of 1.

As by-product this function converts non-linear &sk_buff to linear one, so that &sk_buff becomes completely private and caller is allowed to modify all the data of returned buffer. This means that this function is not recommended for use in circumstances when only header is going to be modified. Use pskb_copy instead.

我把最重要的部份用紅色的字體圈出來了~還要更詳細的解釋...恩,直接來看 code 吧。

首先來看 skb_copy

struct sk_buff *skb_copy(const struct sk_buff *skb, gfp_t gfp_mask)
{
....int headerlen = skb->data - skb->head;
..../*
....* Allocate the copy buffer
....*/
....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);

....if (skb_copy_bits(skb, -headerlen, n->head, headerlen + skb->len))
........BUG();

....copy_skb_header(n, skb);
....return n;
}

簡單來說,就是先 allocate 一塊 skb_buff 的空間出來,並且根據原來的 skb 進行 空間的設定(包含 skb_reserve, skb_put),最後在進行 data 和 header 拷貝的動作。一些 function 的程式碼列在下面。

/* 在 skb 的前端預留一塊空間 */
static inline void skb_reserve(struct sk_buff *skb, int len)
{
....skb->data += len;
....skb->tail += len;
}

/* 在 skb 的後端預留一塊空間準備填入資料 */
extern unsigned char *skb_put(struct sk_buff *skb, unsigned int 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_copy_bits 和 copy_skb_header 我就不解釋了。但要特別注意,copy_skb_header 指的是拷貝 skb 這個資料結構的 Header 而不是封包的 Header

下面再來看看 skb_clone:

static struct sk_buff *__skb_clone(struct sk_buff *n, struct sk_buff *skb)
{
#define C(x) n->x = skb->x

....n->next = n->prev = NULL;
....n->sk = NULL;
....__copy_skb_header(n, skb);

....C(len);
....C(data_len);
....C(mac_len);
....n->hdr_len = skb->nohdr ? skb_headroom(skb) : skb->hdr_len;
....n->cloned = 1;
....n->nohdr = 0;
....n->destructor = NULL;
....C(iif);
....C(tail);
....C(end);
....C(head);
....C(data);
....C(truesize);
#if defined(CONFIG_MAC80211) || defined(CONFIG_MAC80211_MODULE)
....C(do_not_encrypt);
#endif
....atomic_set(&n->users, 1);

....atomic_inc(&(skb_shinfo(skb)->dataref));
....skb->cloned = 1;

....return n;
#undef C
}

看到了嗎,這過程中,skb_clone 並不會去分配 Data 的記憶體位置,而是直接指定位置到原來的 skb -> data 上面。毫無疑問的,這樣的速度會比較快,但使用上也要更加小心才行。

留言

張貼留言

這個網誌中的熱門文章

如何將Linux打造成OpenFlow Switch:Openvswitch

我弟家的新居感恩禮拜分享:善頌善禱

Linux Virtual Interface: TUN/TAP