从开始的sk_buff结构及相关结构体的成员变量分析到sk_buff结构的一些操作函数,然后是sk_buff结构的内存申请和释放,接着是几个克隆拷贝函数的区别,再到现在要分析得列表管理函数。这有关sk_buff结构的一系列blog差不多就接近尾声了,仔细的分析了这几块内容,虽不能说对sk_buff结构非常了解,但也有了个全新的认识。感觉收获匪浅。下面开始分析下队列管理函数。
初始化函数:
头结点初始化函数:void skb_queue_head_init(struct sk_buff_head *list);首先获取到sk_buff_head结构体中自旋锁,因为队列管理函数都是原子操作(要么不操作,要不一定要操作完,操作时不能被打扰),所以获取到锁才可以操作,防止异步中断。然后创建个空的链表。函数实现如下:
static inline void skb_queue_head_init(struct sk_buff_head *list) { spin_lock_init(&list->lock);// 获得头结点中的自旋锁 __skb_queue_head_init(list); // 调用函数初始化头结点 } static inline void __skb_queue_head_init(struct sk_buff_head *list) { list->prev = list->next = (struct sk_buff *)list;// 创建一个链表 list->qlen = 0;// 链表节点个数为零 }
插入函数:
插入分为从队列的头部插入和队列的尾部插入,从队列头部插入由skb_queue_head()函数实现,从队列尾部插入由skb_queue_tail()函数实现。
头部插入函数实现:
void skb_queue_head(struct sk_buff_head *list, struct sk_buff *newsk) { unsigned long flags; spin_lock_irqsave(&list->lock, flags); // 获取自旋锁,并且上锁 __skb_queue_head(list, newsk); // 真正的插入操作 spin_unlock_irqrestore(&list->lock, flags); // 解锁操作 } static inline void __skb_queue_head(struct sk_buff_head *list, struct sk_buff *newsk) { __skb_queue_after(list, (struct sk_buff *)list, newsk); // 第一个list本来表示的是一个链表指针,第二个list是由第一个队列指针list强转为链表头部结点 } static inline void __skb_queue_after(struct sk_buff_head *list, struct sk_buff *prev, struct sk_buff *newsk) { __skb_insert(newsk, prev, prev->next, list); // 这里的prev实则是链表头部结点, }
可以看出头部插入函数一开始就是获取自旋锁,然后上锁操作,接下来才是真正的数据操作,最后一步是解锁。数据操作调用了_skb_queue_head()函数,而该函数其实什么也没做只是调用了_skb_queue_after()函数,同样的_skb_queue_after()函数也是什么都没做只是仅仅调用了_skb_insert()。所以归根结底还是_skb_insert()函数在做插入操作,而其他函数只是在传参数的时候告诉该函数往哪个位置上插入。后面会着重的分析下_skb_insert()插入函数。
尾部插入函数实现:
void skb_queue_tail(struct sk_buff_head *list, struct sk_buff *newsk) { unsigned long flags; spin_lock_irqsave(&list->lock, flags); // 同头部插入函数一样,上锁操作 __skb_queue_tail(list, newsk); // 调用插入函数 spin_unlock_irqrestore(&list->lock, flags); // 解锁操作 } static inline void __skb_queue_tail(struct sk_buff_head *list, struct sk_buff *newsk) { __skb_queue_before(list, (struct sk_buff *)list, newsk); // 调用函数实现参数的变化 } static inline void __skb_queue_before(struct sk_buff_head *list, struct sk_buff *next, struct sk_buff *newsk) { __skb_insert(newsk, next->prev, next, list); // 调用函数实现真正的插入操作 }尾部插入函数其实和头部插入函数非常相似(本来也应该相似,因为他们都是插入操作,仅仅不同的只是插入位置而已),上锁----》插入----》解锁,其他调用的函数也是什么都没有做仅仅是到最后调用_skb_insert()函数来插入,以及在传参数时,控制插入的位置而已。
不管是从队列的头部插入还是从队列的尾部插入,其本质都是调用_skb_insert()函数来进行插入处理。下面分析该函数的实现:
_skb_insert()函数实现:
static inline void __skb_insert(struct sk_buff *newsk, struct sk_buff *prev, struct sk_buff *next, struct sk_buff_head *list) { newsk->next = next; newsk->prev = prev; next->prev = prev->next = newsk; list->qlen++; }该函数实现的是在prev和next之间插入newsk结构体,所以如果是队列头部插入:则prev这个形参就要用头结点来传入,next就要用头结点->next来传入;如果是队列尾部插入:则prev这个形参则用头结点->prev来传入,而next就用头结点来传入;最后再让链表节点个数qlen变量加1;原理如下图:
出队列函数:
头部出队列函数:
struct sk_buff *skb_dequeue(struct sk_buff_head *list) { unsigned long flags; struct sk_buff *result; spin_lock_irqsave(&list->lock, flags); // 获取锁,上锁 result = __skb_dequeue(list); // 出队列操作 spin_unlock_irqrestore(&list->lock, flags); // 解锁操作 return result; } static inline struct sk_buff *__skb_dequeue(struct sk_buff_head *list) { struct sk_buff *skb = skb_peek(list); // 调用函数来获取到出队列元素 if (skb) // 判断获取到元素是否存在,以防是只有头结点的队列 __skb_unlink(skb, list); // 调用函数实现具体的出队列操作 return skb; } static inline struct sk_buff *skb_peek(struct sk_buff_head *list_) { struct sk_buff *list = ((struct sk_buff *)list_)->next;// 获取到头部节点开始的第一个元素 if (list == (struct sk_buff *)list_) // 判断是否为头部节点自身,即:是否只有一个头节点的队列 list = NULL; // 若只有头节点,则返回空 return list; }和所有队列操作函数一样,出队列函数实现步骤也是:上锁----》出队列操作-----》解锁。真正操作还是在_skb_unlink()函数,其他函数也只是定位要出哪个元素而已。具体的出队列函数_skb_unlink()函数,将在后面分析。
尾部出队列函数:
struct sk_buff *skb_dequeue_tail(struct sk_buff_head *list) { unsigned long flags; struct sk_buff *result; spin_lock_irqsave(&list->lock, flags); // 上锁操作 result = __skb_dequeue_tail(list); // 调用函数进行具体的出队列操作 spin_unlock_irqrestore(&list->lock, flags); // 解锁操作 } static inline struct sk_buff *__skb_dequeue_tail(struct sk_buff_head *list) { struct sk_buff *skb = skb_peek_tail(list);// 获取到出队列元素 if (skb) // 判断出队列元素是否为空,防止只有一个元素的队列 __skb_unlink(skb, list);// 调用函数实现具体的出队列操作 return skb; } static inline struct sk_buff *skb_peek_tail(struct sk_buff_head *list_) { struct sk_buff *list = ((struct sk_buff *)list_)->prev; // 获取到头部节点的前一个,即是尾部开始节点 if (list == (struct sk_buff *)list_) // 判断是否是只有一个头节点的队列 list = NULL; return list; }看了尾部出队列操作函数,会发现头部出队列函数命名其实有点不同,按常理来说应该是:skb_dequeue_head()才能和尾部出队列函数:skb_dequeue_tail()相符合。但是不知为什么内核定义的确是skb_dequeue(),这着实不能理解,只能当做是作者的一个美丽失误吧。其实尾部出队列操作函数和头部出队列函数非常相似,其实大部分对应的操作函数的是非常相似,这是内核设计的模块化和代码耦合度考虑。下面来分析下真正的出队列操作函数_skb_unlink()函数。
_skb_unlink()函数:
static inline void __skb_unlink(struct sk_buff *skb, struct sk_buff_head *list) { struct sk_buff *next, *prev; list->qlen--; // 队列元素计数器自减1 next = skb->next; // 下面的实现分析看图 prev = skb->prev; skb->next = skb->prev = NULL; next->prev = prev; prev->next = next; }_skb_unlink()函数实现原理图:
清空队列函数:
void skb_queue_purge(struct sk_buff_head *list) // 传入一个链表头结点 { struct sk_buff *skb; while ((skb = skb_dequeue(list)) != NULL) // 从头部开始循环出队列,直到最后一个头部节点 kfree_skb(skb);// 队列中每出一个元素就释放掉一个元素 }
遍历队列操作:
#define skb_queue_walk(queue, skb) \ for (skb = (queue)->next; \ prefetch(skb->next), (skb != (struct sk_buff *)(queue)); \ skb = skb->next) // 上面的遍历是从queue头结点开始遍历,直到遍历循环回到queue结束。 // 也就是遍历整个队列操作,但该宏不能做删除skb操作,一旦删除了skb后,skb->next就是非法的(因为此时skb不存在)。 #define skb_queue_walk_safe(queue, skb, tmp) \ for (skb = (queue)->next, tmp = skb->next; \ skb != (struct sk_buff *)(queue); \ skb = tmp, tmp = skb->next) // 这个宏也是从queue头结点开始遍历整个队列操作,唯一不同的是这个宏用了一个临时变量,就是防止遍历时要删除掉skb变量, // 因为删除掉了skb后,也可以从skb=tmp中再次获得,然后依次tmp = skb->next;(此时skb是存在的)所以遍历时,可以做删除操作。 #define skb_queue_walk_from(queue, skb) \ for (; prefetch(skb->next), (skb != (struct sk_buff *)(queue)); \ skb = skb->next) // 这个宏是从skb元素处开始遍历直到遇到头结点queue结束,该宏只能做查看操作,不能做删除skb操作,分析如第一个宏 #define skb_queue_walk_from_safe(queue, skb, tmp) \ for (tmp = skb->next; \ skb != (struct sk_buff *)(queue); \ skb = tmp, tmp = skb->next) // 这个宏也是从skb元素开始遍历直到遇到queue元素结束,但该宏可以做删除skb元素操作,具体分析如第一个宏 #define skb_queue_reverse_walk(queue, skb) \ for (skb = (queue)->prev; \ prefetch(skb->prev), (skb != (struct sk_buff *)(queue)); \ skb = skb->prev) // 这是个逆反遍历宏,就是从queue头结点的尾部开始(或者说从前驱元素开始)直到遇到queue元素节点。 // 也即是从头结点尾部开始遍历了整个队列,此宏和第一、第三个宏一样,不能做删除操作。上面五个队列遍历操作宏都是大同小异,还有个指令稍微说下:prefetch(skb->next),这是个预取指令操作,提高CPU效率而已。
插入数据函数:
// skb为被添加的sk_buff类型的结构体,from为将要添加的数据源,copy为数据源的长度 static inline int skb_add_data(struct sk_buff *skb, char __user *from, int copy) { const int off = skb->len; if (skb->ip_summed == CHECKSUM_NONE) {// 表示检验ip包的校验 int err = 0; // 数据拷贝操作,这里调用了skb_put()函数让tail往下移空出控件来存放将要拷贝的数据,并且返回tail指针 __wsum csum = csum_and_copy_from_user(from, skb_put(skb, copy), copy, 0, &err); if (!err) { skb->csum = csum_block_add(skb->csum, csum, off); // 这个应该是IP校验计算吧 return 0; } } else if (!copy_from_user(skb_put(skb, copy), from, copy)) // 这是最本质的数据拷贝操作宏,同样调用了skb_put()函数返回tail指针 return 0; __skb_trim(skb, off); // 这个是删除数据操作,将在下一个数据删除(skb_trim()函数)分析 return -EFAULT; } static inline __wsum csum_and_copy_from_user (const void __user *src, void *dst, int len, __wsum sum, int *err_ptr) { if (access_ok(VERIFY_READ, src, len)) // 判断数据长度关系 return csum_partial_copy_from_user(src, dst, len, sum, err_ptr); // 调用拷贝函数 if (len) *err_ptr = -EFAULT; return sum; } static __inline__ __wsum csum_partial_copy_from_user(const void __user *src, void *dst, int len, __wsum sum, int *err_ptr) { if (copy_from_user(dst, src, len)) { // 拷贝操作 *err_ptr = -EFAULT; return (__force __wsum)-1; } return csum_partial(dst, len, sum); // 设置校验和 } // 这是调用memcpy()函数来对数据进行拷贝,to是tail指针,from是将要插入的数据源指针,n是数据源长度 #define copy_from_user(to, from, n) (memcpy((to), (from), (n)), 0)
删除数据函数:
void skb_trim(struct sk_buff *skb, unsigned int len) { // 这里值得注意的是len不是要删除的数据长度,而是删除后的数据长度,即是新的数据长度。 // 所以新的数据长度不能比开始的skb的长度还大,否则就是插入增加数据函数而不是删除数据函数了 if (skb->len > len) __skb_trim(skb, len);// 调用函数进行删除数据操作 } static inline void __skb_trim(struct sk_buff *skb, unsigned int len) { if (unlikely(skb->data_len)) { WARN_ON(1); return; } skb->len = len; // 为新的skb赋上删除后的len值 skb_set_tail_pointer(skb, len); // 调用函数删除操作 } static inline void skb_set_tail_pointer(struct sk_buff *skb, const int offset) { skb->tail = skb->data + offset; // 实质上没有对数据进行删除,只是让tail指针偏移,改变有效数据值 }删除sk_buff结构中分片结构的数据区数据函数:
static inline int pskb_trim(struct sk_buff *skb, unsigned int len) { return (len < skb->len) ? __pskb_trim(skb, len) : 0; // 这个功能和上面类似,如果新len值小于skb原有的值,则做删除操作 } static inline int __pskb_trim(struct sk_buff *skb, unsigned int len) { if (skb->data_len)// 如果分片结构数据区有数据 return ___pskb_trim(skb, len);// 则调用该函数来删除分片结构中的数据区数据 __skb_trim(skb, len);// 这个和上面删除sk_buff结构中的数据区数据一样 return 0; }pskb_trim()函数其实包含了skb_trim()函数,如果当分片结构数据区没有数据则skb_trim()函数和pskb_trim()函数是一样的。如果分片结构数据区有数据时,则pskb_trim()函数不仅要删除sk_buff结构数据区数据(skb_trim()函数功能),还要删除分片结构数据区数据。
拆分数据函数:
// 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长度内容 // <span style="white-space:pre"> </span>memcpy(to, skb->data + offset, len); //<span style="white-space:pre"> </span>} // 如果对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; }
至此,就把sk_buff结构及其相关结构分析了个透彻:第一篇,分析了sk_buff结构及其相关结构的成员变量字段---- sk_buff 整理笔记(一、数据结构);第二篇,分析了sk_buff结构中的四大指针,以及操作这几个指针的函数---- sk_buff整理笔记(二、操作函数);第三篇,分析了sk_buff结构及其相关结构的内存申请和释放---- sk_buff整理笔记(三、内存申请和释放);第四篇,分析了sk_buff结构以及相关结构的复制拷贝和克隆问题,何时用何种函数进行克隆或者复制拷贝---- sk_buff整理笔记(四、克隆与复制);第五篇,则是分析了sk_buff结构队列链表的一些操作管理函数,及其数据的增删改查,拆分等操作函数---- sk_buff整理笔记(五、队列管理函数);