/*
* Main IP Receive routine.
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
const struct iphdr *iph;
struct net *net;
u32 len; /* When the interface is in promisc. mode, drop all the crap
* that it receives, do not try to analyse it.
*/
if (skb->pkt_type == PACKET_OTHERHOST)
goto drop; net = dev_net(dev);
IP_UPD_PO_STATS_BH(net, IPSTATS_MIB_IN, skb->len); skb = skb_share_check(skb, GFP_ATOMIC);
if (!skb) {
IP_INC_STATS_BH(net, IPSTATS_MIB_INDISCARDS);
goto out;
}

/*     *pskb_may_pull确保skb->data指向的内存包含的数据至少为IP头部大小,由于每个

*IP数据包包括IP分片必须包含一个完整的IP头部。如果小于IP头部大小,则缺失

*的部分将从数据分片中拷贝。这些分片保存在skb_shinfo(skb)->frags[]中。

*/

if (!pskb_may_pull(skb, sizeof(struct iphdr)))
goto inhdr_error;
----------------------------------------------------------- }
/**
* skb_share_check - check if buffer is shared and if so clone it
* @skb: buffer to check
* @pri: priority for memory allocation
*
* If the buffer is shared the buffer is cloned and the old copy
* drops a reference. A new clone with a single reference is returned.
* If the buffer is not shared the original buffer is returned. When
* being called from interrupt status or with spinlocks held pri must
* be GFP_ATOMIC.
*
* NULL is returned on a memory allocation failure.
*/
static inline struct sk_buff *skb_share_check(struct sk_buff *skb, gfp_t pri)
{
might_sleep_if(gfpflags_allow_blocking(pri));
if (skb_shared(skb)) {
struct sk_buff *nskb = skb_clone(skb, pri); if (likely(nskb))
consume_skb(skb);
else
kfree_skb(skb);
skb = nskb;
}
return skb;
}

为什么要这样做??p_rcv调用skb_share_check函数后,自己克隆了一个sk_buff,释放传入的sk_buff-----为了啥???

-----------原来的skb可能被共享,如果需要修改skb,则会影响共享该sbk的其他函数,因此如果被共享,则克隆一份,再调用kfree_skb(实际只是skb->users--,减少引用计数)

----------__netif_receive_skb_core 函数中 会 扫描ptype_all链表与ptype_base哈希表。ptype_base是各种已经注册的协议的哈希表,根据每个数据包协议不同分派给不同的协议来处理,在这里递增skb进行索引计数

在调用 skb_pull() 去掉外层协议头之前,通常先调用此函数判断一下是否有足够的数据用于“pull”。
如果线性 buffer足够 pull,则返回1;
如果需要 pull 的数据超过 skb->len,则返回0;
最后,调用__pskb_pull_tail() 来检查 page buffer 有没有足够的数据用于 pull。

sk_buff->len,表示当前协议下的数据长度,包括线性缓冲区的数据长度和分片的数据长度。线性缓冲区的长度从skb->data指针开始计算。注意skb->data是随着协议不同而变化的

sk_buff->data_len,只表示分片的数据长度

sk_buff->truesize,表示sk_buff->len 加上struct sk_buff结构大小

