网络数据结构skb_buff原理

skb_buff基本原理

内核中sk_buff结构体在各层协议之间传输不是用拷贝sk_buff结构体,而是通过增加协议头和移动指针来操作的。如果是从L4传输到L2,则是通过往sk_buff结构体中增加该层协议头来操作;如果是从L4到L2,则是通过移动sk_buff结构体中的data指针来实现,不会删除各层协议头,这样方式极大的提高CPU工作效率。

sk_buff结构体是linux网络代码中最重要的数据结构,是整个网络传输载体。所以sk_buff结构体里面有很多关于其他功能的成员字段,比如:防火墙,子路由系统,多播等。这些字段并不是一定有的,只有在满足特点条件才有的。所以可以在需要时候再去关心这些成员字段,现在我们只来讲解主要的成员字段

网络数据结构skb_buff原理_第1张图片

skb_buff主要字段

为了好理解结构中的一些成员字段,先把后面要讲的内容提前说下。sk_buff结构体关联多个其他结构体,主要可以分为: 第一是数据区:由sk_buff中head和end指向的数据块,用来存储sk_buff结构的数据也即是存储数据包的内容和各层协议头。 第二是分片结构:用来表示IP分片的一个结构体,实则上是和sk_buff结构的数据区相连的,即是end指针的下一个字节开始就是分片结构。正因此,分片结构和sk_buff数据区内存分配及销毁时都是一起的。 第三个是分片结构指向的数据区,即是IP分片内容。

网络数据结构skb_buff原理_第2张图片

struct sk_buff {

/* These two members must be first. */

struct sk_buff  *next;  //  因为sk_buff结构体是双链表,所以有前驱后继。这是个指向后面的sk_buff结构体指针

struct sk_buff  *prev;  //  这是指向前一个sk_buff结构体指针

//老版本(2.6以前)应该还有个字段: sk_buff_head *list  //即每个sk_buff结构都有个指针指向头节点

struct sock *sk;  // 指向拥有此缓冲的套接字sock结构体,即:宿主传输控制模块

ktime_t tstamp;  // 时间戳,表示这个skb的接收到的时间,一般是在包从驱动中往二层发送的接口函数中设置

struct net_device *dev;  // 表示一个网络设备,当skb为输出/输入时,dev表示要输出/输入到的设备

unsigned long  skb_dst;  // 主要用于路由子系统,保存路由有关的东西

char cb[48];  // 保存每层的控制信息,每一层的私有信息

unsigned int len,  // 表示数据区的长度(tail - data)与分片结构体数据区的长度之和。其实这个len中数据区长度是个有效长度,因为不删除协议头,所以只计算有效协议头和包内容。如:当在L3时,不会计算L2的协议头长度。

data_len;  // 只表示分片结构体数据区的长度,所以len = (tail - data) + data_len;

__u16 mac_len,  // mac报头的长度

hdr_len;  // 用于clone时,表示clone的skb的头长度

// 接下来是校验相关域,这里就不详细讲了。

__u32 priority;  // 优先级,主要用于QOS

kmemcheck_bitfield_begin(flags1);

__u8 local_df:1,  // 是否可以本地切片的标志

cloned:1,  // 为1表示该结构被克隆,或者自己是个克隆的结构体;同理被克隆时,自身skb和克隆skb的cloned都要置1

ip_summed:2,

nohdr:1,  // nohdr标识payload是否被单独引用,不存在协议首部。                                                                                                      // 如果被引用,则决不能再修改协议首部,也不能通过skb->data来访问协议首部。

nfctinfo:3;

__u8 pkt_type:3,  // 标记帧的类型

fclone:2,   // 这个成员字段是克隆时使用,表示克隆状态

ipvs_property:1,

peeked:1,

nf_trace:1;

__be16 protocol:16;  // 这是包的协议类型,标识是IP包还是ARP包或者其他数据包。

kmemcheck_bitfield_end(flags1);

void (*destructor)(struct sk_buff *skb);  // 这是析构函数,后期在skb内存销毁时会用到

#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)

struct nf_conntrack *nfct;

struct sk_buff *nfct_reasm;

#endif

#ifdef CONFIG_BRIDGE_NETFILTER

struct nf_bridge_info *nf_bridge;

#endif

int iif;  // 接受设备的index

#ifdef CONFIG_NET_SCHED

__u16 tc_index; /* traffic control index */

#ifdef CONFIG_NET_CLS_ACT

__u16 tc_verd; /* traffic control verdict */

#endif

#endif

kmemcheck_bitfield_begin(flags2);

__u16 queue_mapping:16;

#ifdef CONFIG_IPV6_NDISC_NODETYPE

__u8 ndisc_nodetype:2;

#endif

kmemcheck_bitfield_end(flags2);

/* 0/14 bit hole */

#ifdef CONFIG_NET_DMA

dma_cookie_t dma_cookie;

#endif

#ifdef CONFIG_NETWORK_SECMARK

__u32 secmark;

#endif

__u32 mark;

__u16 vlan_tci;

sk_buff_data_t transport_header;      // 指向四层帧头结构体指针

sk_buff_data_t network_header;        // 指向三层IP头结构体指针

sk_buff_data_t mac_header;        // 指向二层mac头的头

/* These elements must be at the end, see alloc_skb() for details.  */

sk_buff_data_t tail;   // 指向数据区中实际数据结束的位置

sk_buff_data_t end;   // 指向数据区中结束的位置(非实际数据区域结束位置)

unsigned char *head, //指向数据区中开始的位置(非实际数据区域开始位置)

*data;   // 指向数据区中实际数据开始的位置

unsigned int truesize;   // 表示总长度,包括sk_buff自身长度和数据区以及分片结构体的数据区长度

atomic_t users;           // skb被克隆引用的次数,在内存申请和克隆时会用到

};   //end sk_buff

