Chinaunix首页 | 论坛 | 博客
  • 博客访问: 79728
  • 博文数量: 14
  • 博客积分: 132
  • 博客等级: 入伍新兵
  • 技术积分: 90
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-25 20:46
文章分类
文章存档

2012年(14)

我的朋友

分类:

2012-11-07 21:03:23

额...这个函数还是相当复杂的。首先需要了解的就是收到的包的数据在sk_buff中是如何组织的。
数据先会出现在sk_buff->data中,也就是线性数据缓冲区,多余的数据就放在skb_shinfo(skb)->frag[]当中,这些数据存在于所谓的非线性缓冲区当中,这些数据都存在于unmapped page当中,这是用于支持驱动的分散/聚集 I/O的。另外,还有一部分存在于skb_shinfo(skb)->frag_list当中,这是一个sk_buff结构的链表。所以,对shk_shinfo(skb)->frag_list当中的sk_buff中的数据可以进行上述的递归表达。
数据的连续性和这里描述的数据缓冲区中的数据是一致的。
该函数的定义为

点击(此处)折叠或打开

  1. unsigned char *__pskb_pull_tail(struct sk_buff *skb, int delta)
delta参数是skb->tail需要前进的字节数。而skb->tail是线性数据缓冲区的结尾,后面不含任何有效数据,
所以delta参数也是需要从其他部分拷贝数据到线性缓冲区的长度。

该函数比较长,下面分段描述。

点击(此处)折叠或打开

  1. /**
  2.  *    __pskb_pull_tail - advance tail of skb header
  3.  *    @skb: buffer to reallocate
  4.  *    @delta: number of bytes to advance tail
  5.  *
  6.  *    The function makes a sense only on a fragmented &sk_buff,
  7.  *    it expands header moving its tail forward and copying necessary
  8.  *    data from fragmented part.
  9.  *
  10.  *    &sk_buff MUST have reference count of 1.
  11.  *
  12.  *    Returns %NULL (and &sk_buff does not change) if pull failed
  13.  *    or value of new tail of skb in the case of success.
  14.  *
  15.  *    All the pointers pointing into skb header may change and must be
  16.  *    reloaded after call to this function.
  17.  */

  18. /* Moves tail of skb head forward, copying data from fragmented part,
  19.  * when it is necessary.
  20.  * 1. It may fail due to malloc failure.
  21.  * 2. It may change skb pointers.
  22.  *
  23.  * It is pretty complicated. Luckily, it is called only in exceptional cases.
  24.  */
仔细看这个注释还是有必要的,如注释所说的,它极少情况下会被调用,因此看不下去也没关系,可以继续分析其他的,只需要知道它是干什么的,不必知道它是具体怎么做的。

