深入理解Linux网络(五):TCP接收唤醒

深入理解Linux网络(五):TCP接收唤醒

TCP接收唤醒由软中断提供服务。
在这里插入图片描述

软中断(也就是 Linux ⾥的 ksoftirqd 进程)⾥收到数据包以后,发现是 tcp 的包的话就会执⾏到 tcp_v4_rcv 函数。接着如果是 ESTABLISH 状态下的数据包,则最终会把数据拆出来放到对应 socket 的接收队列中。然后调⽤ sk_data_ready 来唤醒⽤户进程。

// file: net/ipv4/tcp_ipv4.c
int tcp_v4_rcv(struct sk_buff *skb)
{......th = tcp_hdr(skb); //获取tcp headeriph = ip_hdr(skb); //获取ip header//根据数据包 header 中的 ip、端⼝信息查找到对应的socketsk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);......//socket 未被⽤户锁定if (!sock_owned_by_user(sk)) {{if (!tcp_prequeue(sk, skb))ret = tcp_v4_do_rcv(sk, skb);}}
}

在 tcp_v4_rcv 中⾸先根据收到的⽹络包的 header ⾥的 source 和 dest 信息来在本机上查询对应的 socket。找到以后,我们直接进⼊接收的主体函数 tcp_v4_do_rcv 来看。

