linux网络内核源码路由查询,Linux内核网络源码解析1——sk_buff结构

Linux内核源码学习,套接字缓存sk_buff的结构体

前言

接下会有一系列阅读Linux内核网络的博客,主要是学习网络子系统,使用的Linux内核代码2.6.20,

主要的参考资料是樊东东老师写的《Linux内核源码剖析——TCP/IP实现》和网上的一些资料。

这一篇博客主要是解析sk_buff结构体,下一篇会解析有关它的操作函数

什么是sk_buff

sk_buff的意思是socket buffer,这是Linux网络子系统中的核心数据结构

skbuffs是Linux内核用来处理从网卡传来的网络包的缓冲

因此,在内核栈处理网络包的效率很重要:

+ 能很容易在头尾部添加和移除数据,因为这些缓存区需要在不同网络层次间进行传递

+ 能很方便地处理可变长缓存,因为接收和发送的数据包长度不是固定的

+ 每个协议都应该很方便访问他们的头部

+ 在添加和移除数据时能尽量避免数据的复制

include/linux/skbuff.h(sk_buff结构定义和sk_buff宏)

struct sk_buff {

/* These two members must be first. */

struct sk_buff*next;

struct sk_buff*prev;

struct sock*sk;

struct skb_timevaltstamp;

struct net_device*dev;

struct net_device*input_dev;

union {

struct tcphdr*th;

struct udphdr*uh;

struct icmphdr*icmph;

struct igmphdr*igmph;

struct iphdr*ipiph;

struct ipv6hdr*ipv6h;

unsigned char*raw;

} h;

union {

struct iphdr*iph;

struct ipv6hdr*ipv6h;

struct arphdr*arph;

unsigned char*raw;

} nh;

union {

unsigned char *raw;

} mac;

struct dst_entry*dst;

structsec_path*sp;

/*

* 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.

*/

charcb[48];

unsigned intlen,

data_len,

mac_len;

union {

__wsumcsum;

__u32csum_offset;

};

__u32priority;

__u8local_df:1,

cloned:1,

ip_summed:2,

nohdr:1,

nfctinfo:3;

__u8pkt_type:3,

fclone:2,

ipvs_property:1;

__be16protocol;

void(*destructor)(struct sk_buff *skb);

#ifdef CONFIG_NETFILTER

struct nf_conntrack*nfct;

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

struct sk_buff*nfct_reasm;

#endif

#ifdef CONFIG_BRIDGE_NETFILTER

struct nf_bridge_info*nf_bridge;

#endif

#endif /* CONFIG_NETFILTER */

#ifdef CONFIG_NET_SCHED

__u16tc_index;/* traffic control index */

#ifdef CONFIG_NET_CLS_ACT

__u16tc_verd;/* traffic control verdict */

#endif

#endif

#ifdef CONFIG_NET_DMA

dma_cookie_tdma_cookie;

#endif

#ifdef CONFIG_NETWORK_SECMARK

__u32secmark;

#endif

__u32mark;

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

unsigned inttruesize;

atomic_tusers;

unsigned char*head,

*data,

*tail,

*end;

};

看了那么长的结构体,估计都看晕了,下面来看张图:

72d76ff558dc1a5246f5a5de711c07e6.png

重要的成员都包含在图内了,接下来分别解析这些成员。

sk_buff组织结构

struct sk_buff*next;

struct sk_buff*prev

这两个成员很好理解,就是sk_buff是以双向链表来组织的

为了快速地从整个链表的头部找到每个sk_buff,在第一个sk_buff前面会插入一个辅助的头结点:

struct sk_buff_head {

/* These two members must be first. */

struct sk_buff*next;

struct sk_buff*prev;

__u32qlen;

spinlock_tlock;

};

qlen:链表长度

lock:用来控制sk_buff链表并发操作的自旋锁

传输相关

struct sock*sk;

struct skb_timevaltstamp;

struct net_device*dev;

struct net_device*input_dev;

sk指向拥有该sk_buff的传输控制块,只有在网络数据报文由本地发送或本地接收才有效,否则为NULL

tstamp,接收或发送时间戳

dev指向网络设备,作用与该sk_buff是发送包还是接收包有关。在初始化网络设备驱动,分配接收缓存队列时,将该指针指向收到数据包的网络设备

input_dev指向接收报文的原始网络设备,如果包是本地生成的,则该值为NLL,主要用于流量控制

协议头部指针

依次是传输层、网络层、mac层的头部指针,用union表示是互斥的,只能表示其中的一种

union {

struct tcphdr*th;

struct udphdr*uh;

struct icmphdr*icmph;

struct igmphdr*igmph;

struct iphdr*ipiph;

struct ipv6hdr*ipv6h;

unsigned char*raw;

} h;

