IT技术互动交流平台

linux TCP/IP协议栈 __pskb_pull_tail()

作者:PCliangtao  发布日期:2012-05-24 09:25:33

额...这个函数还是相当复杂的。首先需要了解的就是收到的包的数据在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中的数据可以进行上述的递归表达。
数据的连续性和这里描述的数据缓冲区中的数据是一致的。
该函数的定义为

 

unsigned char *__pskb_pull_tail(struct sk_buff *skb, int delta)

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

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


/**

 *    __pskb_pull_tail - advance tail of skb header

 *    @skb: buffer to reallocate

 *    @delta: number of bytes to advance tail

 *

 *    The function makes a sense only on a fragmented &sk_buff,

 *    it expands header moving its tail forward and copying necessary

 *    data from fragmented part.

 *

 *    &sk_buff MUST have reference count of 1.

 *

 *    Returns %NULL (and &sk_buff does not change) if pull failed

 *    or value of new tail of skb in the case of success.

 *

 *    All the pointers pointing into skb header may change and must be

 *    reloaded after call to this function.

 */

 

/* Moves tail of skb head forward, copying data from fragmented part,

 * when it is necessary.

 * 1. It may fail due to malloc failure.

 * 2. It may change skb pointers.

 *

 * It is pretty complicated. Luckily, it is called only in exceptional cases.

 */

仔细看这个注释还是有必要的,如注释所说的,它极少情况下会被调用,因此看不下去也没关系,可以继续分析其他的,只需要知道它是干什么的,不必知道它是具体怎么做的。

 

unsigned char *__pskb_pull_tail(struct sk_buff *skb, int delta)