点击(此处)折叠或打开

  1. unsigned char *__pskb_pull_tail(struct sk_buff *skb, int delta)
  2. {
  3.     /* If skb has not enough free space at tail, get new one
  4.      * plus 128 bytes for future expansions. If we have enough
  5.      * room at tail, reallocate without expansion only if skb is cloned.
  6.      */
  7.     int i, k, eat = (skb->tail + delta) - skb->end;
  8.     /* eat > 0 说明skb的线性缓冲区尾部没有足够空闲空间,或者如果skb是被克隆过的那
  9.      * 么pskb_expand_head会重新分配一个线性数据缓冲区,该缓冲区大小在原缓冲区的
  10.      * 基础上,将尾部扩大eat + 128字节,或者得到一份新的数据缓冲区拷贝,它保证该
  11.      * skb是没有克隆过,且数据缓冲区是私有的,即skb->cloned = 0且
  12.      * skb_shareinfo(skb)->dataref = 1.
  13.      * 首先分析eat > 0 的情况下要扩展线性缓冲区尾部的理由很直接,因为尾部空间不
  14.      * 足以容纳将要从其他地方拷贝来的数据。
  15.      * skb是被克隆的情况下,需要一个私有的数据缓冲区,这是因为skb被克隆时,数
  16.      * 据缓冲区是被共享的,而接下来需要从其他地方拷贝数据到线性缓冲区,也就是对
  17.      * 缓冲区进行了修改,因此,需要一份私有的数据缓冲区。*/
  18.     if (eat > 0 || skb_cloned(skb)) {
  19.         if (pskb_expand_head(skb, 0, eat > 0 ? eat + 128 : 0,
  20.                  GFP_ATOMIC))
  21.             return NULL;
  22.     }
这时候skb的线性数据缓冲区的tailroom有足够的空间来容纳即将被拷贝进来的数据,接下来当然就是进行数据拷贝,这个数据拷贝可是一个艰难的过程,不只是一个memcpy就可以做到的。因为前面提到过,有些数据存在于unmapped page当中,还有一些存在于其他skb片段当中。

点击(此处)折叠或打开

  1.     /*
  2.      * 从skb的线性数据区以及可能从非线性数据区,甚至可能从skb的frag_list链中拷贝
  3.      * 总长度为delta的数据到skb_tail_pointer(skb)。这个函数也比较复杂,它甚至还递归!
  4.      * 单独分析。
  5.      */
  6.     if (skb_copy_bits(skb, skb_headlen(skb), skb_tail_pointer(skb), delta))
  7.         BUG();
自此之后,所需要的数据都拷贝到了skb的线性数据缓冲区,所拷贝的数据在其他地方也存在,接下来要清理重复的数据也就顺理成章了。可这并不是简单的事。

点击(此处)折叠或打开

  1. /* Optimization: no fragments, no reasons to preestimate
  2.      * size of pulled pages. Superb.
  3.      */
  4.     if (!skb_shinfo(skb)->frag_list)
  5.         goto pull_pages;
如果执行goto,这当然是最好的了,skb_shinfo(skb)->frag_list如果是NULL,则表明上面的skb_copy_bits最坏只是从skb_shinfo(skb)->frags[]当中拷贝了数据。
如果skb_shinfo(skb)->frag_list不是NULL,则有可能从中拷贝了数据,我们需要测试一下。

点击(此处)折叠或打开

  1. /* Estimate size of pulled pages. */
  2.     /* 统计这次从非线性缓冲区中拷贝了多少数据 */
  3.     eat = delta;
  4.     for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  5.         if (skb_shinfo(skb)->frags[i].size >= eat)
  6.             goto pull_pages;
  7.         eat -= skb_shinfo(skb)->frags[i].size;
  8.     }
若eat > 0,则表明的确从frag_list中拷贝了数据,否则没有。没有当然最好了。

不幸总是要来临的,首先要找到那些frag_list中的sk_buff中存在重复数据,因为frag_list中的sk_buff的数据是线性顺序的,所以,只要找到第一个没有重复的sk_buff即可,前面的所有sk_buff节点都是重复数据,因此需要释放掉这些sk_buff。