union {

struct iphdr*iph;

struct ipv6hdr*ipv6h;

struct arphdr*arph;

unsigned char*raw;

} nh;

union {

unsigned char *raw;

} mac

路由相关

struct dst_entry*dst;

structsec_path*sp

dst_entry指向需要转发包的函数,这个指针在sk_buff在IP层传输前必须是合法的

sec_path是一个可选的有关网络安全的成员

信息控制块

charcb[48]

每层协议私有的信息存储空间,由每一层自己维护和使用,并只在本层有效

长度相关

unsigned intlen,

data_len,

mac_len;

union {

__wsumcsum;

__u32csum_offset;

};

__u32priority;

__u8local_df:1,

cloned:1,

ip_summed:2,

nohdr:1,

nfctinfo:3;

__u8pkt_type:3,

fclone:2,

ipvs_property:1;

__be16protocol;

void(*destructor)(struct sk_buff *skb);

前面几个长度在后面几个区域的操作函数会解释

在变量名后面加一个冒号?我也是第一次见到这种语法,这叫位域,具体可以看这个解释:

http://c.biancheng.net/view/2037.html

引用计数

atomic_tusers;

记录有多少引用指向这个sk_buff,该计数器只保护sk_buff描述符,要区别于skb_shared_info结构的dataref成员

通常使用skb_get()和kfree_skb()操作引用计数

数据区域指针

unsigned char*head,

*data,

*tail,

*end;

看个图更清楚:

0808e069dcfd5dde0bc9e8acd7ef50e4.png

之后介绍的几个函数主要就是移动这几个指针,改变这几个空间的大小

skb_shared_info

在sk_buff的数据缓冲区的末尾,即end指针所指向的地址起紧跟着有一个skb_shared_info结构,保存了数据块的附加信息

/* This data is invariant across clones and lives at

* the end of the header data, ie. at skb->end.

*/

struct skb_shared_info {

atomic_tdataref;

unsigned shortnr_frags;

unsigned shortgso_size;

/* Warning: this field is not always filled in (UFO)! */

unsigned shortgso_segs;

unsigned short gso_type;

__be32 ip6_frag_id;

struct sk_buff*frag_list;

skb_frag_tfrags[MAX_SKB_FRAGS];

};

dataref:当一个数据缓存区被多个skb_buff的描述符引用时,就会设置相应的计数,比如克隆一个sk_buff

之前skb_shared_info结构体有几个成员和聚合分散IO有关:

nr_frags、frag_list、frags与IP分片的存储有关

一般来说,数据都存储到线性区域中,即sk_buff几个区域指针指向的区域,但当为了支持聚合分散IO(后面会解释),数据需要存储在支持聚合分散IO的区域中

零拷贝技术

Linux的sendfile系统调用就用到了零拷贝技术,零拷贝可以减少数据复制和减少上下文切换的次数

比较read+write和open+sendfile的数据复制:

+ read+write

1. 从硬件的DMA缓存复制到内核缓存

2. 从内核缓存复制到用户缓存

3. 从用户缓存复制到内核缓存

4. 从内核缓存复制到DMA缓存

+ open+sendfile

1. 数据通过DMA被复制到内核缓存区

2. 由于数据并未被复制到套接口关联的缓存区内,而只是记录数据位置和长度的数据缓存区被加入到sk_buff中,因此DMA模块直接将数据从内核缓存区传递给协议模块

对聚合分散IO数据的支持

什么是聚合分散IO?

+ 网络中创建一个发送报文的过程包括组合多个片,报文数据必须从用户空间复制到内核空间,同时加上网络协议栈各层的首部,这需要大量的数据拷贝

+ 如果发送报文的网络接口支持聚合分散IO,报文就无需组装成一个单块,可避免大量拷贝

+ 局和分散IO从用户空间启动零拷贝网络发送,首先要检查网络设备是否设置了NETIF_F_SG,没设置就只能线性化处理,设置了接下来就检查nr_frags的值,该字段确定了分段数,这些分散的片段以关联的方式存储在frags数组中

/* To allow 64K frame to be packed as single skb without frag_list */

#define MAX_SKB_FRAGS (65536/PAGE_SIZE + 2)

typedef struct skb_frag_struct skb_frag_t;

struct skb_frag_struct {

struct page *page;

__u16 page_offset;

__u16 size;

};

page:指向文件系统缓存页的指针

page_offset:数据起始地址在文件系统缓存页中的偏移

size:数据在文件系统缓存页中使用的长度

总结

sk_buff是Linux网络子系统中最基础的数据结构了,后面基本都会用到这个结构,因此掌握好它十分重要,

由于篇幅太长,操作函数就留到下一篇了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值