额...这个函数还是相当复杂的。首先需要了解的就是收到的包的数据在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是本函数的基本流程。
阅读(8588) | 评论(0) | 转发(5) |