热门标签 | 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为什么要这么设计,下一篇文章会分析到这一点。
在这里插入图片描述







推荐阅读
  • 题目描述:给定 n 把雨伞和 m 个人,t 分钟后开始下雨。求在每个人只能使用一把雨伞的情况下,最多有多少人可以拿到雨伞。 ... [详细]
  • 本文探讨了在使用 STL 容器(如 map、vector 和 list)插入自定义类对象或指针时,构造函数和析构函数的调用情况,以及可能引发的问题。 ... [详细]
  • 深入解析mt_allocator内存分配器(二):多线程与单线程场景下的实现
    本文详细介绍了mt_allocator内存分配器在多线程和单线程环境下的实现机制。该分配器以2的幂次方字节为单位分配内存,支持灵活的配置和高效的性能。文章分为内存池特性描述、内存池实现、单线程内存池实现、内存池策略类实现及多线程内存池实现等部分,深入探讨了内存池的初始化、内存分配与回收的具体实现。 ... [详细]
  • 深入解析C++中的红黑树
    本文将详细介绍二叉搜索树的一种重要变体——红黑树,探讨其通过颜色标记维持平衡的机制,以及它在实际应用中的优势。 ... [详细]
  • 本文介绍了一种算法,用于在一个给定的二叉树中找到一个节点,该节点的子树包含最大数量的值小于该节点的节点。如果存在多个符合条件的节点,可以选择任意一个。 ... [详细]
  • 本文详细介绍如何在Spring Boot项目中集成和使用JPA,涵盖JPA的基本概念、Spring Data JPA的功能以及具体的操作步骤,帮助开发者快速掌握这一强大的持久化技术。 ... [详细]
  • 本文探讨了SQLAlchemy ORM框架中如何利用外键和关系(relationship)来建立表间联系,简化复杂的查询操作。通过示例代码详细解释了relationship的定义、使用方法及其与外键的相互作用。 ... [详细]
  • C基本语法C程序可以定义为对象的集合,这些对象通过调用彼此的方法进行交互。现在让我们简要地看一下什么是类、对象,方法、即时变量。对象-对象具有状态和行为 ... [详细]
  • 基于Flutter实现风车加载组件的制作_Android
    Flutter官方提供了诸如 CircularProgressIndicator和 LinearProgressIndicator两种常见的加载指示组件,但是说实话,实在太普通,所 ... [详细]
  • 时序数据是指按时间顺序排列的数据集。通过时间轴上的数据点连接,可以构建多维度报表,揭示数据的趋势、规律及异常情况。 ... [详细]
  • 本文主要解决了在编译CM10.2时出现的关于Samsung Exynos 4 HDMI HAL库中SecHdmiV4L2Utils.cpp文件的编译错误。 ... [详细]
  • 本文探讨了如何利用数组来构建二叉树,并介绍了通过队列实现的二叉树层次遍历方法。通过具体的C++代码示例,详细说明了构建及打印二叉树的过程。 ... [详细]
  • 迷宫问题_____________________________________________问题描述:给定一个二维数组如下所示,数值1位墙壁,0 ... [详细]
  • 了解如何有效清除远程桌面连接中的缓存记录,对于提升服务器安全性至关重要。本文将指导您完成这一过程。 ... [详细]
  • 微信小程序支付官方参数小程序中代码后端发起支付代码支付回调官方参数文档地址:https:developers.weixin.qq.comminiprogramdeva ... [详细]
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社区 版权所有