热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

linux内核协议栈接收数据流程(二)

linux网络接收数据第二站——内核协议栈之网络协议接口层上文分析到了linux内核接收流程将数据传到了__netif_receive_skb里,由此开始进入第二站




linux网络接收数据第二站——内核协议栈之网络协议接口层

上文分析到了linux内核接收流程将数据传到了__netif_receive_skb里,由此开始进入第二站,内核协议栈的网络协议接口层。


网络协议接口层入口

__netif_receive_skb函数定义如下:

static int __netif_receive_skb(struct sk_buff *skb)
{
int ret;
if (sk_memalloc_socks() && skb_pfmemalloc(skb)) {//if判断内容见下方解析
unsigned int noreclaim_flag;
/*
* PFMEMALLOC skbs are special, they should
* - be delivered to SOCK_MEMALLOC sockets only
* - stay away from userspace
* - have bounded memory usage
*
* Use PF_MEMALLOC as this saves us from propagating the allocation
* context down to all allocation sites.
*/

noreclaim_flag = memalloc_noreclaim_save();//将当前进程置上PF_MEMALLOC标识,表示允许使用reserved内存不用考虑水位问题
ret = __netif_receive_skb_one_core(skb, true);
memalloc_noreclaim_restore(noreclaim_flag);//撤销当前进程的PF_MEMALLOC标识
} else
ret = __netif_receive_skb_one_core(skb, false);
return ret;
}

先看if条件红的第一个函数sk_memalloc_socks,此函数判断memalloc_socks_key是否为0,如果为0 函数返回0,如果为1 函数返回1(不太可能的情况)。

#ifdef CONFIG_NET
DECLARE_STATIC_KEY_FALSE(memalloc_socks_key);//初始值为0
static inline int sk_memalloc_socks(void)
{
return static_branch_unlikely(&memalloc_socks_key);
}
#else
static inline int sk_memalloc_socks(void)
{
return 0;
}
#endif

memalloc_socks_key的功能可以从它被使用的场景分析出来,它的值在下面两个函数内被加减:

void sk_set_memalloc(struct sock *sk)
{
sock_set_flag(sk, SOCK_MEMALLOC);//在socket被设置了SOCK_MEMALLOC标志后key值加一
sk->sk_allocation |= __GFP_MEMALLOC;//设置后,此socket申请的skb会设置GFP_MEMALLOC标识
static_branch_inc(&memalloc_socks_key);//设置GFP_MEMALLOC后,skb可使用系统reserved的内存。
}
EXPORT_SYMBOL_GPL(sk_set_memalloc);
void sk_clear_memalloc(struct sock *sk)
{
sock_reset_flag(sk, SOCK_MEMALLOC);
sk->sk_allocation &= ~__GFP_MEMALLOC;
static_branch_dec(&memalloc_socks_key);//在socket连接清理缓存时key减一
sk_mem_reclaim(sk);//会对socket占用的内存进行缓存回收操作
}
EXPORT_SYMBOL_GPL(sk_clear_memalloc);

由上方的操作代码,可知memalloc_socks_key代表着当前系统内有权使用reserved内存区的socket连接数。

来看if判断条件涉及到的另一个函数skb_pfmemalloc:

static inline bool skb_pfmemalloc(const struct sk_buff *skb)//判断skb是否被置上了PFMEMALLOC标识,置上了返回true,未置上返回false
{
return unlikely(skb->pfmemalloc);//置上PFMEMALLOC代表着可使用系统保留的紧急内存部分。
}

所以__netif_receive_skb内的if判断的是当前socket连接是否拥有能使用系统保留区内存的权利并且当前skb使用了系统保留内存,因为置上了PFMEMALLOC的skb只能在设置了SOCK_MEMALLOC的socket连接上传递。

继续跟踪代码,__netif_receive_skb-> __netif_receive_skb_one_core