char cb[48];这个字段是skb信息控制块,也就是存储每层的一些协议信息,当数据包在哪一层时,存储的就是哪一层协议信息。这个字段由数据包所在层使用和维护,如果要访问本层协议信息,可以通过用一些宏来操作这个成员字段。如:#define TCP_SKB_CB(__skb) ((struct tcp_skb_cb *)&((__skb)->cb[0]))

_u8 fclone:2;这是个克隆状态标志,到sk_buff结构内存申请时会使用到。这里提前讲下:若fclone = SKB_FCLONE_UNAVAILABLE,则表明SKB未被克隆;若fclone = SKB_FCLONE_ORIG,则表明是从skbuff_fclone_cache缓存池(这个缓存池上分配内存时,每次都分配一对skb内存)中分配的父skb,可以被克隆;若fclone = SKB_FCLONE_CLONE,则表明是在skbuff_fclone_cache分配的子SKB,从父SKB克隆得到的;

atomic_t users;这是个引用计数,表明了有多少实体引用了这个skb。其作用就是在销毁skb结构体时,先查看下users是否为零,若不为零,则调用函数递减下引用计数users即可;当某一次销毁时,users为零才真正释放内存空间。有两个操作函数:atomic_inc()引用计数增加1;atomic_dec()引用计数减去1;

sk_buff->len:表示当前缓冲区中数据块的大小的总长度。它包括主缓冲中(即是sk_buff结构中指针data指向)的数据区的实际长度(data-tail)和分片中的数据长度。这个长度在数据包在各层间传输时会改变,因为分片数据长度不变,从L2到L4时,则len要减去帧头大小和网络头大小;从L4到L2则相反,要加上帧头和网络头大小。所以:len = (data - tail) + data_len;

sk_buff->truesize:这是缓冲区的总长度,包括sk_buff结构和数据部分。如果申请一个len字节的缓冲区,alloc_skb函数会把它初始化成len+sizeof(sk_buff)。当skb->len变化时,这个变量也会变化。所以:truesize = len + sizeof(sk_buff) = (data - tail) + data_len + sizeof(sk_buff);

你可能感兴趣的:(计算机网络,网络,数据结构,服务器)