sk_buff结构可能是网络代码中最重要的数据结构。代表已接收或正要传输的数据报。定义在include/linux/skbuff.h头文件中。由变量堆(heap)组成。用于管理网络数据包。操作sk_buff的函数定义在net/core/skbuff.c中。

当网络包被内核接收处理时,底层协议的数据被传送高层,当数据传送时,过程反过来。sk_buff在网络实现层交换数据而不用拷贝来或去数据包,可以显著获得速度收益。

一个 skb 表示 Linux 网络栈中的一个 packetTCP 分段和 IP 分组生产的多个 skb 被一个 skb list 形式来保存。

当从上层往下层,或下层网上层传递时候,并不复制数据报,而是在缓冲区中操作增减报头而已,非常高效。

        1. 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 stackRB树节点 */

        };

        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.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.1.3 sk_buff关系

sk_buff在一个双链表中,没有使用内核标准的标准链表。

sk_buff之间的关系如下图:

Linux协议栈(4)——sk_buff及代码 Linux协议栈(4)——sk_buff及代码_套接字

        1. 1.1.1.4 sk_buff函数

skb链表的管理函数

主要位于include/linux/skbuff.hskbuff.c文件中。

alloc_skb

alloc_skb函数分配套接字缓冲区。分配两块内存,一块是数据缓存区,另一块是SKB描述符。

dev_alloc_skb

  dev_alloc_skb也是缓存区分配函数,通常用在被设备驱动用在的中断上下文中。是alloc_skb的封装函数。

kfree_skb

kfree_skb释放数据包占用的套接字缓冲区。返回给高速缓存。

dev_kfree_skb

dev_kfree_skbkfree_skb的封装函数。

skb_reserver函数

  当缓冲区往下传经每个分层时,调用skb_reserver函数为该协议的报头预留空间。

  实现在数据缓存区头部预留一定的空间。被用来在数据缓存区中插入协议首部或者某个边界上对齐。更新数据缓存区的两个指针,分别指向负载起始和结尾的datatail指针。

skb_push

  skb_push在数据缓存区的前头加入一块数据。也是移动datatail指针。

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_headskb_queue_tailSKB加入到队列的头首部和尾部。

skb_dequeueskb_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

根据指定长度重新扩展headroomtailroom空间。

skb_shared_info

  用于管理套接字缓冲区的数据包分片信息。在数据缓存区的末尾,即end指针所指向的地址起紧跟着有一个skb_shared_info结构。保存了数据块的附加信息。

sk_buff结构中并没有指向skb_shared_info结构的指针,可以用skb_info宏来访问skb_shared_info结构。