static int __netif_receive_skb_one_core(struct sk_buff *skb, bool pfmemalloc)
{
struct net_device *orig_dev = skb->dev;
struct packet_type *pt_prev = NULL;
int ret;
ret = __netif_receive_skb_core(skb, pfmemalloc, &pt_prev);//将pt_prev指针地址传进去
if (pt_prev)//如果__netif_receive_skb_core内对pt_prev指针赋了非空的值
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);//调用pt_prev->func,对应的是skb协议的处理函数
return ret;
}

__netif_receive_skb-> __netif_receive_skb_one_core -> __netif_receive_skb_core

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc,
struct packet_type **ppt_prev)
{
struct packet_type *ptype, *pt_prev;
rx_handler_func_t *rx_handler;
struct net_device *orig_dev;
bool deliver_exact = false;
int ret = NET_RX_DROP;
__be16 type;
net_timestamp_check(!netdev_tstamp_prequeue, skb);
trace_netif_receive_skb(skb);
orig_dev = skb->dev;//将接收此skb的设备指针记录到orig_dev
skb_reset_network_header(skb);
if (!skb_transport_header_was_set(skb))//skb传输层header是否有置好位置
skb_reset_transport_header(skb);
skb_reset_mac_len(skb);//这部分没有看懂为什么收上来的skb要reset一遍各层头部。
pt_prev = NULL;//此函数内创建的指针pt_prev初始为NULL
another_round:
skb->skb_iif = skb->dev->ifindex;
__this_cpu_inc(softnet_data.processed);//增加当前cpu处理包的计数
... ...
list_for_each_entry_rcu(ptype, &ptype_all, list) {//这里遍历ptype_all链表的每个对象,关于ptype_all见下方讲解链接
if (pt_prev)//第一次走到此句,pt_prev为空,直接跳过此if,pt_prev被ptype赋值,第二次及往后才会进入下方if流程
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
//到此处时,pt_prev指向的是上个循环ptype_all的最后一个对象,无论后文是进第二个list循环或是goto out或是rx_handler,都会将此包通过pt_prev递给上层
list_for_each_entry_rcu(ptype, &skb->dev->ptype_all, list) {//这里不太理解dev->ptype_all与ptype_all有什么差别,好像是和tcpdump抓包配置相关,不太确定
if (pt_prev)//第一次走到此句,将尚未递交的pt_prev送入deliver_skb
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;//这里将pt_prev置为skb->dev->ptype_all的第一个对象
}
skip_taps:
#ifdef CONFIG_NET_INGRESS//ingress流控相关,ingress是针对输入数据进行流控处理的
... ...
#endif
skb_reset_tc(skb);
skip_classify:
if (pfmemalloc && !skb_pfmemalloc_protocol(skb))//pfmemalloc标识与skb的memalloc必须同时置true才说明支持使用reserved内存
goto drop;
... ...//vlan相关
rx_handler = rcu_dereference(skb->dev->rx_handler);
if (rx_handler) {//有的模块(例如网桥)会注册rx_handler,注册就在此调用
if (pt_prev) {
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
switch (rx_handler(&skb)) {
case RX_HANDLER_CONSUMED:
ret = NET_RX_SUCCESS;
goto out;
case RX_HANDLER_ANOTHER:
goto another_round;
case RX_HANDLER_EXACT:
deliver_exact = true;
case RX_HANDLER_PASS:
break;
default:
BUG();
}
}
... ...//vlan相关
type = skb->protocol;
/* deliver only exact match when indicated */
if (likely(!deliver_exact)) {//初始化为false,只有函数上方的rx_handler里的RX_HANDLER_EXACT情况下才会为true
deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type, &ptype_base[ntohs(type) & PTYPE_HASH_MASK]);//deliver_exact为false代表不需要向指定设备发送,则向ptype_base内的当前skb所属协议传递此包。
}
deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type, &orig_dev->ptype_specific);//对当前设备指定的ptype特定协议传递此包
if (unlikely(skb->dev != orig_dev)) {//orig_dev初始化成skb->dev,所以这两个值不相等是不太可能出现的情况
deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
&skb->dev->ptype_specific);
}
if (pt_prev) {
if (unlikely(skb_orphan_frags_rx(skb, GFP_ATOMIC)))//skb_orphan_frags_rx没太看懂函数为了做啥
goto drop;
*ppt_prev = pt_prev;
} else {
drop:
if (!deliver_exact)//如果deliver_exact为false时,且pt_prev为NULL,说明当前skb没有调用deliver_skb传递给上层
atomic_long_inc(&skb->dev->rx_dropped);//那么此skb将被丢掉
else//deliver_exact为true时,且pt_prev为NULL,说明当前设备没有找到对应的rx_handler函数,skb将被丢掉
atomic_long_inc(&skb->dev->rx_nohandler);
kfree_skb(skb);
/* Jamal, now you will not able to escape explaining
* me how you were going to use this. :-)
*/

ret = NET_RX_DROP;
}
out:
return ret;
}