//file: net/ipv4/tcp_ipv4.c
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{if (sk->sk_state == TCP_ESTABLISHED) {//执⾏连接状态下的数据处理if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {rsk = sk;goto reset;}return 0;}//其它⾮ ESTABLISH 状态的数据包处理......
}

我们假设处理的是 ESTABLISH 状态下的包,这样就⼜进⼊ tcp_rcv_established 函数中进⾏处理。

//file: net/ipv4/tcp_input.c
int tcp_rcv_established(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{......//接收数据到队列中eaten = tcp_queue_rcv(sk, skb, tcp_header_len,&fragstolen);//数据 ready,唤醒 socket 上阻塞掉的进程sk->sk_data_ready(sk, 0);

在 tcp_rcv_established 中通过调⽤ tcp_queue_rcv 函数中完成了将接收数据放到 socket 的接收队列上。
在这里插入图片描述

//file: net/ipv4/tcp_input.c
static int __must_check tcp_queue_rcv(struct sock *sk, structsk_buff *skb, int hdrlen, bool *fragstolen)
{//把接收到的数据放到 socket 的接收队列的尾部if (!eaten) {__skb_queue_tail(&sk->sk_receive_queue, skb);skb_set_owner_r(skb, sk);}return eaten;
}

调⽤ tcp_queue_rcv 接收完成之后,接着再调⽤ sk_data_ready 来唤醒在socket上等待的⽤户进程
这⼜是⼀个函数指针。 回想上⾯我们在 创建 socket 流程⾥执⾏到的 sock_init_data 函数,在这个函数⾥已经把 sk_data_ready 设置成 sock_def_readable 函数了。它是默认的数据就绪处理函数。

//file: net/core/sock.c
static void sock_def_readable(struct sock *sk, int len)
{struct socket_wq *wq;rcu_read_lock();wq = rcu_dereference(sk->sk_wq);//有进程在此 socket 的等待队列if (wq_has_sleeper(wq))//唤醒等待队列上的进程wake_up_interruptible_sync_poll(&wq->wait, POLLIN | POLLPRI| POLLRDNORM | POLLRDBAND);sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);rcu_read_unlock();
}

在 sock_def_readable 中再⼀次访问到了 sock->sk_wq 下的wait。回忆下我们前⾯调⽤ recvfrom 执⾏的最后,通过 DEFINE_WAIT(wait) 将当前进程关联的等待队列添加到 sock->sk_wq 下的 wait ⾥了。
那接下来就是调⽤ wake_up_interruptible_sync_poll 来唤醒在 socket 上因为等待数据⽽被阻塞掉的进程了。
在这里插入图片描述

//file: include/linux/wait.h
#define wake_up_interruptible_sync_poll(x, m) \__wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m))//file: kernel/sched/core.c
void __wake_up_sync_key(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key)
{unsigned long flags;int wake_flags = WF_SYNC;if (unlikely(!q))return;if (unlikely(!nr_exclusive))wake_flags = 0;spin_lock_irqsave(&q->lock, flags);__wake_up_common(q, mode, nr_exclusive, wake_flags, key);spin_unlock_irqrestore(&q->lock, flags);
}

__wake_up_common 实现唤醒。这⾥注意下, 该函数调⽤是参数 nr_exclusive 传⼊的是 1,这⾥指的是即使是有多个进程都阻塞在同⼀个 socket 上,也只唤醒 1 个进程。其作⽤是为了避免惊群

//file: kernel/sched/core.c
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,int nr_exclusive, int wake_flags, void *key)
{wait_queue_t *curr, *next;list_for_each_entry_safe(curr, next, &q->task_list, task_list) {unsigned flags = curr->flags;if (curr->func(curr, mode, wake_flags, key) &&(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)break;}
}

在 __wake_up_common 中找出⼀个等待队列项 curr,然后调⽤其 curr->func。在 recv 函数执⾏的时候,使⽤ DEFINE_WAIT() 定义等待队列项的细节,内核把 curr->func 设置成了 autoremove_wake_function。

//file: include/linux/wait.h
#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)
#define DEFINE_WAIT_FUNC(name, function) \wait_queue_t name = { \.private = current, \.func = function, \.task_list = LIST_HEAD_INIT((name).task_list), \}

autoremove_wake_function 又调⽤了 default_wake_function。

//file: kernel/sched/core.c
int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags, void *key)
{return try_to_wake_up(curr->private, mode, wake_flags);
}

调⽤ try_to_wake_up 时传⼊的 task_struct 是 curr->private。这个就是当时因为等待⽽被阻塞的进程项。 当这个函数执⾏完的时候,在 socket 上等待⽽被阻塞的进程就被推⼊到可运⾏队列⾥了,这⼜将是⼀次进程上下⽂切换的开销。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/381821.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql JSON特性优化

有朋友问到,mysql如果要根据json中的某个属性过滤,数据量大的话,性能很差,要如何提高性能? 为什么要用json串? 由于一些特定场景,mysql需要用到json串,例如文档,不同的…

【LabVIEW作业篇 - 5】:水仙花数、数组与for循环的连接

文章目录 水仙花数数组与for循环的连接 水仙花数 水仙花数,是指一个3位数,它的每个位上的数字的3次幂之和等于它本身。如371 3^3 7^3 1^3,则371是一个水仙花数。 思路:水仙花数是一个三位数,通过使用for循环&#xf…

RabbitMQ的学习和模拟实现|muduo库的介绍和使用

muduo库 项目仓库:https://github.com/ffengc/HareMQ muduo库 muduo库是什么快速上手搭建服务端快速上手搭建客户端上面搭建的服务端-客户端通信还有什么问题?muduo库中的protobuf基于muduo库中的protobuf协议实现一个服务器 muduo库是什么 Muduo由陈硕大佬开…

ReadAgent,一款具有要点记忆的人工智能阅读代理

人工智能咨询培训老师叶梓 转载标明出处 现有的大模型(LLMs)在处理长文本时受限于固定的最大上下文长度,并且当输入文本越来越长时,性能往往会下降,即使在没有超出明确上下文窗口的情况下,LLMs 的性能也会随…

pytorch 笔记:torch.optim.Adam

torch.optim.Adam 是一个实现 Adam 优化算法的类。Adam 是一个常用的梯度下降优化方法,特别适合处理大规模数据集和参数的深度学习模型 torch.optim.Adam(params, lr0.001, betas(0.9, 0.999), eps1e-08, weight_decay0, amsgradFalse, *, foreachNone, maximizeFa…

OpenAI从GPT-4V到GPT-4O,再到GPT-4OMini简介

OpenAI从GPT-4V到GPT-4O,再到GPT-4OMini简介 一、引言 在人工智能领域,OpenAI的GPT系列模型一直是自然语言处理的标杆。随着技术的不断进步,OpenAI推出了多个版本的GPT模型,包括视觉增强的GPT-4V(GPT-4 with Vision&…

HarmonyOS应用开发者高级认证,Next版本发布后最新题库 - 单选题序号3

基础认证题库请移步:HarmonyOS应用开发者基础认证题库 注:有读者反馈,题库的代码块比较多,打开文章时会卡死。所以笔者将题库拆分,单选题20个为一组,多选题10个为一组,题库目录如下,…

hive3 hql脚本传递参数

在数仓的构建过程中,需要配置hive的调度任务,这时就需要对hive hql脚本进行封装,将参数提取出来,作为变量进行配置,比如日期、类型等。 hive3版本,hive -f 在执行sql脚本文件的时候是可以传递参数。 具体…

GitHub 令牌泄漏, Python 核心资源库面临潜在攻击

TheHackerNews网站消息,软件供应链安全公司 JFrog 的网络安全研究人员称,他们发现了一个意外泄露的 GitHub 令牌,可授予 Python 语言 GitHub 存储库、Python 软件包索引(PyPI)和 Python 软件基金会(PSF&…

系统架构设计师教程 第3章 信息系统基础知识-3.5 专家系统-解读

系统架构设计师教程 第3章 信息系统基础知识-3.5 专家系统(ES) 3.5.1 人工智能3.5.1.1 人工智能的特点3.5.1.2 人工智能的主要分支3.5.2 ES的概念3.5.2.1 ES 概述3.5.2.2 与传统程序的区别3.5.3 ES的特点3.5.4 ES的组成3.5.4.1 知识库3.5.4.2 综合数据库3.5.4.3 推理机3.5.4.…

google 浏览器插件开发简单学习案例:TodoList

参考: google插件支持: https://blog.csdn.net/weixin_42357472/article/details/140412993 这里是把前面做的TodoList做成google插件,具体网页可以参考下面链接 TodoList网页: https://blog.csdn.net/weixin_42357472/article/de…

MongoDB教程(十八):MongoDB MapReduce

💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 文章目录 引言一、MapRed…

OpenCV 图像旋转和平移 数学和代码原理详解

文章目录 数学原理旋转矩阵平移和旋转合成变换矩阵应用在OpenCV中的实现 代码关键点解读完整代码C代码:Python代码: 在OpenCV中进行图像旋转涉及到一些基本的几何变换和图像处理操作。 数学原理 在图像旋转中,背后的数学原理主要涉及二维欧…

阿里云ubuntu宝塔面板部署uni-app-flask-websocket前后端项目

1.下载宝塔面板 wget -O install.sh https://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh ed8484bec 然后去安全组开放对应的端口 面板账户登录信息 【云服务器】请在安全组放行 29725 端口 进入控制面板后修改默认用户名和密码 2. …

linux、windows、macos,命令终端清屏

文章目录 LinuxWindowsmacOS 在Linux、Windows和macOS的命令终端中,清屏的命令或方法各不相同。以下是针对这三种系统的清屏方法: Linux clear命令:这是最常用的清空终端屏幕的命令之一。在终端中输入clear命令后,屏幕上的所有内容…

Web开发:ASP.NET CORE使用Ajax定时获取后端数据

一、低难度(刷新a标签) 1、需求 给a标签每15s刷新一次,显示最新的时间(时间必须由后端获取) 应该如何操作呢 2、代码 后端 using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Mi…

微信小程序获取蓝牙并实现内容打印

通过微信小程序如何实现获取蓝牙打印机并实现打印能力&#xff0c;之前做过一个测试Dome&#xff0c;能够获取附近的蓝牙打印机设备并实现打印,今天开放出来供大家参考。 wxml <!--右下角搜索--> <view class"ly-cass-box"><view class"ly-cas…

Docker核心技术:Docker原理之Namespace

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 Docker核心技术 系列文章&#xff1a;Docker原理之Namespace&#xff0c;其他文章快捷链接如下&#xff1a; 应用架构演进容器技术要解决哪些问题Docker的基本使用Docker是如何实现的 Docker核心技术&#xff1…

设计模式思想

设计模式思想 1. 理论2. 结构型模式——更优雅的声明和构建对象2.1 适配器模式——将一个类的接口适配成用户所期待的类2.2 代理模式——创建一个代理对象劫持对目标对象的调用&#xff0c;为目标访问增加一层控制和拦截&#xff0c;将控制逻辑和主逻辑隔离2.3 装饰器模式——在…

017、Vue动态tag标签

文章目录 1、先看效果2、代码 1、先看效果 2、代码 <template><div class "tags"><el-tag size"medium"closable v-for"item,index in tags":key"item.path":effect"item.title$route.name?dark:plain"cl…