{

    /* If skb has not enough free space at tail, get new one

     * plus 128 bytes for future expansions. If we have enough

     * room at tail, reallocate without expansion only if skb is cloned.

     */

    int i, k, eat = (skb->tail + delta) - skb->end;

    /* eat > 0 说明skb的线性缓冲区尾部没有足够空闲空间,或者如果skb是被克隆过的那

     * 么pskb_expand_head会重新分配一个线性数据缓冲区,该缓冲区大小在原缓冲区的

     * 基础上,将尾部扩大eat + 128字节,或者得到一份新的数据缓冲区拷贝,它保证该

     * skb是没有克隆过,且数据缓冲区是私有的,即skb->cloned = 0且

     * skb_shareinfo(skb)->dataref = 1.

     * 首先分析eat > 0 的情况下要扩展线性缓冲区尾部的理由很直接,因为尾部空间不

     * 足以容纳将要从其他地方拷贝来的数据。

     * skb是被克隆的情况下,需要一个私有的数据缓冲区,这是因为skb被克隆时,数

     * 据缓冲区是被共享的,而接下来需要从其他地方拷贝数据到线性缓冲区,也就是对

     * 缓冲区进行了修改,因此,需要一份私有的数据缓冲区。*/

    if (eat > 0 || skb_cloned(skb)) {

        if (pskb_expand_head(skb, 0, eat > 0 ? eat + 128 : 0,

                 GFP_ATOMIC))

            return NULL;

    }

这时候skb的线性数据缓冲区的tailroom有足够的空间来容纳即将被拷贝进来的数据,接下来当然就是进行数据拷贝,这个数据拷贝可是一个艰难的过程,不只是一个memcpy就可以做到的。因为前面提到过,有些数据存在于unmapped page当中,还有一些存在于其他skb片段当中。

 

    /*

     * 从skb的线性数据区以及可能从非线性数据区,甚至可能从skb的frag_list链中拷贝

     * 总长度为delta的数据到skb_tail_pointer(skb)。这个函数也比较复杂,它甚至还递归!

     * 单独分析。

     */

    if (skb_copy_bits(skb, skb_headlen(skb), skb_tail_pointer(skb), delta))

        BUG();

自此之后,所需要的数据都拷贝到了skb的线性数据缓冲区,所拷贝的数据在其他地方也存在,接下来要清理重复的数据也就顺理成章了。可这并不是简单的事。

 

/* Optimization: no fragments, no reasons to preestimate

     * size of pulled pages. Superb.

     */

    if (!skb_shinfo(skb)->frag_list)

        goto pull_pages;

如果执行goto,这当然是最好的了,skb_shinfo(skb)->frag_list如果是NULL,则表明上面的skb_copy_bits最坏只是从skb_shinfo(skb)->frags[]当中拷贝了数据。
如果skb_shinfo(skb)->frag_list不是NULL,则有可能从中拷贝了数据,我们需要测试一下。

 

/* Estimate size of pulled pages. */

    /* 统计这次从非线性缓冲区中拷贝了多少数据 */

    eat = delta;

    for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {

        if (skb_shinfo(skb)->frags[i].size >= eat)

            goto pull_pages;

        eat -= skb_shinfo(skb)->frags[i].size;

    }

若eat > 0,则表明的确从frag_list中拷贝了数据,否则没有。没有当然最好了。

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

 

/* If we need update frag list, we are in troubles.

     * Certainly, it possible to add an offset to skb data,

     * but taking into account that pulling is expected to

     * be very rare operation, it is worth to fight against

     * further bloating skb head and crucify ourselves here instead.

     * Pure masohism, indeed. 8)8)

     */

    /* 如果eat大于零...那么本次的拷贝还从skb的frag_list中进行了拷贝*/

    if (eat) {

        struct sk_buff *list = skb_shinfo(skb)->frag_list;

        struct sk_buff *clone = NULL;

    /* insp记录第一个未被拷贝的sk_buff结构,在释放skb时有用。 */

        struct sk_buff *insp = NULL;   

 

        do {

            BUG_ON(!list);

 

            if (list->len <= eat) {    /* 这个sk_buff中的所有数据都被拷贝了。 */

                /* Eaten as whole. */

                eat -= list->len;

                list = list->next;

                insp = list;

            } else {

                /* Eaten partially. */

    /* 有其他部分和我们共享这个skb的数据,所以需要克隆一个sk_buff

     * 因为接下我们要修改sk_buff中的指针。 */

                if (skb_shared(list)) {

                    /* We need to fork list. :-( */

                    clone = skb_clone(list, GFP_ATOMIC);

                    if (!clone)

                        return NULL;

                    insp = list->next;

                    list = clone;

                } else {

                    /* This may be pulled without

                     * problems. */

                    insp = list;

                }

    /* 将list->data向前移动eat个字节,因为这部分已经被拷贝走了.额...

     * pskb_pull 可能会继续调用__pskb_pull_tail。好混乱,因为前面在调用

     * pskb_copy_bits时会对frag_list中的skb递归调用__pskb_copy_bits,而

     * 这个skb线性缓冲区中的数据可能又不满足需要拷贝的长度,因此又

     * 要从非线性缓冲、frag_list中拷贝数据...*/

                if (!pskb_pull(list, eat)) {

                    if (clone)

                        kfree_skb(clone);

                    return NULL;

                }

                break;    /* 之后的skb的内容没有被拷贝...所以break */

            }

        } while (eat);

找到了链表中的结束位置,接下来就可以对重复的数据进行清理,释放掉这些多余的东西。

 

/* Free pulled out fragments. */

    /* 开始释放已经被拷贝了所有数据缓冲区内容的的sk_buff */

        while ((list = skb_shinfo(skb)->frag_list) != insp) {

            skb_shinfo(skb)->frag_list = list->next;

            kfree_skb(list);

        }

上面这些被free掉的skb,其包含的数据全部是重复的,所以可以完全释放掉。但是insp指向的sk_buff可能包含部分重复的数据,这个也需要进行处理,请看上一个代码块中if-else中else语句里的处理过程。

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


/* 如果eat刚好覆盖的是几个skb_buff数据的总和,那么clone = NULL */

    /*

        /* And insert new clone at head. */

        if (clone) {

            clone->next = list;

            skb_shinfo(skb)->frag_list = clone;

        }

    }


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

 

pull_pages:    /* 处理被eat掉的非线性数据缓冲区 */

    eat = delta;

    k = 0;

    for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {

        if (skb_shinfo(skb)->frags[i].size <= eat) {

            put_page(skb_shinfo(skb)->frags[i].page);    /* 可能会导致释放该页面 */

            eat -= skb_shinfo(skb)->frags[i].size;

        } else { /* 重新调整非线性缓冲区 */

            skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i];

            if (eat) {   

                skb_shinfo(skb)->frags[k].page_offset += eat;

                skb_shinfo(skb)->frags[k].size -= eat;

                eat = 0;

            }

            k++;

        }

    }

 

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

 

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

 

    skb->tail += delta;    /* 更新tail指针 */

    skb->data_len -= delta;    /* 减少非线性缓冲区的数据长度 */

 

    return skb_tail_pointer(skb);    /* 返回线性数据缓冲区中的tail 指针 */

}

skb->tail += delta才是本函数的最根本的目的!

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

 

Tag标签: TCP/IP协议栈   pskb_pull_tail  
  • 专题推荐

About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规