【协议森林】深入浅出理解skb_buff

1.前言

在互联网技术里,有两件事最为重要,一个是 TCP/IP 协议,它是万物互联的事实标准;另一个是 Linux 操作系统,它是推动互联网技术走向繁荣的基石。在 Linux内核的协议栈中的实现中,数据结构skb_buff是最关键和最核心的数据,它表示接收或发送数据包的包头信息,并包含很多成员变量供网络代码中的各子系统使用。本文以及后续关于skb_buff的介绍,均来源于经典著作《深入理解linux网络技术内幕》和《linux内核源码剖析:TCP/IP实现》。
在这里插入图片描述

2.skb_buff基本原理

内核中sk_buff结构体在各层协议之间传输不是用拷贝sk_buff结构体,而是通过增加协议头和移动指针来操作的。如果是从L4传输到L2,则是通过往sk_buff结构体中增加该层协议头来操作;如果是从L4到L2,则是通过移动sk_buff结构体中的data指针来实现,不会删除各层协议头,这样方式极大的提高CPU工作效率。
在这里插入图片描述
sk_buff结构体是linux网络代码中最重要的数据结构,是整个网络传输载体。所以sk_buff结构体里面有很多关于其他功能的成员字段,比如:防火墙,子路由系统,多播等。这些字段并不是一定有的,只有在满足特点条件才有的。所以可以在需要时候再去关心这些成员字段,现在我们只来讲解主要的成员字段。

3.skb_buff主要字段

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

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来访问协议首部。</span></span>
				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->data_len:只计算分片中数据的长度,即是分片结构体中page指向的数据区长度。这个在分片结构体中会再详细讲解下。

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);

加入讨论

在这里插入图片描述

  • 3
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值