来看函数deliver_ptype_list_skb:

static inline void deliver_ptype_list_skb(struct sk_buff *skb, struct packet_type **pt,
struct net_device *orig_dev, __be16 type, struct list_head *ptype_list)
{
struct packet_type *ptype, *pt_prev = *pt;
list_for_each_entry_rcu(ptype, ptype_list, list) {//遍历ptype_list链表
if (ptype->type != type)//找到链表中匹配type值的对象
continue;
if (pt_prev)//如果pt_prev不为空,则将skb传递给deliver_skb
deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
*pt = pt_prev;//如果此时pt_prev是空,就会把空值继续赋给pt指针。如果不为空代表着待处理的对象,同样赋值给pt指针。
}

综合来看,函数__netif_receive_skb_core中的所有直接调用deliver_skb或是调用deliver_ptype_list_skb再传进deliver_skb的地方,所用的传参均是这三个参数:

deliver_skb(skb, pt_prev, orig_dev)

其中orig_dev初始化为skb->dev,skb是当前待传递的数据,pt_prev是指定的packet_type。

skb与orig_dev不用说了,pt_prev永远指向待传递skb处理的packet_type,如果值为NULL就说明没能将skb传递出去。

这部分有点容易看乱,可以参考下面概括的简化后的函数内容:

__netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc, struct packet_type **ppt_prev){
struct packet_type *pt_prev=NULL;//初始化为空
list_for_each_entry_rcu(ptype, &ptype_all, list) {//第n次走进这处循环时,pt_prev记录着链表中第n-1个对象,循环内处理(第n-1个对象),离开这处循环时,pt_prev记录着链表中第n个对象
if (pt_prev)//第一次走到这里为空,不进if内容。
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;//pt_prev代表着下一个待处理的ptype
}//最后一次离开这个循环时,pt_prev记录着链表中最后一个对象,该对象等待处理
if (判断条件){
if (pt_prev) {//如果为空,说明前面的流程未找到skb关于packet_type的处理函数,从未成功通过deliver_skb递交过skb(未进上方循环)
ret = deliver_skb(skb, pt_prev, orig_dev);//不为空,则处理掉当前流程内找到的最后一个packet_type
pt_prev = NULL;//置为空是为了告知后续流程,暂时没有待处理的packet_type
}
}
if (判断条件){
deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type, &ptype_base[ntohs(type) &PTYPE_HASH_MASK]);//翻到上方该函数的内容,可以看到此函数内对于pt_prev指针的操作可总结为如果能找到匹配的packet_type就赋值给pt_prev(无论传进去时pt_prev是否有值逻辑都正确),如果找不到就保持传进来的原值
}
if (pt_prev) {//如果pt_prev不为空代表还有尚未处理的packet_type
*ppt_prev = pt_prev;//需要传递给上层函数做处理
}
return;//在这里ppt_prev指向了最后一个待处理的packet_type
}
__netif_receive_skb_one_core -> __netif_receive_skb_core
static int __netif_receive_skb_one_core(struct sk_buff *skb, bool pfmemalloc)
{
struct net_device *orig_dev = skb->dev;
struct packet_type *pt_prev = NULL;
int ret;
ret = __netif_receive_skb_core(skb, pfmemalloc, &pt_prev);//回到上层调用函数处,pt_prev指向的是__netif_receive_skb_core函数走到最后时的pt_prev,等于__netif_receive_skb_core的参数ppt_prev
if (pt_prev)//这里会直接调用最后一个待处理的packet_type的func回调函数,处理了__netif_receive_skb_core退出时遗留的那个packet_type
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
return ret;
}