点击(此处)折叠或打开

  1. /* If we need update frag list, we are in troubles.
  2.      * Certainly, it possible to add an offset to skb data,
  3.      * but taking into account that pulling is expected to
  4.      * be very rare operation, it is worth to fight against
  5.      * further bloating skb head and crucify ourselves here instead.
  6.      * Pure masohism, indeed. 8)8)
  7.      */
  8.     /* 如果eat大于零...那么本次的拷贝还从skb的frag_list中进行了拷贝*/
  9.     if (eat) {
  10.         struct sk_buff *list = skb_shinfo(skb)->frag_list;
  11.         struct sk_buff *clone = NULL;
  12.     /* insp记录第一个未被拷贝的sk_buff结构,在释放skb时有用。 */
  13.         struct sk_buff *insp = NULL;    

  14.         do {
  15.             BUG_ON(!list);

  16.             if (list->len <= eat) {    /* 这个sk_buff中的所有数据都被拷贝了。 */
  17.                 /* Eaten as whole. */
  18.                 eat -= list->len;
  19.                 list = list->next;
  20.                 insp = list;
  21.             } else {
  22.                 /* Eaten partially. */
  23.     /* 有其他部分和我们共享这个skb的数据,所以需要克隆一个sk_buff
  24.      * 因为接下我们要修改sk_buff中的指针。 */
  25.                 if (skb_shared(list)) {
  26.                     /* We need to fork list. :-( */
  27.                     clone = skb_clone(list, GFP_ATOMIC);
  28.                     if (!clone)
  29.                         return NULL;
  30.                     insp = list->next;
  31.                     list = clone;
  32.                 } else {
  33.                     /* This may be pulled without
  34.                      * problems. */
  35.                     insp = list;
  36.                 }
  37.     /* 将list->data向前移动eat个字节,因为这部分已经被拷贝走了....
  38.      * pskb_pull 可能会继续调用__pskb_pull_tail。好混乱,因为前面在调用
  39.      * pskb_copy_bits时会对frag_list中的skb递归调用__pskb_copy_bits,而
  40.      * 这个skb线性缓冲区中的数据可能又不满足需要拷贝的长度,因此又
  41.      * 要从非线性缓冲、frag_list中拷贝数据...*/
  42.                 if (!pskb_pull(list, eat)) {
  43.                     if (clone)
  44.                         kfree_skb(clone);
  45.                     return NULL;
  46.                 }
  47.                 break;    /* 之后的skb的内容没有被拷贝...所以break */
  48.             }
  49.         } while (eat);
找到了链表中的结束位置,接下来就可以对重复的数据进行清理,释放掉这些多余的东西。

点击(此处)折叠或打开

  1. /* Free pulled out fragments. */
  2.     /* 开始释放已经被拷贝了所有数据缓冲区内容的的sk_buff */
  3.         while ((list = skb_shinfo(skb)->frag_list) != insp) {
  4.             skb_shinfo(skb)->frag_list = list->next;
  5.             kfree_skb(list);
  6.         }
上面这些被free掉的skb,其包含的数据全部是重复的,所以可以完全释放掉。但是insp指向的sk_buff可能包含部分重复的数据,这个也需要进行处理,请看上一个代码块中if-else中else语句里的处理过程。

更新skb_shinfo(skb)->frag_list链表头,如果有需要的话。

点击(此处)折叠或打开

  1. /* 如果eat刚好覆盖的是几个skb_buff数据的总和,那么clone = NULL */
  2.     /*
  3.         /* And insert new clone at head. */
  4.         if (clone) {
  5.             clone->next = list;
  6.             skb_shinfo(skb)->frag_list = clone;
  7.         }
  8.     }

繁杂的skb_shinfo(skb)->frag_list处理完之后,还要对skb_shinfo(skb)->frag[]进行消除重复数据的梳理。这个过程相对还是比较愉快的。代码比较直观。

点击(此处)折叠或打开

  1. pull_pages:    /* 处理被eat掉的非线性数据缓冲区 */
  2.     eat = delta;
  3.     k = 0;
  4.     for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  5.         if (skb_shinfo(skb)->frags[i].size <= eat) {
  6.             put_page(skb_shinfo(skb)->frags[i].page);    /* 可能会导致释放该页面 */
  7.             eat -= skb_shinfo(skb)->frags[i].size;
  8.         } else { /* 重新调整非线性缓冲区 */
  9.             skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i];
  10.             if (eat) {    
  11.                 skb_shinfo(skb)->frags[k].page_offset += eat;
  12.                 skb_shinfo(skb)->frags[k].size -= eat;
  13.                 eat = 0;
  14.             }
  15.             k++;
  16.         }
  17.     }


剩下的工作就是更新skb的一些字段了。

点击(此处)折叠或打开

  1. skb_shinfo(skb)->nr_frags = k;    /* 更新现在剩余的非线性缓冲区中页面个数 */

  2.     skb->tail += delta;    /* 更新tail指针 */
  3.     skb->data_len -= delta;    /* 减少非线性缓冲区的数据长度 */

  4.     return skb_tail_pointer(skb);    /* 返回线性数据缓冲区中的tail 指针 */
  5. }
skb->tail += delta才是本函数的最根本的目的!

copy->cleanup->update是本函数的基本流程。





阅读(3179) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~