这个系列内容会有点杂,会涉及tcp/ip, netfilter, lvs, 网卡驱动等各个方面。。下半年准备把内核网络这一块好好研究下。。好吧从第一篇开始
这篇介绍内核网络中最重要的数据结构,大部分可以在understanding linux network internal里找到
struct sock
struct sock首先包含一个struct sock_common结构,主要是一些属性,标志位,状态,和哈希表相关的值,内核通过哈希表把这些struct sock_common连接起来
struct sock结构下面包含了一个一个sk_backlog结构,可以看作是一个sk_buff的链表,我猜是connect request的链表,同时还有个对应的sk_sleep的wait_queue_head_t
还有缓冲区相关的参数,如sk_receive_queue, sk_write_queue, 缓冲区大小参数,如sk_sndbuf, sk_rcvbuf,发送接受的时间参数,如sk_rcvtimeo, sk_sndtimeo,以及一些setsockopt里有关的sock属性参数,如sk_priority, sk_lingertime。。 当然更多的是我不知道干吗用的,以后如果涉及到了再说吧
struct sk_buff
sk_buff应该是最重要的结构了,ULN里讲的很多了,我只把我认为最重要的列出来
struct sk_buff *next, *prev:所有的sk_buff通过这两个结构组成了一个双向链表
struct sock* sk: 报文对应的sock结构
ktime_t tstamp:报文到达网卡的时间戳
struct net_device *dev:报文到达的网络设备
char cb[48]:control buffer,e.g. tcp_skb_cb
sk_buff_data_t transport_header, network_header, mac_header:不同level的报文头offset
sk_buff_data_t tail, end
unsigned char *head, *data:报文加头剥头用到的各种offset
还有各种标志位就不一一讲了
struct sk_buff在内存中的结构有两部分,一块是struct sk_buff的内存,由slab allocator分配,另一块是data指向的真正的报文所在的内存,后面跟一个struct skb_shared_info结构。这块连续内存是在skb_alloc时由kmalloc分配的
skb_shared_info有三个成员用来支持scatter-gather IO,分别是
struct sk_buff* frag_list
unsigned short nr_frags
skb_frag_t frags[MAX_SKB_FRAGS]
frags是scatter-gather的IO。frags是一个线性数组,其中MAX_SKB_FRAGS表示一个scatter-gather skb最多可以有64K个分片(分散在64K个page中),struct skb_frag_t的结构如下
struct skb_frag_struct {
struct page *page;
__u32 page_offset;
__u32 size;
};
可以看出每个skb_frag_t都代表了单独一个page中的skb数据信息
而frag_list则指向链表的下一个skb,合并skb数据的时候,先是合并frags数组的数据,然后再在frag_list里遍历下一个skb。frag_list一般用于IP分片的场景中,相比而言frags数组只是简单的scatter-gather IO
P.S. 需要注意的是,对于IP fragment/defragment的情况,接受的时候所有分片是会形成一个sk_buff的链表的,但此时IP头的数据基本相同
sk_buff有多个表示length的成员,其区别如下:
sk_buff->len,表示当前协议下的数据长度,包括线性缓冲区的数据长度和分片的数据长度。线性缓冲区的长度从skb->data指针开始计算。注意skb->data是随着协议不同而变化的
sk_buff->data_len,只表示分片的数据长度
sk_buff->truesize,表示sk_buff->len 加上struct sk_buff结构大小
struct net_device
struct net_device代表了网络设备的抽象,在include/linux/netdevice.h中可以看到其定义
char *name, *ifalias:网络设备名,其中所有网络设备组成了一个hash表,由struct hlist_node name_hlist连起来
unsigned long base_addr 记录了设备的io port,一般用来inb outb这些操作 unsigned int irq 记录了设备对应的IRQ中断
unsigned long feature 记录了设备支持的功能:
#define NETIF_F_SG 1 /* Scatter/gather IO. */
#define NETIF_F_IP_CSUM 2 /* Can checksum TCP/UDP over IPv4. */
#define NETIF_F_NO_CSUM 4 /* Does not require checksum. F.e. loopack. */
#define NETIF_F_HW_CSUM 8 /* Can checksum all the packets. */
#define NETIF_F_IPV6_CSUM 16 /* Can checksum TCP/UDP over IPV6 */
#define NETIF_F_HIGHDMA 32 /* Can DMA to high memory. */
#define NETIF_F_FRAGLIST 64 /* Scatter/gather IO. */
#define NETIF_F_HW_VLAN_TX 128 /* Transmit VLAN hw acceleration */
#define NETIF_F_HW_VLAN_RX 256 /* Receive VLAN hw acceleration */
#define NETIF_F_HW_VLAN_FILTER 512 /* Receive filtering on VLAN */
#define NETIF_F_VLAN_CHALLENGED 1024 /* Device cannot handle VLAN packets */
#define NETIF_F_GSO 2048 /* Enable software GSO. */
/* Segmentation offload features */
#define NETIF_F_GSO_SHIFT 16
#define NETIF_F_GSO_MASK 0x00ff0000
#define NETIF_F_TSO (SKB_GSO_TCPV4 << NETIF_F_GSO_SHIFT)
#define NETIF_F_UFO (SKB_GSO_UDP << NETIF_F_GSO_SHIFT)
#define NETIF_F_GSO_ROBUST (SKB_GSO_DODGY << NETIF_F_GSO_SHIFT)
#define NETIF_F_TSO_ECN (SKB_GSO_TCP_ECN << NETIF_F_GSO_SHIFT)
#define NETIF_F_TSO6 (SKB_GSO_TCPV6 << NETIF_F_GSO_SHIFT)
#define NETIF_F_FSO (SKB_GSO_FCOE << NETIF_F_GSO_SHIFT)
int ifindex : 网络设备的单一index
unsigned short mtu:MTU大小
struct dev_addr_list *mc_list, int mc_count, 多播的mac地址列表和个数
unsigned long last_rx:上一次收到包的时间
unsigned char* dev_addr:mac地址?
unsigned char broadcast[MAX_ADDR_LEN]:mac广播地址
struct netdev_queue rx_queue:接收队列
struct netdev_queue *_tx, unsigned int num_tx_queues:既然有了rx_queue为什么还要这这个呢,原来这个是RPS/RFS的patch里新加的多队列的支持
struct netdev_queue *_rx, unsigned int num_rx_queues:发送队列链表,个数,我猜这个是tc qdisc会用到的
netdev_ops是众多函数指针组成的结构体,e.g.
ndo_init:register_netdevice时call到
ndo_uninit:unregister_netdevice时call到
ndo_open:用于dev_open
ndo_close:用于dev_close
这些函数指针都是device driver需要注册进去的,以intel 10Gbit万兆卡为例 ixgbe 驱动定义如下:
static const struct net_device_ops ixgbe_netdev_ops = {
.ndo_open = ixgbe_open,
.ndo_stop = ixgbe_close,
.ndo_start_xmit = ixgbe_xmit_frame,
.ndo_select_queue = ixgbe_select_queue,
.ndo_set_rx_mode = ixgbe_set_rx_mode,
.ndo_set_multicast_list = ixgbe_set_rx_mode,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = ixgbe_set_mac,
.ndo_change_mtu = ixgbe_change_mtu,
.ndo_tx_timeout = ixgbe_tx_timeout,
.ndo_vlan_rx_register = ixgbe_vlan_rx_register,
.ndo_vlan_rx_add_vid = ixgbe_vlan_rx_add_vid,
.ndo_vlan_rx_kill_vid = ixgbe_vlan_rx_kill_vid,
.ndo_do_ioctl = ixgbe_ioctl,
.ndo_set_vf_mac = ixgbe_ndo_set_vf_mac,
.ndo_set_vf_vlan = ixgbe_ndo_set_vf_vlan,
.ndo_set_vf_tx_rate = ixgbe_ndo_set_vf_bw,
.ndo_get_vf_config = ixgbe_ndo_get_vf_config,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = ixgbe_netpoll,
#endif
#ifdef IXGBE_FCOE
.ndo_fcoe_ddp_setup = ixgbe_fcoe_ddp_get,
.ndo_fcoe_ddp_done = ixgbe_fcoe_ddp_put,
.ndo_fcoe_enable = ixgbe_fcoe_enable,
.ndo_fcoe_disable = ixgbe_fcoe_disable,
.ndo_fcoe_get_wwn = ixgbe_fcoe_get_wwn,
#endif /* IXGBE_FCOE */
};
关于skb的处理有相当多的函数,一一介绍如下:
alloc_skb:
struct sk_buff *alloc_skb(unsigned int size, int gfp_mask)
{
struct sk_buff *skb;
u8 *data;
/* Get the HEAD */
/* 从cache缓冲池中获取内存,屏蔽__GFP_DMA的原因在于sk_buff结构体和DMA没毛关系,不需要占用宝贵的DMA内存区 */
skb = kmem_cache_alloc(skbuff_head_cache,
gfp_mask & ~__GFP_DMA);
if (!skb)
goto out;
/* Get the DATA. Size must match skb_add_mtu(). */
/* 对其size */
size = SKB_DATA_ALIGN(size);
/* 分配的缓冲长度包含skb_shared_info的长度。这里没有了屏蔽__GFP_DMA,因为数据区域最好能在DMA内存区,这样DMA可以直接操作映射的内存 */
data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
if (!data)
goto nodata;
/*
* offsetof是一个编译器宏或者是自定义的宏,用于计算member在struct中的偏移量。
* 把在truesize前面的field全部清零。
*/
memset(skb, 0, offsetof(struct sk_buff, truesize));
/* truesize是广义SKB的大小,包含了4个部分的长度:skb自身,header,page frags,frag list. 和len, data_len不同在于,truesize是len + sk_buff结构体大小的总和,每当len有变化,truesize也要跟着变 */
skb->truesize = size + sizeof(struct sk_buff);
/* users初始化成1 */
atomic_set(&skb->users, 1);
/* 初始化所有数据指针 */
skb->head = data;
skb->data = data;
skb->tail = data;
skb->end = data + size;
/*
* skb_shinfo是个宏,#define skb_shinfo(SKB) ((struct skb_shared_info *)((SKB)->end))
* 所以用这个宏的时候必须等skb->end已经初始化。
* skb_shinfo 接在skb->end指向的内存空间后面。
*/
/* 初始化skb_shared_info结构体 */
atomic_set(&(skb_shinfo(skb)->dataref), 1);
skb_shinfo(skb)->nr_frags = 0;
skb_shinfo(skb)->tso_size = 0;
skb_shinfo(skb)->tso_segs = 0;
skb_shinfo(skb)->frag_list = NULL;
out:
return skb;
nodata:
kmem_cache_free(skbuff_head_cache, skb);
skb = NULL;
goto out;
}
skb_drop_fraglist:
static void skb_drop_fraglist(struct sk_buff *skb)
{
struct sk_buff *list = skb_shinfo(skb)->frag_list;
skb_shinfo(skb)->frag_list = NULL;
/* 循环前进,直到没有为止。 */
do {
struct sk_buff *this = list;
list = list->next;
kfree_skb(this);
} while (list);
}
skb_clone_fraglist:
static void skb_clone_fraglist(struct sk_buff *skb)
{
struct sk_buff *list;
/* 对当前skb的frag_list区链上的每个skb增加引用计数。 */
for (list = skb_shinfo(skb)->frag_list; list; list = list->next)
skb_get(list);
}
__kfree_skb:
void __kfree_skb(struct sk_buff *skb)
{
skb_release_all(skb);
kfree_skbmem(skb);
}
__kfree_skb首先调用了skb_release_all,释放skb成员引用的其他结构体,和skb的数据区域(skb header, skb frag, skb fraglist)
/* Free everything but the sk_buff shell. */
static void skb_release_all(struct sk_buff *skb)
{
skb_release_head_state(skb);
skb_release_data(skb);
}
static void skb_release_head_state(struct sk_buff *skb)
{
/* 减少路由缓存的引用计数 */
skb_dst_drop(skb);
#ifdef CONFIG_XFRM
secpath_put(skb->sp);
#endif
/* 如果有析构,调用析构函数 */
if (skb->destructor) {
WARN_ON(in_irq());
skb->destructor(skb);
}
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
nf_conntrack_put(skb->nfct);
nf_conntrack_put_reasm(skb->nfct_reasm);
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
nf_bridge_put(skb->nf_bridge);
#endif
/* XXX: IS this still necessary? - JHS */
#ifdef CONFIG_NET_SCHED
skb->tc_index = 0;
#ifdef CONFIG_NET_CLS_ACT
skb->tc_verd = 0;
#endif
#endif
}
void skb_release_data(struct sk_buff *skb)kfree_skbmem :
{
/* 查看skb是否被clone?skb_shinfo的dataref是否为0?
* 如果是,那么就释放skb非线性区域和线性区域。 */
if (!skb->cloned ||
!atomic_sub_return(skb->nohdr ? (1 << SKB_DATAREF_SHIFT) + 1 : 1,
&skb_shinfo(skb)->dataref)) {
/* 释放page frags区 */
if (skb_shinfo(skb)->nr_frags) {
int i;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
put_page(skb_shinfo(skb)->frags[i].page);
}
/* 释放frag_list区 */
if (skb_shinfo(skb)->frag_list)
skb_drop_fraglist(skb);
/* 释放线性区域 */
kfree(skb->head);
}
}
kfree_skbmem用来通过slab allocator释放skb结构体
static void kfree_skbmem(struct sk_buff *skb)
{
struct sk_buff *other;
atomic_t *fclone_ref;
switch (skb->fclone) {
/* 如果是skbuff_head_cache分配的skb,那么直接通过slab释放掉 */
case SKB_FCLONE_UNAVAILABLE:
kmem_cache_free(skbuff_head_cache, skb);
break;
/* 如果是skbuff_fclone_cache分配的skb,那么查看fclone_ref,只有为1才能通过skbuff_fclone_cache释放整个结构体(值为1说明只有第一个skb)*/
case SKB_FCLONE_ORIG:
fclone_ref = (atomic_t *) (skb + 2);
if (atomic_dec_and_test(fclone_ref))
kmem_cache_free(skbuff_fclone_cache, skb);
break;
/*
如果是skbuff_fclone_cache分配的结构体中的第二个skb,首先把skb->fclone置为无效,接下来查看fclone_ref,只有为1才能通过skbuff_fclone_cache释放整个结构体(值为1说明只有第二个skb)
*/
case SKB_FCLONE_CLONE:
fclone_ref = (atomic_t *) (skb + 1);
other = skb - 1;
/* The clone portion is available for
* fast-cloning again.
*/
skb->fclone = SKB_FCLONE_UNAVAILABLE;
if (atomic_dec_and_test(fclone_ref))
kmem_cache_free(skbuff_fclone_cache, other);
break;
}
}
skb_realloc_headroom:
struct sk_buff *skb_realloc_headroom(struct sk_buff *skb, unsigned int headroom)
{
struct sk_buff *skb2;
/* 计算现在要求的headroom 和原来headroom之间的差值 */
int delta = headroom - skb_headroom(skb);
/* 如果现在要求的headroom没有原来的headroom大,那说明原来的header section可以用,
* 所以只要用pskb_copy复制一份skb结构体和它的线性区域就可以了。
*/
if (delta <= 0)
skb2 = pskb_copy(skb, GFP_ATOMIC);
else {
/* 如果要求的headroom比原来的headroom大的话,clone一个skb */
skb2 = skb_clone(skb, GFP_ATOMIC);
/* 把新clone的skb用pskb_expand_head扩大headroom */
if (skb2 && pskb_expand_head(skb2, SKB_DATA_ALIGN(delta), 0,
GFP_ATOMIC)) {
kfree_skb(skb2);
skb2 = NULL;
}
}
return skb2;
}
skb_header_pointer:
函数从skb->data开始的offset处,把len长度的数据拷贝到buffer中
static inline void *skb_header_pointer(const struct sk_buff *skb, int offset,
int len, void *buffer)
{
int hlen = skb_headlen(skb);
if (hlen - offset >= len)
return skb->data + offset;
if (skb_copy_bits(skb, offset, buffer, len) < 0)
return NULL;
return buffer;
}
skb_headlen函数返回skb->len - skb->data_len,表示当前page中的skb数据大小(其中skb->len表示整个skb数据长度,由于skb会分散到多个page中,因此skb->data_len用来表示分散在其他页中的skb数据长度)
hlen - offset >= len 则表示当前page中的线性skb数据大于要拷贝的len大小,那么就很简单地返回skb->data + offset指针即可
否则就说明要拷贝的len大小的数据还有一部分放在了skb非当前page中,此时调用skb_copy_bits,该函数把skb->data + offset开始的len长度的数据拷贝到buffer头开始的空间中
skb_copy_bits:
该函数用来把skb->data开头偏移offset的len长度的数据拷贝到to指向的缓冲区,由于skb header线性区的数据可能会不够,也可能offset本身已经超过了当前skb header的线性区,因此该函数需要遍历skb在其他page里的数据,即遍历frags数组指向的page甚至frag_list指向的其他skb结构
int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len)
{
int start = skb_headlen(skb);
struct sk_buff *frag_iter;
int i, copy;
/* 如果offset > skb->len - len,说明offset位置错误 */
if (offset > (int)skb->len - len)
goto fault;
/* Copy header. */
/* 如果copy > 0,说明拷贝的数据有一部分在skb header中,否则拷贝的数据全部在非线性空间 */
if ((copy = start - offset) > 0) {
/* 如果copy > len,那么要拷贝的数据全部在skb header中了,此时把copy = len,否则copy <= len,所以只剩下了两种可能 copy == len, copy < len */
if (copy > len)
copy = len;
/* 调用memcpy把skb header中offset开始的copy字节拷贝到to指向的内存区域 */
skb_copy_from_linear_data_offset(skb, offset, to, copy);
/* 如果copy == len,那么拷贝已经完成了,返回,否则len减去copy的长度,因为这部分已经被拷贝到to里面了 */
if ((len -= copy) == 0)
return 0;
/* 继续拷贝剩余的部分,此时offset从非线性区开始算起,目的地址to也顺势偏移copy个字节 */
offset += copy;
to += copy;
}
/* 开始遍历skb frag数组 */
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end;
WARN_ON(start > offset + len);
/* end为之前计算的长度加上当前frag的大小 */
end = start + skb_shinfo(skb)->frags[i].size;
/* 如果copy > 0,说明还有数据等待拷贝 */
if ((copy = end - offset) > 0) {
u8 *vaddr;
/* 如果copy > len,那么要拷贝的数据全部在当前frag中了,此时把copy = len,否则copy <= len,所以只剩下了两种可能 copy == len, copy < len */
if (copy > len)
copy = len;
/* kmap_skb_frag/kunmap_skb_frag调用kmap_atomic/kunmap_atomic为可能的高端内存地址建立虚拟地址的临时映射 */
vaddr = kmap_skb_frag(&skb_shinfo(skb)->frags[i]);
memcpy(to,
vaddr + skb_shinfo(skb)->frags[i].page_offset+
offset - start, copy);
kunmap_skb_frag(vaddr);
/* 如果copy == len,那么拷贝已经完成了,返回,否则len减去copy的长度,因为这部分已经被拷贝到to里面了 */
if ((len -= copy) == 0)
return 0;
/* 继续增加offset, to的位置 */
offset += copy;
to += copy;
}
start = end;
}
/* 开始遍历frag list链表 */
skb_walk_frags(skb, frag_iter) {
int end;
WARN_ON(start > offset + len);
end = start + frag_iter->len;
if ((copy = end - offset) > 0) {
if (copy > len)
copy = len;
/* 递归调用skb_copy_bits,拷贝copy大小的字节到to指向的内存区域 */
if (skb_copy_bits(frag_iter, offset - start, to, copy))
goto fault;
if ((len -= copy) == 0)
return 0;
offset += copy;
to += copy;
}
start = end;
}
/* 此时len应该为0,否则返回-EFAULT */
if (!len)
return 0;
fault:
return -EFAULT;
}
EXPORT_SYMBOL(skb_copy_bits);
pskb_may_pull:
static inline int pskb_may_pull(struct sk_buff *skb, unsigned int len)
{
if (likely(len <= skb_headlen(skb)))
return 1;
if (unlikely(len > skb->len))
return 0;
return __pskb_pull_tail(skb, len - skb_headlen(skb)) != NULL;
}
pskb_may_pull函数用来保证skb的线性数据长度至少有len的大小。这里的判断逻辑为
1. 如果skb_headlen,也就是skb->len - skb->data_len,即skb当前page线性空间的大小已经超过了len,啥事情都不需要做
2. 如果len比整个skb的数据长度还要长,直接报错
3. 这个分支表示需要数据拷贝,即从其他页拷贝到线性空间页,调用__pskb_pull_tail来完成
__pskb_pull_tail:可以参考 http://blog.chinaunix.net/uid-22577711-id-3220155.html 的说明
/**
* __pskb_pull_tail - advance tail of skb header
* @skb: buffer to reallocate
* @delta: number of bytes to advance tail
*
* The function makes a sense only on a fragmented &sk_buff,
* it expands header moving its tail forward and copying necessary
* data from fragmented part.
*
* &sk_buff MUST have reference count of 1.
*
* Returns %NULL (and &sk_buff does not change) if pull failed
* or value of new tail of skb in the case of success.
*
* All the pointers pointing into skb header may change and must be
* reloaded after call to this function.
*/
/* Moves tail of skb head forward, copying data from fragmented part,
* when it is necessary.
* 1. It may fail due to malloc failure.
* 2. It may change skb pointers.
*
* It is pretty complicated. Luckily, it is called only in exceptional cases.
*/
unsigned char *__pskb_pull_tail(struct sk_buff *skb, int delta)
{
/* If skb has not enough free space at tail, get new one
* plus 128 bytes for future expansions. If we have enough
* room at tail, reallocate without expansion only if skb is cloned.
*/
int i, k, eat = (skb->tail + delta) - skb->end;
/* 如果skb是克隆过的,这时候要调用pskb_expand_head了,同时顺便扩充下skb header的空间。调用pskb_expand_head之后skb->cloned=0, skb_shareinfo(skb)->dataref = 1 */
if (eat > 0 || skb_cloned(skb)) {
if (pskb_expand_head(skb, 0, eat > 0 ? eat + 128 : 0,
GFP_ATOMIC))
return NULL;
}
/* skb header的线性空间已经准备好了,下面把skb->tail之后长度delta的数据,拷贝到skb header偏移从skb_headlen(skb)开始的地方 */
if (skb_copy_bits(skb, skb_headlen(skb), skb_tail_pointer(skb), delta))
BUG();
/* 下面的工作就是去frag, fraglist里收拾残局了 */
/* Optimization: no fragments, no reasons to preestimate
* size of pulled pages. Superb.
*/
/* 如果frag list为空,那么只需要pull frags即可 */
if (!skb_has_frags(skb))
goto pull_pages;
/* Estimate size of pulled pages. */
eat = delta;
/*
eat = delta,为总共要pull的大小,之后遍历frags数组,如果frags数组中frag size的总和超过了delta,说明在frags中就可以把数据pull完
*/
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
if (skb_shinfo(skb)->frags[i].size >= eat)
goto pull_pages;
eat -= skb_shinfo(skb)->frags[i].size;
}
/* If we need update frag list, we are in troubles.
* Certainly, it possible to add an offset to skb data,
* but taking into account that pulling is expected to
* be very rare operation, it is worth to fight against
* further bloating skb head and crucify ourselves here instead.
* Pure masohism, indeed. 8)8)
*/
if (eat) {
struct sk_buff *list = skb_shinfo(skb)->frag_list;
struct sk_buff *clone = NULL;
struct sk_buff *insp = NULL;
/* 如果除了frags之外,还需要pull fraglist,那么麻烦就大了,好吧开始遍历frag list */
do {
BUG_ON(!list);
if (list->len <= eat) {
/* Eaten as whole. */
eat -= list->len;
list = list->next;
/* insp记录了第一个未被eat过的skb */
insp = list;
} else {
/* Eaten partially. */
/* 这个skb还是被shared的,那只能clone一个出来,因为需要修改sk_buff */
if (skb_shared(list)) {
/* Sucks! We need to fork list. :-( */
clone = skb_clone(list, GFP_ATOMIC);
if (!clone)
return NULL;
/* insp记录了第一个未被eat过的skb */
insp = list->next;
/* list指向的skb由于被clone过,因此是需要被调用kfree_skb的 */
list = clone;
} else {
/* This may be pulled without
* problems. */
/* insp这里记录了被部分eat的skb */
insp = list;
}
/* 这里pskb_pull实际递归调用了__pskb_pull_tail,因为有eat长度的部分要被pull掉,把这部分数据pull到skb header中 */
/* 这里似乎存在一个bug,即list为clone过的skb时,此时再调用pskb_pull会对被clone的skb产生影响,使其数据不正确 */
if (!pskb_pull(list, eat)) {
kfree_skb(clone);
return NULL;
}
break;
}
} while (eat);
/* 下面释放已经被pull掉数据的frag list中的skb,但是clone的场景中,cloned skb只是被pull掉了部分eat长度的数据,因此需要最后再找回来插入到frag list开头 */
/* Free pulled out fragments. */
while ((list = skb_shinfo(skb)->frag_list) != insp) {
skb_shinfo(skb)->frag_list = list->next;
kfree_skb(list);
}
/* And insert new clone at head. */
if (clone) {
clone->next = list;
skb_shinfo(skb)->frag_list = clone;
}
}
/* Success! Now we may commit changes to skb data. */
pull_pages:
/* 好了,开始处理frags数组 */
eat = delta;
k = 0;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
if (skb_shinfo(skb)->frags[i].size <= eat) {
put_page(skb_shinfo(skb)->frags[i].page);
eat -= skb_shinfo(skb)->frags[i].size;
} else {
/* 好了,找到了最后一个被部分eat的frags[i],下面依次把后面没有被eat过的frags[i],frags[i+1] ... frags[nr_frags]拷贝到frags[0],frags[1] ... frags[k] */
skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i];
if (eat) {
/* 对于被部分eat的frags[i],在拷贝给frags[0]之后,还需要修改page_offset, size */
skb_shinfo(skb)->frags[k].page_offset += eat;
skb_shinfo(skb)->frags[k].size -= eat;
eat = 0;
}
k++;
}
}
skb_shinfo(skb)->nr_frags = k;
skb->tail += delta;
skb->data_len -= delta;
return skb_tail_pointer(skb);
}
EXPORT_SYMBOL(__pskb_pull_tail);
pskb_expand_head:pskb_expand_head用来扩展skb head的区域,如果nhead, ntail为0,那么相当于复制一个skb head出来
int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail,
gfp_t gfp_mask)
{
int i;
u8 *data;
#ifdef NET_SKBUFF_DATA_USES_OFFSET
int size = nhead + skb->end + ntail;
#else
int size = nhead + (skb->end - skb->head) + ntail;
#endif
long off;
BUG_ON(nhead < 0);
if (skb_shared(skb))
BUG();
size = SKB_DATA_ALIGN(size);
data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
if (!data)
goto nodata;
/* 把sk_buff的成员拷贝到新的内存去 */
#ifdef NET_SKBUFF_DATA_USES_OFFSET
memcpy(data + nhead, skb->head, skb->tail);
#else
memcpy(data + nhead, skb->head, skb->tail - skb->head);
#endif
memcpy(data + size, skb_end_pointer(skb),
sizeof(struct skb_shared_info));
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
get_page(skb_shinfo(skb)->frags[i].page); /* 遍历skb_shinfo(skb)->frags数组的所有page,增加page的refcnt */
if (skb_has_frags(skb)) /* 遍历skb的fragments链表,增加skb的refcnt */
skb_clone_fraglist(skb);
skb_release_data(skb); /* 释放skb的数据部分 */
off = (data + nhead) - skb->head;
skb->head = data;
skb->data += off;
#ifdef NET_SKBUFF_DATA_USES_OFFSET
skb->end = size;
off = nhead;
#else
skb->end = skb->head + size;
#endif
/* {transport,network,mac}_header and tail are relative to skb->head */
skb->tail += off;
skb->transport_header += off;
skb->network_header += off;
if (skb_mac_header_was_set(skb))
skb->mac_header += off;
skb->csum_start += nhead;
skb->cloned = 0;
skb->hdr_len = 0;
skb->nohdr = 0;
atomic_set(&skb_shinfo(skb)->dataref, 1);
return 0;
nodata:
return -ENOMEM;
}
pskb_expand_head用来扩充skb的空间大小。skb->head扩充nhead大小,skb->tail扩充ntail大小。
skb_make_writable: 该函数使得skb header至少有writable_len的数据可写
int skb_make_writable(struct sk_buff *skb, unsigned int writable_len)
{
if (writable_len > skb->len) /* 如果要写入的数据大小超过了skb->len即整个skb的大小,溢出返错 */
return 0;
/* Not exclusive use of packet? Must copy. */
if (!skb_cloned(skb)) { // 非cloned
if (writable_len <= skb_headlen(skb)) // 如果当前页skb线性空间能容纳要写入的数据长度,返回成功
return 1;
} else if (skb_clone_writable(skb, writable_len)) // cloned skb,则判断skb_headroom是否有空间容纳
return 1;
if (writable_len <= skb_headlen(skb))
writable_len = 0;
else
writable_len -= skb_headlen(skb);
return !!__pskb_pull_tail(skb, writable_len); //为线性缓冲区增加writable_len的空间
}
skb_push:
skb_push上移skb->data指针,该指针指向skb的数据区。此举增加skb数据部分的空间,但skb->data不能小于skb->head,后者代表了skb头部指针。
skb_pull: skb_pull实际调用__skb_pull
static inline unsigned char *__skb_pull(struct sk_buff *skb, unsigned int len)
{
skb->len -= len;
BUG_ON(skb->len < skb->data_len);
return skb->data += len;
}
skb_pull下移skb->data指针,之前需要保证线性数据区的长度足够
pskb_pull: pskb_pull实际调用__pskb_pull,__pskb_pull通过调用__pskb_pull_tail保证了header中有足够的空间容纳skb->data指针下移len位置
static inline unsigned char *__pskb_pull(struct sk_buff *skb, unsigned int len)
{
if (len > skb_headlen(skb) &&
!__pskb_pull_tail(skb, len - skb_headlen(skb)))
return NULL;
skb->len -= len;
return skb->data += len;
}
skb_put:
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;
if (unlikely(skb->tail > skb->end))
skb_over_panic(skb, len, __builtin_return_address(0));
return tmp;
}
skb_put增加skb->tail的值,skb->tail代表一个偏移量。需要保证skb->tail的偏移不超过skb->end代表的偏移。
skb_clone:
skb_clone实际调用的__skb_clone。__skb_clone只复制sk_buff结构,不复制skb数据部分,两个sk_buff结构指向同一个数据缓冲区
static struct sk_buff *__skb_clone(struct sk_buff *n, struct sk_buff *skb)
{
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(tail);
C(end);
C(head);
C(data);
C(truesize);
atomic_set(&n->users, 1);
atomic_inc(&(skb_shinfo(skb)->dataref));
skb->cloned = 1;
return n;
}
skb的引用计数为1,skb->cloned设为1。skb_shinfo(skb)->dataref增加1,因为有两个skb指向这块数据缓冲区。
pskb_copy:
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); /* 分配skb结构和线性数据缓冲区,数据缓冲区size为skb->end的值 */
#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); /* skb_reserve预留skb headroom空间 */
/* Set the tail pointer and length */
skb_put(n, skb_headlen(skb)); /* skb_put下移skb->tail指针,预留skb_headlen(skb)大小的空间,即数据线性缓冲区大小的空间 */
/* Copy the bytes */
skb_copy_from_linear_data(skb, n->data, n->len); /* skb->data拷贝n->len长度的数据到n->data指向的空间 */
n->truesize += skb->data_len;
n->data_len = skb->data_len;
n->len = skb->len;
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); /* 只增加refcnt */
}
skb_shinfo(n)->nr_frags = i;
}
if (skb_has_frags(skb)) {
skb_shinfo(n)->frag_list = skb_shinfo(skb)->frag_list;
skb_clone_fraglist(n);
}
copy_skb_header(n, skb); /* copy_skb_header只复制sk_buff结构内容 */
out:
return n;
}
pskb_copy只复制skb和数据线性缓冲区的部分,不包括frag list以及skb list
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;
}
skb_copy是把skb的所有数据全部拷贝到新skb的线性数据缓冲区中,之前即使有frag list或者skb list,也会一并merge到线性内存中
pskb_trim:
pskb_trim把skb的长度trim到指定的len。如果len > skb->len,那么直接返回0,否则调用__pskb_trim
static inline int __pskb_trim(struct sk_buff *skb, unsigned int len)__skb_trim 调用 skb_set_tail_pointer 把 skb->tail 赋值给 skb->data + len 的位置
{
/* 如果skb->data_len不为0,说明有非线性部分,此时调用 ___pskb_trim */
if (skb->data_len)
return ___pskb_trim(skb, len);
/* 否则skb只有线性部分,调用__skb_trim */
__skb_trim(skb, len);
return 0;
}
static inline void __skb_trim(struct sk_buff *skb, unsigned int len)__pskb_trim 有一点复杂
{
if (unlikely(skb->data_len)) {
WARN_ON(1);
return;
}
skb->len = len;
skb_set_tail_pointer(skb, len);
}
int ___pskb_trim(struct sk_buff *skb, unsigned int len)
{
struct sk_buff **fragp;
struct sk_buff *frag;
int offset = skb_headlen(skb); /* offset设置为skb header长度 */
int nfrags = skb_shinfo(skb)->nr_frags;
int i;
int err;
/*
如果skb是克隆过的,尝试调用pskb_expand_head分离出一个独立的skb,此时skb->head指向新分配的header线性区域
这里阐述下skb clone的细节:
1) skb可以从两种slab allocator中来分配,skbuff_head_cache分配的是一个struct sk_buff,而skbuff_fclone_cache则是两个struct sk_buff加一个atomic_t的结构
2) skbuff_fclone_cache分配出来的第一个skb是主体,skb->fclone = SKB_FCLONE_ORIG,第二个skb是留给未来的cloned skb,skb->fclone = SKB_CLONE_UNAVAILABLE,如果未来调用了skb_clone,则把第二个skb->fclone设为SKB_FCLONE_CLONE。最后一个atomic_t* fclone_ref,记录了克隆次数
3) skbuff_head_cache分配的skb,有skb->flone = SKB_FCLONE_UNAVAILABLE
4) 无论是哪种skb,都会通过__skb_clone,把orig skb内容复制到dst skb中,两个skb会指向同一个skb header线性数据区域(包括同样的frag和frag list)。这样header区域的dataref会增加(skb_shinfo(skb)->dataref),orig skb的skb->cloned设为1,dst skb的skb->users设为1
5) skb->users和skb克隆没半毛钱关系,是表示skb被引用的次数(只有skb->users为0才可以释放)
总结下来:
1) skb_clone克隆一个skb出来,和老的skb共享skb header和skb frag, skb fraglist
2) pskb_copy复制一个skb连同skb header出来,但skb frag, skb fraglist依旧共享
3) skb_copy完全复制一个skb的内容,包括skb frag, skb fraglist
*/
if (skb_cloned(skb) &&
unlikely((err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC))))
return err;
i = 0;
if (offset >= len) /* 如果offset >= len,说明trim的目的length范围已经在skb header之内了,那么可以drop所有的frag, fraglist */
goto drop_pages;
/*
下面的循环,遍历skb frag,找到len之后的frag后全部trim掉
*/
for (; i < nfrags; i++) {
int end = offset + skb_shinfo(skb)->frags[i].size;
if (end < len) {
offset = end;
continue;
}
/* 最后一个frag只取其长度0 -> len - offset的部分 */
skb_shinfo(skb)->frags[i++].size = len - offset;
drop_pages:
skb_shinfo(skb)->nr_frags = i;
/* 对接下来的frag,调用put_page,最后调用skb_drop_fraglist释放skb fraglist */
for (; i < nfrags; i++)
put_page(skb_shinfo(skb)->frags[i].page);
if (skb_has_frags(skb))
skb_drop_fraglist(skb);
goto done;
}
/*
遍历frag list,trim掉多余的skb
*/
for (fragp = &skb_shinfo(skb)->frag_list; (frag = *fragp);
fragp = &frag->next) {
int end = offset + frag->len;
/* 如果skb有多个引用,此时skb_clone出一个nskb,但skb, nskb还指向同一个线性区域 */
if (skb_shared(frag)) {
struct sk_buff *nfrag;
nfrag = skb_clone(frag, GFP_ATOMIC);
if (unlikely(!nfrag))
return -ENOMEM;
nfrag->next = frag->next;
kfree_skb(frag);
frag = nfrag;
*fragp = frag;
}
if (end < len) {
offset = end;
continue;
}
/* 递归调用pskb_trim,裁剪最后一个fraglist skb的长度 */
if (end > len &&
unlikely((err = pskb_trim(frag, len - offset))))
return err;
/* drop掉剩余的fraglist */
if (frag->next)
skb_drop_list(&frag->next);
break;
}
done:
/* 如果 len > skb_headlen(skb),说明还有非线性部分,这时修正一下skb->data_len, skb->len的值即可 */
if (len > skb_headlen(skb)) {
skb->data_len -= skb->len - len;
skb->len = len;
} else {
/* 此时只有skb header了,那么skb->data_len自然为0,修正skb->len的值,调整skb->tail指针位置 */
skb->len = len;
skb->data_len = 0;
skb_set_tail_pointer(skb, len);
}
return 0;
}
EXPORT_SYMBOL(___pskb_trim);
skb_cow:
skb_cow实际调用了__skb_cow。skb_cow可以用在skb_clone之后,如果新clone的skb不想和orig skb共享skb header
static inline int __skb_cow(struct sk_buff *skb, unsigned int headroom,
int cloned)
{
int delta = 0;
/* 计算出新的headroom大小 */
if (headroom < NET_SKB_PAD)
headroom = NET_SKB_PAD;
if (headroom > skb_headroom(skb))
delta = headroom - skb_headroom(skb);
/* 如果headroom有变化,或者skb是skb_clone生成的,那么调用pskb_expand_head重新生成skb header */
if (delta || cloned)
return pskb_expand_head(skb, ALIGN(delta, NET_SKB_PAD), 0,
GFP_ATOMIC);
return 0;
}
/**
* skb_cow - copy header of skb when it is required
* @skb: buffer to cow
* @headroom: needed headroom
*
* If the skb passed lacks sufficient headroom or its data part
* is shared, data is reallocated. If reallocation fails, an error
* is returned and original skb is not changed.
*
* The result is skb with writable area skb->head...skb->tail
* and at least @headroom of space at head.
*/
static inline int skb_cow(struct sk_buff *skb, unsigned int headroom)
{
return __skb_cow(skb, headroom, skb_cloned(skb));
}