经过上文分析,所有的路径最终都会到deliver_skb函数内:
__netif_receive_skb-> __netif_receive_skb_one_core -> __netif_receive_skb_core -> deliver_skb

static inline int deliver_skb(struct sk_buff *skb,
struct packet_type *pt_prev,
struct net_device *orig_dev)
{
if (unlikely(skb_orphan_frags_rx(skb, GFP_ATOMIC)))
return -ENOMEM;
refcount_inc(&skb->users);//增加users计数
return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);//调用当前数据包对应的协议处理函数
}

对于tcp、udp数据包,在这里会调ip协议的处理函数ip_rcv,从这里正式进入ip协议层。
至于上文分析的pt_prev为什么要这么设计,下一篇文章会分析到这一点。
在这里插入图片描述







推荐阅读
  • 本文详细解析了 Android 系统启动过程中的核心文件 `init.c`,探讨了其在系统初始化阶段的关键作用。通过对 `init.c` 的源代码进行深入分析,揭示了其如何管理进程、解析配置文件以及执行系统启动脚本。此外,文章还介绍了 `init` 进程的生命周期及其与内核的交互方式,为开发者提供了深入了解 Android 启动机制的宝贵资料。 ... [详细]
  • 微信小程序实现类似微博的无限回复功能,内置云开发数据库支持
    本文详细介绍了如何利用微信小程序实现类似于微博的无限回复功能,并充分利用了微信云开发的数据库支持。文中不仅提供了关键代码片段,还包含了完整的页面代码,方便开发者按需使用。此外,HTML页面中包含了一些示例图片,开发者可以根据个人喜好进行替换。文章还将展示详细的数据库结构设计,帮助读者更好地理解和实现这一功能。 ... [详细]
  • 深入解析:React与Webpack配置进阶指南(第二部分)
    在本篇进阶指南的第二部分中,我们将继续探讨 React 与 Webpack 的高级配置技巧。通过实际案例,我们将展示如何使用 React 和 Webpack 构建一个简单的 Todo 应用程序,具体包括 `TodoApp.js` 文件中的代码实现,如导入 React 和自定义组件 `TodoList`。此外,我们还将深入讲解 Webpack 配置文件的优化方法,以提升开发效率和应用性能。 ... [详细]
  • FastDFS Nginx 扩展模块的源代码解析与技术剖析
    FastDFS Nginx 扩展模块的源代码解析与技术剖析 ... [详细]
  • MySQL 5.7 学习指南:SQLyog 中的主键、列属性和数据类型
    本文介绍了 MySQL 5.7 中主键(Primary Key)和自增(Auto-Increment)的概念,以及如何在 SQLyog 中设置这些属性。同时,还探讨了数据类型的分类和选择,以及列属性的设置方法。 ... [详细]
  • com.hazelcast.config.MapConfig.isStatisticsEnabled()方法的使用及代码示例 ... [详细]
  • 本文介绍了如何使用 Node.js 和 Express(4.x 及以上版本)构建高效的文件上传功能。通过引入 `multer` 中间件,可以轻松实现文件上传。首先,需要通过 `npm install multer` 安装该中间件。接着,在 Express 应用中配置 `multer`,以处理多部分表单数据。本文详细讲解了 `multer` 的基本用法和高级配置,帮助开发者快速搭建稳定可靠的文件上传服务。 ... [详细]
  • 为了在Hadoop 2.7.2中实现对Snappy压缩和解压功能的原生支持,本文详细介绍了如何重新编译Hadoop源代码,并优化其Native编译过程。通过这一优化,可以显著提升数据处理的效率和性能。此外,还探讨了编译过程中可能遇到的问题及其解决方案,为用户提供了一套完整的操作指南。 ... [详细]
  • ### 优化后的摘要本学习指南旨在帮助读者全面掌握 Bootstrap 前端框架的核心知识点与实战技巧。内容涵盖基础入门、核心功能和高级应用。第一章通过一个简单的“Hello World”示例,介绍 Bootstrap 的基本用法和快速上手方法。第二章深入探讨 Bootstrap 与 JSP 集成的细节,揭示两者结合的优势和应用场景。第三章则进一步讲解 Bootstrap 的高级特性,如响应式设计和组件定制,为开发者提供全方位的技术支持。 ... [详细]
  • Presto:高效即席查询引擎的深度解析与应用
    本文深入解析了Presto这一高效的即席查询引擎,详细探讨了其架构设计及其优缺点。Presto通过内存到内存的数据处理方式,显著提升了查询性能,相比传统的MapReduce查询,不仅减少了数据传输的延迟,还提高了查询的准确性和效率。然而,Presto在大规模数据处理和容错机制方面仍存在一定的局限性。本文还介绍了Presto在实际应用中的多种场景,展示了其在大数据分析领域的强大潜力。 ... [详细]
  • 在JavaWeb项目架构中,NFS(网络文件系统)的实现与优化是关键环节。NFS允许不同主机系统通过局域网共享文件和目录,提高资源利用率和数据访问效率。本文详细探讨了NFS在JavaWeb项目中的应用,包括配置、性能优化及常见问题的解决方案,旨在为开发者提供实用的技术参考。 ... [详细]
  • HBase Java API 进阶:过滤器详解与应用实例
    本文详细探讨了HBase 1.2.6版本中Java API的高级应用,重点介绍了过滤器的使用方法和实际案例。首先,文章对几种常见的HBase过滤器进行了概述,包括列前缀过滤器(ColumnPrefixFilter)和时间戳过滤器(TimestampsFilter)。此外,还详细讲解了分页过滤器(PageFilter)的实现原理及其在大数据查询中的应用场景。通过具体的代码示例,读者可以更好地理解和掌握这些过滤器的使用技巧,从而提高数据处理的效率和灵活性。 ... [详细]
  • 本文深入探讨了CGLIB BeanCopier在Bean对象复制中的应用及其优化技巧。相较于Spring的BeanUtils和Apache的BeanUtils,CGLIB BeanCopier在性能上具有显著优势。通过详细分析其内部机制和使用场景,本文提供了多种优化方法,帮助开发者在实际项目中更高效地利用这一工具。此外,文章还讨论了CGLIB BeanCopier在复杂对象结构和大规模数据处理中的表现,为读者提供了实用的参考和建议。 ... [详细]
  • C#微信开发入门教程第二篇:新手快速上手指南,含详细视频讲解
    在距离上次课程一个多星期后,我们终于带来了第二讲的内容。虽然原计划是一周一次更新,但由于工作繁忙有所延迟。近期在交流群中发现,一些初学者已经能够熟练调用微信接口,但对微信公众平台的消息接收处理机制还不够了解。因此,本次课程将详细介绍如何高效处理微信公众平台的消息接收,并提供详细的视频讲解,帮助大家快速上手。 ... [详细]
  • MySQL:不仅仅是数据库那么简单
    MySQL不仅是一款高效、可靠的数据库管理系统,它还具备丰富的功能和扩展性,支持多种存储引擎,适用于各种应用场景。从简单的网站开发到复杂的企业级应用,MySQL都能提供强大的数据管理和优化能力,满足不同用户的需求。其开源特性也促进了社区的活跃发展,为技术进步提供了持续动力。 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有