这个系列内容会有点杂,会涉及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) { /* 查看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 :
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->data_len不为0,说明有非线性部分,此时调用 ___pskb_trim */ if (skb->data_len) return ___pskb_trim(skb, len); /* 否则skb只有线性部分,调用__skb_trim */ __skb_trim(skb, len); return 0; }__skb_trim 调用 skb_set_tail_pointer 把 skb->tail 赋值给 skb->data + len 的位置
static inline void __skb_trim(struct sk_buff *skb, unsigned int len) { if (unlikely(skb->data_len)) { WARN_ON(1); return; } skb->len = len; skb_set_tail_pointer(skb, len); }__pskb_trim 有一点复杂
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)); }