static inline int pskb_may_pull(struct sk_buff *skb, unsigned int len)
{
/*
sk_buff->len,表示当前协议下的数据长度,包括线性缓冲区的数据长度和分片的数据长度。线性缓冲区的长度从skb->data指针开始计算。注意skb->data是随着协议不同而变化的 sk_buff->data_len,只表示分片的数据长度 sk_buff->truesize,表示sk_buff->len 加上struct sk_buff结构大小
*/
if (likely(len <= skb_headlen(skb)))//skb->data 到 skb->tail之间的数据足够len长度
return 1;
if (unlikely(len > skb->len))//len长度超过skb总长度
return 0;//移动后边的数据到skb->data中
return __pskb_pull_tail(skb, len - skb_headlen(skb)) != NULL;
} /**
* __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.
*///delta为需要从frags或者frag_list向前移动的数据量
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.
*///eat为去除当前skb可用内存,还需要多少内存
int i, k, eat = (skb->tail + delta) - skb->end; if (eat > 0 || skb_cloned(skb)) {//判断当前skb是否被克隆
if (pskb_expand_head(skb, 0, eat > 0 ? eat + 128 : 0,
GFP_ATOMIC))//对sk_buff重新分配头
return NULL;
}
/从skb的offset(skb->tail),拷贝delta个字节到skb->tail之后
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_has_frag_list(skb))
goto pull_pages;
//由于数据已经拷贝到了skb->data中,因此需要释放frags,frag_list中被拷贝过的数据
//计算从frags数组中拷贝的数据量
/* Estimate size of pulled pages. */
eat = delta;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
//寻找到满足eat这么多数据量的最后一个page
int size = skb_frag_size(&skb_shinfo(skb)->frags[i]); if (size >= eat)
goto pull_pages;
eat -= size;
} /* 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仍不为0,说明从frag_list中进行了拷贝,释放frag_list
if (eat) {
struct sk_buff *list = skb_shinfo(skb)->frag_list;
struct sk_buff *clone = NULL;
struct sk_buff *insp = NULL; do {
BUG_ON(!list);//list为null,说明数据量不够 if (list->len <= eat) {//当前skb的长度小于需要的长度
/* Eaten as whole. */
eat -= list->len;//找到下一个skb
list = list->next;//list指向下一个需要的skb
insp = list;//insp指向当前的skb
} else {
/* Eaten partially. */
//此时insp指向前一个skb
//说明当前skb可以满足需要的数据量
if (skb_shared(list)) {//但是当前skb被共享
/* Sucks! We need to fork list. :-( */
clone = skb_clone(list, GFP_ATOMIC);//对最后那个拷贝不完全的skb,进行克隆
if (!clone)
return NULL;
//list指向当前被克隆的的skb
//insp指向下一个skb
insp = list->next;
list = clone;
} else {
/* This may be pulled without
* problems. *///list与insp指向当前的skb
insp = list;
}//修改最后一个skb,移动指针,删除掉被拷贝的数据
if (!pskb_pull(list, eat)) {
kfree_skb(clone);///递减clone的引用计数
return NULL;
}
break;
}
} while (eat);
//list指向frag_list头
//直到list遍历到数据量足够的最后一个skb
/* Free pulled out fragments. */
while ((list = skb_shinfo(skb)->frag_list) != insp) {
skb_shinfo(skb)->frag_list = list->next;
kfree_skb(list);
}
/* And insert new clone at head. */
if (clone) {//说明最后一个skb只被拷贝了一部分,将此skb挂到frag_list头
clone->next = list;
skb_shinfo(skb)->frag_list = clone;
}
}
/* Success! Now we may commit changes to skb data. */ pull_pages:
eat = delta;
k = 0;//释放frags中的page
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int size = skb_frag_size(&skb_shinfo(skb)->frags[i]); if (size <= eat) {
skb_frag_unref(skb, i);
eat -= size;
} else {
skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i];
if (eat) {
skb_shinfo(skb)->frags[k].page_offset += eat;
skb_frag_size_sub(&skb_shinfo(skb)->frags[k], eat);
eat = 0;
}
k++;
}
}
skb_shinfo(skb)->nr_frags = k; skb->tail += delta;
skb->data_len -= delta; return skb_tail_pointer(skb);
}
EXPORT_SYMBOL(__pskb_pull_tail);
/* This data is invariant across clones and lives at
* the end of the header data, ie. at skb->end.
*/
struct skb_shared_info {
unsigned char nr_frags;
__u8 tx_flags;
unsigned short gso_size;
/* Warning: this field is not always filled in (UFO)! */
unsigned short gso_segs;
unsigned short gso_type;
struct sk_buff *frag_list;
struct skb_shared_hwtstamps hwtstamps;
u32 tskey;
__be32 ip6_frag_id; /*
* Warning : all fields before dataref are cleared in __alloc_skb()
*/
atomic_t dataref; /* Intermediate layers must ensure that destructor_arg
* remains valid until skb destructor */
void * destructor_arg; /* must be last field, see pskb_expand_head() */
skb_frag_t frags[MAX_SKB_FRAGS];
};

typedef struct skb_frag_struct skb_frag_t;

struct skb_frag_struct {
struct {
struct page *p;
} page;
#if (BITS_PER_LONG > 32) || (PAGE_SIZE >= 65536)
__u32 page_offset;
__u32 size;
#else
__u16 page_offset;
__u16 size;
#endif
};

 

注意:struct sk_buff *frag_list;

 /* must be last field, see pskb_expand_head() */
skb_frag_t frags[MAX_SKB_FRAGS];

而frag_list则指向链表的下一个skb,合并skb数据的时候,先是合并frags数组的数据,然后再在frag_list里遍历下一个skb。frag_list一般用于IP分片的场景中,相比而言frags数组只是简单的scatter-gather IO

frags[]用于分散收集I/O缓冲区
frag_list用于IP片段

