sk_buff结构可能是网络代码中最重要的数据结构。代表已接收或正要传输的数据报。定义在include/linux/skbuff.h头文件中。由变量堆(heap)组成。用于管理网络数据包。操作sk_buff的函数定义在net/core/skbuff.c中。
当网络包被内核接收处理时,底层协议的数据被传送高层,当数据传送时,过程反过来。sk_buff在网络实现层交换数据而不用拷贝来或去数据包,可以显著获得速度收益。
一个 skb 表示 Linux 网络栈中的一个 packet,TCP 分段和 IP 分组生产的多个 skb 被一个 skb list 形式来保存。
当从上层往下层,或下层网上层传递时候,并不复制数据报,而是在缓冲区中操作增减报头而已,非常高效。
-
-
-
- 1.1.1.1 sk_buff定义
-
-
具体如下,差不多有两页左右:
struct sk_buff {
union {
struct {
/* These two members must be first. */
struct sk_buff *next;//列表中下一个buffer
struct sk_buff *prev;//列表中上一个buffer
union {
ktime_t tstamp;//分组到达或离开的时间
u64 skb_mstamp;
};
};
struct rb_node rbnode; /* used in netem & tcp stack,RB树节点 */
};
struct sock *sk;//指针,指向拥有此缓冲区套接字的sock数据结构。当缓冲区只是转发则不需要设置为NULL.
union {
struct net_device *dev;//处理分组的网络设备
/* Some protocols might use this space to store information,
* while device pointer would be NULL.
* UDP receive path is one user.
*/
unsigned long dev_scratch;
};
/*
* This is the control buffer. It is free to use for every
* layer. Please put your private variables there. If you
* want to keep them across layers you have to do a skb_clone()
* first. This is owned by whoever has the skb queued ATM.
*/
char cb[48] __aligned(8);//控制缓存,给每层使用,可以将私有变量放在此处。如果要跨越不同层,就需要调用skb_clone.
unsigned long _skb_refdst;//目标入口(with norefcount bit)
void (*destructor)(struct sk_buff *skb);// Destruct function
#ifdef CONFIG_XFRM
struct sec_path *sp;//安全路径,给xfrm使用
#endif
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
unsigned long _nfct;// Associated connection, if any (with nfctinfo bits)
#endif
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
struct nf_bridge_info *nf_bridge;// Saved data about a bridged frame - see br_netfilter.c
#endif
unsigned int len, //缓冲区中数据区块的大小。包括由head所指以及一些片段数据。当从一个分层移动到另一个分层时候回发生变化。协议报头也算在里面。
data_len;//只计算片段中的数据大小
__u16 mac_len,//mac报头的大小
hdr_len;//克隆的skb可写的头长度
/* Following fields are _not_ copied in __copy_skb_header()
* Note that queue_mapping is here mostly to fill a hole.
*/
kmemcheck_bitfield_begin(flags1);
__u16 queue_mapping;//多队列设备的队列映射
/* if you move cloned around you also must adapt those constants */
#ifdef __BIG_ENDIAN_BITFIELD
#define CLONED_MASK (1 << 7)
#else
#define CLONED_MASK 1
#endif
#define CLONED_OFFSET() offsetof(struct sk_buff, __cloned_offset)
__u8 __cloned_offset[0];
__u8 cloned:1,//头被复制(检测refcnt)
nohdr:1,// Payload reference only, must not modify header
fclone:2,// skbuff clone status
peeked:1,// this packet has been seen already, so stats have been done for it, don't do them again
head_frag:1,
xmit_more:1,//有更多的skb在这个队列中
__unused:1; /* one bit hole */
kmemcheck_bitfield_end(flags1);
/* fields enclosed in headers_start/headers_end are copied
* using a single memcpy() in __copy_skb_header()
*/
/* private: */
__u32 headers_start[0];
/* public: */
/* if you move pkt_type around you also must adapt those constants */
#ifdef __BIG_ENDIAN_BITFIELD
#define PKT_TYPE_MAX (7 << 5)
#else
#define PKT_TYPE_MAX 7
#endif
#define PKT_TYPE_OFFSET() offsetof(struct sk_buff, __pkt_type_offset)
__u8 __pkt_type_offset[0];
__u8 pkt_type:3;// 定义在include/uapi/linux/if_packet.h,共3位最多8个值。
__u8 pfmemalloc:1;
__u8 ignore_df:1;//允许本地分段
__u8 nf_trace:1;//netfilter packet trace flag
__u8 ip_summed:2;// Driver fed us an IP checksum
__u8 ooo_okay:1;// allow the mapping of a socket to a queue to be changed
__u8 l4_hash:1;// indicate hash is a canonical 4-tuple hash over transport port
__u8 sw_hash:1;// indicates hash was computed in software stack
__u8 wifi_acked_valid:1;// wifi_acked was set
__u8 wifi_acked:1;// whether frame was acked on wifi or not
__u8 no_fcs:1;// Request NIC to treat last 4 bytes as Ethernet FCS
/* Indicates the inner headers are valid in the skbuff. */
__u8 encapsulation:1;
__u8 encap_hdr_csum:1;
__u8 csum_valid:1;
__u8 csum_complete_sw:1;
__u8 csum_level:2;
__u8 csum_not_inet:1;// use CRC32c to resolve CHECKSUM_PARTIAL
__u8 dst_pending_confirm:1;// need to confirm neighbour
#ifdef CONFIG_IPV6_NDISC_NODETYPE
__u8 ndisc_nodetype:2;// router type (from link layer)
#endif
__u8 ipvs_property:1;// skbuff is owned by ipvs
__u8 inner_protocol_type:1;
__u8 remcsum_offload:1;
#ifdef CONFIG_NET_SWITCHDEV
__u8 offload_fwd_mark:1;
#endif
#ifdef CONFIG_NET_CLS_ACT
__u8 tc_skip_classify:1;// do not classify packet. set by IFB device
__u8 tc_at_ingress:1;// used within tc_classify to distinguish in/egress
__u8 tc_redirected:1;// packet was redirected by a tc action
__u8 tc_from_ingress:1;// if tc_redirected, tc_at_ingress at time of redirect
#endif
#ifdef CONFIG_NET_SCHED
__u16 tc_index; /* traffic control index */
#endif
union {
__wsum csum;//检验码,必须包括开始/偏移
struct {
__u16 csum_start;//Offset from skb->head where checksumming should start
__u16 csum_offset;//Offset from csum_start where checksum should be stored
};
};
__u32 priority;//表示正被传输或转发的封包QoS. 如果在本地产生,套接字层会定义优先级值。如果转发,会根据IP报头的ToS设置此字段的值。
int skb_iif;//输入设备的接口索引号
__u32 hash;//包的哈希值
__be16 vlan_proto;// vlan encapsulation protocol
__u16 vlan_tci;// vlan tag control information
#if defined(CONFIG_NET_RX_BUSY_POLL) || defined(CONFIG_XPS)
union {
unsigned int napi_id;// id of the NAPI struct this skb came from
unsigned int sender_cpu;
};
#endif
#ifdef CONFIG_NETWORK_SECMARK
__u32 secmark;// security marking
#endif
union {
__u32 mark;// Generic packet mark
__u32 reserved_tailroom;
};
union {
__be16 inner_protocol;
__u8 inner_ipproto;
};
__u16 inner_transport_header;
__u16 inner_network_header;
__u16 inner_mac_header;
__be16 protocol;//下一个较高层的协议,例如IP,IPv6,ARP.每种协议都有自己的函数处理例程用来处理输入的封包。驱动程序用这个字段通知上层该使用哪个处理例程。
__u16 transport_header;//传输层头
__u16 network_header;//网络层头
__u16 mac_header;//链路层头
/* private: */
__u32 headers_end[0];
/* public: */
/* These elements must be at the end, see alloc_skb() for details. */
sk_buff_data_t tail;//尾指针
sk_buff_data_t end;//End 指针
unsigned char *head,//缓冲区头
*data;//数据头指针
unsigned int truesize;//此缓存区的大小,包括sk_buff本身结构大小。
refcount_t users;//引用计数,使用缓冲区实例的数目
};
-
-
-
- 1.1.1.2 sk_buff_head定义
-
-
用于管理套接字缓冲区。
双向列表头,定义如下:
struct sk_buff_head {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
__u32 qlen;
spinlock_t lock;
};
-
-
-
- 1.1.1.3 sk_buff关系
-
-
sk_buff在一个双链表中,没有使用内核标准的标准链表。
sk_buff之间的关系如下图:
-
-
-
- 1.1.1.4 sk_buff函数
-
-
skb链表的管理函数
主要位于include/linux/skbuff.h和skbuff.c文件中。
alloc_skb
alloc_skb函数分配套接字缓冲区。分配两块内存,一块是数据缓存区,另一块是SKB描述符。
dev_alloc_skb
dev_alloc_skb也是缓存区分配函数,通常用在被设备驱动用在的中断上下文中。是alloc_skb的封装函数。
kfree_skb
kfree_skb释放数据包占用的套接字缓冲区。返回给高速缓存。
dev_kfree_skb
dev_kfree_skb是kfree_skb的封装函数。
skb_reserver函数
当缓冲区往下传经每个分层时,调用skb_reserver函数为该协议的报头预留空间。
实现在数据缓存区头部预留一定的空间。被用来在数据缓存区中插入协议首部或者某个边界上对齐。更新数据缓存区的两个指针,分别指向负载起始和结尾的data和tail指针。
skb_push
skb_push在数据缓存区的前头加入一块数据。也是移动data和tail指针。
skb_put
skb_put修改指向数据区末尾的指针tail, 使之往下移len字节。使数据区向下扩大len字节,并更新数据区长度len。
skb_pull
skb_pull通过将data指针往下移动,在数据区首部忽略len字节长度的数据,用于接收到的数据包在各层间由下往上传递时,上层忽略下层的首部。
skb_clone
没有必要复制一份完整的SKB描述及其相应的数据缓存区,而会为了提高性能,只作克隆操作。复制SKB描述符,同时增加数据缓冲区的引用计数即可。
pskb_copy
当函数不仅要修改SKB描述符,而且还要修改数据缓存区中的数据时,需要同时复制数据缓存区。需要使用pskb_copy函数来复制这部分数据。
skb_copy
如果需要同时修改聚合分散I/O存储区中的数据,使用skb_copy。
skb链表管理函数
skb_queue_head_init函数用来初始化sk_buff_head结构。
skb_queue_head和skb_queue_tail将SKB加入到队列的头首部和尾部。
skb_dequeue和skb_dequeue_tail从队列的首部和尾部取一下SKB。
skb_queue_purge清空一个SKB链表
skb_queue_walk宏,定义一个for语句,来顺序遍历SKB链表中的每一个元素。
skb添加或删除尾部数据
skb_add_data
将用户空间的数据添加到SKB的数据缓存区的尾部。
skb_trim
根据指定长度删除SKB的数据缓存区尾部的数据。如果长度大于当前长度,则不作处理。前提是待操作的SKB数据必须是线性存储的。
pskb_trim
skb_trim函数的功能超集,不仅可以处理线性数据的SKB,还可以处理非线性的SKB。
skb_split
可以根据指定长度拆分SKB。原SKB中的数据长度为指定的长度,剩下的数据保存到拆分得到的SKB中。
pskb_expand_head
根据指定长度重新扩展headroom和tailroom空间。
skb_shared_info
用于管理套接字缓冲区的数据包分片信息。在数据缓存区的末尾,即end指针所指向的地址起紧跟着有一个skb_shared_info结构。保存了数据块的附加信息。
sk_buff结构中并没有指向skb_shared_info结构的指针,可以用skb_info宏来访问skb_shared_info结构。