在ip_rcv()函数当中遇到了pskb_may_pull()这个函数,进行了简单的注释:
/*
*pskb_may_pull确保skb->data指向的内存包含的数据至少为IP头部大小,由于每个
*IP数据包包括IP分片必须包含一个完整的IP头部。如果小于IP头部大小,则缺失
*的部分将从数据分片中拷贝。这些分片保存在skb_shinfo(skb)->frags[]中。
*/
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
当数据包进入协议栈往上层递交的过程中,比如在IP层,它需要对数据包的IP头部进行分析,比如头部合法性等,这时候就需要确保IP头部在线性缓冲区中,这样才能对它进行分析,如果在非线性缓冲区中,而非线性缓冲区是unmapped的page,因此就需要从这些unmapped page当中把数据复制到线性缓冲区中。
这个艰难的工作就是__pskb_pull_tail完成的。我将对它的代码进行单独的分析。
当然最好情况是skb->data指向的线性缓冲区中的数据至少是大于len的,这样就可以直接返回了成功了。
len一定是不能大于整个skb的数据总长的。这个就不必说明吧...
线性缓冲区中数据不足,不幸还是发生了...调用__pskb_pull_tail。
- static inline int pskb_may_pull(struct sk_buff *skb, unsigned int len)
- {
- /* skb_headlen定义为skb->len - skb->data_len。即skb->head指向的线性缓冲区里当前
- * 有效数据的长度。*/
- if (likely(len <= skb_headlen(skb)))
- return 1;
- if (unlikely(len > skb->len))
- return 0;
- return __pskb_pull_tail(skb, len-skb_headlen(skb)) != NULL;
- }
额...这个函数还是相当复杂的。首先需要了解的就是收到的包的数据在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中的数据可以进行上述的递归表达。
数据的连续性和这里描述的数据缓冲区中的数据是一致的。
该函数的定义为
数据先会出现在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参数也是需要从其他部分拷贝数据到线性缓冲区的长度。
该函数比较长,下面分段描述。
- /**
- * __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的线性数据区以及可能从非线性数据区,甚至可能从skb的frag_list链中拷贝
- * 总长度为delta的数据到skb_tail_pointer(skb)。这个函数也比较复杂,它甚至还递归!
- * 单独分析。
- */
- if (skb_copy_bits(skb, skb_headlen(skb), skb_tail_pointer(skb), delta))
- BUG();
- /* Optimization: no fragments, no reasons to preestimate
- * size of pulled pages. Superb.
- */
- if (!skb_shinfo(skb)->frag_list)
- goto pull_pages;
如果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;
- }
不幸总是要来临的,首先要找到那些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);
- }
更新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 指针 */
- }
copy->cleanup->update是本函数的基本流程。
没有评论:
发表评论