ip_rcv 中使用skb_share_check的更多相关文章

  1. skb详细解析【转】

    skb详细解析[转]    摘自:http://blog.chinaunix.net/uid-30035229-id-4883992.html     在自己的模块发送函数中,需要对skb进行重新构造 ...

  2. IP 层收发报文简要剖析1-ip报文的输入

    ip层数据包处理场景如下: 网络层处理数据包文时需要和路由表以及邻居系统打交道.输入数据时,提供输入接口给链路层调用,并调用传输层的输入接口将数据输入到传输层. 在输出数据时,提供输出接口给传输层,并 ...

  3. netfiler源代码分析之框架介绍

    netfiler框架是在内核协议栈实现的基础上完成的,在报文从网口接收,路由等方法实现基础上使用NF_HOOK调用相应的钩子来进入netfiler框架的处理,如 ip_rcv之后会调用NF_HOOK( ...

  4. (转)Netfilter分析

    看到一篇讲Netfilter框架的,如果有一点基础了的话对于捋清整个框架很好帮助,转下来细细阅读. 转自http://aichundi.blog.163.com/blog/static/7013846 ...

  5. 深入Linux网络核心堆栈(对于netfilter的用法和讲解)

    http://blog.csdn.net/wswifth/article/details/5115475 注册一个hook函数是围绕nf_hook_ops数据结构的一个非常简单的操作,nf_hook_ ...

  6. OpenVPN多实例优化的思考过程

    1.sss 当构建组件之间的关系已经错综复杂到接近于一张全然图的时候,就要换一个思路了,或者你须要重构整个系统,或者你将又一次实现一个. 2.TAP网卡和TUN网卡 2.1.TAP的优势 1.方便组网 ...

  7. Linux内核分析 - 网络[十四]:IP选项

    Linux内核分析 - 网络[十四]:IP选项 标签: linux内核网络structsocketdst 2012-04-25 17:14 5639人阅读 评论(1) 收藏 举报  分类: 内核协议栈 ...

  8. netfilter 学习摘要

    netfilter 子系入口在L3,完成后把数据包发往L4 netfilter 主要功能: 数据包选择(iptables) 数据包过滤 网络地址转换(NAT) 数据包操纵(在路由选择之前或之后修改数据 ...

  9. Linux netfliter 架构

    netfliter 简介 netfilter是在Linux 2.4.X内核引入的一个子系统,它提供了一个抽象的.通用框架,这个框架提供了一整套的钩子函数的管理机制.包括钩子函数的原型定义,注册,注销等 ...

随机推荐

  1. css做模糊处理

    -webkit-filter: blur(9px); filter: blur(9px);

  2. Vim最强调试插件:vimspector

    最近看到了韦大在知乎的回答后,想去试用一下vimspector,却发现vimspector诞生两年了却没有介绍它的中文资料.我查阅官方文档遇到不少困难,在这里记录折腾出来的结果,与大家分享. vims ...

  3. ZooKeeper伪分布式集群安装及使用

    ZooKeeper伪分布式集群安装及使用 让Hadoop跑在云端系列文章,介绍了如何整合虚拟化和Hadoop,让Hadoop集群跑在VPS虚拟主机上,通过云向用户提供存储和计算的服务. 现在硬件越来越 ...

  4. day55 Pyhton 前端Jquery07

    昨日回顾: 表单,点击submit提交以后,服务端受到信息 import socket import pymysql from urllib.parse import unquote def run( ...

  5. pytest文档59-运行未提交git的用例(pytest-picked)

    前言 我们每天写完自动化用例后都会提交到 git 仓库,随着用例的增多,为了保证仓库代码的干净,当有用例新增的时候,我们希望只运行新增的未提交 git 仓库的用例. pytest-picked 插件可 ...

  6. C++实现求离散数学命题公式的真值表

    一.实验内容 (1)求任意一个命题公式的真值表. (2)利用真值表求任意一个命题公式的主范式. (3)利用真值表进行逻辑推理. 注:(2)和(3)可在(1)的基础上完成. 二.实验目的 真值表是命题逻 ...

  7. C++学习---栈的构建及操作

    一.顺序栈 #include <iostream> using namespace std; #define MAXSIZE 100 //栈的最大容量 typedef struct { i ...

  8. update不能直接使用select的返回结果

    update不能直接使用select的返回结果,这是会报错的,这是SQL的语法规定的,若想在update中与select结合使用,sql需要这样写: 1.其中field1,field2为表中的字段名 ...

  9. git常见操作和指令

    1.指令集 1.1 本地与远程操作 创建文件 echo > README.md(文件名) 创建文件时输入信息 echo "(message)" >> README ...

  10. Linux服务器部署redis常见问题处理

    redis开启和禁用登陆密码校验 1. 开启登陆密码校验 在redis-cli命令行工具中执行如下命令: config set requirepass yourpassword2. 禁用登陆密码校验 ...