这章学习一下TCP用于表示数据包的最重要的一个数据结构:sk_buff。
sk_buff有三部分:sk_buff基本数据元素, linear-data buffer, paged-data(struct skb_shared_info)。
这章将详细的讲解如何有sk_buff构建一个被发送出去的包(主要就是包头的构建)和如何解析一个数据包得到一个sk_buff。
struct sk_buff
sk_buff结构体的定义位于:include/linux/skbuff.h文件中。其中的主要元素及解释如下所示:
struct sk_buff {
/* These two members must be first */
struct sk_buff *next;
struct sk_buff *prev;
struct sk_buff tstamp; // sk_buff发送时间/接收时间
struct sock *sk; // 标示这个sk_buff属于哪个sock
struct net_device *dev; // 表示从哪个设备接收/发出
char cb[48] __aligned(8); // control buffer, TCP map this buffer to struct tcp_skb_cb
...
unsigned int len; // sk_buff的总长度 = linear-data + paged-data
unsigned int data_len; // paged-data length
__u16 mac_len;
__u16 hdr_len; // writable header length of cloned skb
...
__u32 priority; // packet queueing priority
...
__u8 peeked:1; // packet has been seen already, so stats have been done for it, don't do them again
...
/* 有意思的是alloc_skb时,skb->tail = skb->data - skb->head。tail其实是一个偏移量,而不是一个真正意义上的指针
* 同时skb->end = skb->tail + size 也是一样偏移量。
*/
sk_buff_data_t tail; // last byte of the data residing in linear area (与data指针对应)
sk_buff_data_t end; // end of linear-data area (与head指针对应)
unsigned char *head; // points to the start of the linear data area (first byte allocated)
unsigned char *data; // start of data residing in the linear area (first byte used)
unsigned int truesize; // total memory allocated for this buffer = sizeof(sk_buff) + the size of the data block allocated for this sk_buff
};
typedef unsigned char *sk_buff_data_t;
struct skb_shared_info
This data is invariant(不变的) across clones and lives at the end of the header data, ie. at skb->end.
这个数据结构主要半酣sk_buff中的nonlinear data。所谓的nonlinear data其实就是linear-data area放不下的数据就放到nonlinear area了。
书中把nonlinear data area也叫做 paged-data area. The paged-data area is possible only if DMA allows scatter-gather operations on the physically scattered pages.
Routines operating on sk_buff
stuct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask, int flags, int mode)
该函数分配一个新的sk_buff实例,需要给定的参数是linear-data area的大小和内存分配的模式。static inline void skb_reserve(struct sk_buff *skb, int len)
该函数将skb的data和tail指针往后移动,移动长度为len。常见的用途是为协议头部保留空间。unsigned char skb_put(struct sk_buff skb, unsigned int len)
该函数负责将sk_buff的linear-data area的tail指针增加len。可以看到3.10的kernel里面使用了skb_tail_pointer的方式获取真正意义上的指向数据使用部分的最后一个字节的指针。进一步验证了之前分析sk_buff结构体定义时认为tail是一个offset的判断。这点与书中描述的是不一致的。可见Linux kernel中关于TCP这块的更新还是很频繁的。连sk_buff这种重要的数据结构也一直在改动。unsigned char skb_push(struct sk_buff skb, unsigend int len)
该函数负责将sk_buff的linear-data area的data指针往前移动,距离为len。不难看出,skb_put是用来构建包的数据的,而skb_push则是当一个上层包传递到下一层后,下层在添加头部数据时调用skb_push。unsigned char skb_pull(struct sk_buff skb, unsigned int len)
该函数与skb_push相对应,它将data指针往后移动,距离为len。也就意味着skb->len要减少len。当有数据包到达后,一层层解析包头的过程中往往会用到该函数。
书中的5.5节描述了发送数据包时header是如何添加的,5.6节描述了收到的数据包头是如何被解析的。不过基本上熟悉了上面几个函数后,基本的操作方法应该都能理解了。还有不清楚的可以看下书。
这章主要就是讲sk_buff及其相关的一些操作函数,还算比较好理解。
tcp_skb_cb结构体是TCP层对于sk_buff->cb的实例化。里面主要有seq,timestamp和sacked flag