skb 添加删除尾部数据
skb_add_data
skb_add_data()将指定用户空间的数据添加到SKB的数据缓存区的尾部,操作过程如图3-22所示。如果成功则返回0,否则返回相应的错误码。参数skb为待添加数据的SKB;from为待添加的数据源,指向在用户空间的存储缓存区;copy为待添加数据的长度。
skb_trim
skb_trim()根据指定长度删除SKB的数据缓存区尾部的数据,如果新长度大于当前长度,则不作处理,操作过程如图3-23所示。调用该函数的前提条件是,待操作的SKB的数据必须是线性存储的。参数skb为待操作的SKB;len为删除尾部数据后剩余的长度。
pskb_strim
pskb_trim()与skb_trim()功能类似,也是根据指定长度删除SKB尾部的数据。不同的是,pskb_trim()是skb_trim()的功能超集,不仅可以处理线性数据的SKB,还可以处理非线性的SKB。线性数据的处理过程与skb_trim()相同,非线性数据操作过程如图3-24 和图3-25所示。
skb_split
拆分数据函数是把数据区数据拆分成两个存放到另外一个 skb 中,其实拆分数据函数并不复杂,只是一些指针的赋值,和控制。下面看函数实现:
// skb为原来的skb结构体(将要被拆分的),skb1为拆分后得到的子skb,len为拆分后的skb的新长度 void skb_split(struct sk_buff *skb, struct sk_buff *skb1, const u32 len) { int pos = skb_headlen(skb);// pos = skb->len - skb->data_len,pos是skb结构中数据区的有效数据长度 if (len < pos) // 如果拆分长度小于skb数据区中的有效长度,则调用下面函数 skb_split_inside_header(skb, skb1, len, pos);// 该函数只拆分skb数据区中的数据 else // 反之,如果拆分长度不小于skb数据区中的有效长度,则调用下面函数 skb_split_no_header(skb, skb1, len, pos);// 拆分skb结构中的分片结构中数据区数据 } // 这是只拆分sk_buff结构数据区的数据,其他参数不变,参数:pos则是sk_buff结构数据区中有效数据长度 static inline void skb_split_inside_header(struct sk_buff *skb, struct sk_buff* skb1, const u32 len, const int pos) { int i; // 这是个把sk_buff结构中有效数据拷贝到新的skb1中,pos为有效数据长度,len为剩下数据长度,得:pos-len为要拷贝的数据长度 // skb_put(skb1,pos-len)是移动tail指针让skb1结构数据区空出空间来存放将要拷贝的数据,该函数返回tail指针 skb_copy_from_linear_data_offset(skb, len, skb_put(skb1, pos - len), pos - len); // 为了方便理解,把该函数实现代码注释进来 // skb为要被拆分的sk_buff结构,offset为剩下新的skb数据长度,to为skb1结构中tail指针,len为要拷贝的数据长度 // static inline void skb_copy_from_linear_data_offset(const struct sk_buff *skb, // const int offset, void *to, // const unsigned int len) // { // 从skb要剩下的数据位置开始(即是skb->data+offset,skb->data和skb->data+offset之间的数据是要保留的) // to则是tail指针移动前返回的一个位置指针(详细请看skb_put()函数实现),拷贝len长度内容 // memcpy(to, skb->data + offset, len); // } // 如果对sk_buff结构及相关结构体中成员变量了解,则这些代码就非常好理解了。 // nr_frags为多少个分片数据区,循环把所有分片数据拷贝到skb1中 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) skb_shinfo(skb1)->frags[i] = skb_shinfo(skb)->frags[i]; //下面做的都是些成员字段拷贝赋值操作,并且设置skb的字段 skb_shinfo(skb1)->nr_frags = skb_shinfo(skb)->nr_frags; skb_shinfo(skb)->nr_frags = 0; skb1->data_len = skb->data_len; skb1->len += skb1->data_len; skb->data_len = 0; skb->len = len; skb_set_tail_pointer(skb, len);// 下面把实现函数代码注释进来,方便理解 // static inline void skb_set_tail_pointer(struct sk_buff *skb, const int offset) // { // // 这是把tail指针移到数据区的最后面 // skb->tail = skb->data + offset; // } } // 这是拆分分片结构数据区数据,同理,其他参数不变,参数:pos则是sk_buff结构数据区中有效数据长度 static inline void skb_split_no_header(struct sk_buff *skb, struct sk_buff* skb1, const u32 len, int pos) { int i, k = 0; // 开始设置sk_buff结构数据区内容 const int nfrags = skb_shinfo(skb)->nr_frags; skb_shinfo(skb)->nr_frags = 0; skb1->len = skb1->data_len = skb->len - len; skb->len = len; skb->data_len = len - pos; // 这是循环拆分分片结构数据区数据 for (i = 0; i < nfrags; i++) { int size = skb_shinfo(skb)->frags[i].size; // 其实拆分,数据区存储不会动,动的只是指向这些数据存储的位置指针 // 下面都是把skb的一些指向分片结构数据区的指针赋值给skb1中的数据区相关变量 if (pos + size > len) { skb_shinfo(skb1)->frags[k] = skb_shinfo(skb)->frags[i]; if (pos < len) { get_page(skb_shinfo(skb)->frags[i].page); skb_shinfo(skb1)->frags[0].page_offset += len - pos; skb_shinfo(skb1)->frags[0].size -= len - pos; skb_shinfo(skb)->frags[i].size = len - pos; skb_shinfo(skb)->nr_frags++; } k++; } else skb_shinfo(skb)->nr_frags++; pos += size; } skb_shinfo(skb1)->nr_frags = k; }
pskb_expand_head
pskb_pull():
对于带有frag page的分片skb来说,data指针往下移动,可能会导致线性区越界,因此需要判断是否线性区有足够的空间用来pull操作,如果空间不够,那么需要执行linearize,重构线性区,把一部分frags中的数据移动到线性区中来操作。
pskb_may_pull():
主要在使用skb_pull之前来检查线性区buffer有没有足够的数据用于 pull。