内核角度看IO模型

news2024/11/25 22:54:51

聊聊Netty那些事儿之从内核角度看IO模型

网络包接收流程

 

  • 网络数据帧通过网络传输到达网卡时,网卡会将网络数据帧通过DMA的方式放到环形缓冲区RingBuffer中。RingBuffer是网卡在启动的时候分配和初始化环形缓冲队列。当RingBuffer满的时候,新来的数据包就会被丢弃

  • DMA操作完成时,网卡会向CPU发起一个硬中断,告诉CPU有网络数据到达。CPU调用网卡驱动注册的硬中断响应程序。网卡硬中断响应程序会为网络数据帧创建内核数据结构sk_buffer,并将网络数据帧拷贝sk_buffer中。然后发起软中断请求,通知内核有新的网络数据帧到达。

  • sk_buff缓冲区,是一个维护网络帧结构的双向链表,链表中的每一个元素都是一个网络帧

  • 内核线程ksoftirqd发现有软中断请求到来,随后调用网卡驱动注册的poll函数poll函数sk_buffer中的网络数据包送到内核协议栈中注册的ip_rcv函数中。每个CPU会绑定一个ksoftirqd内核线程专门用来处理软中断响应。2个 CPU 时,就会有两个ksoftirpd两个内核线程。

  • ip_rcv函数中也就是上图中的网络层取出数据包的IP头,判断该数据包下一跳的走向,如果数据包是发送给本机的,则取出传输层的协议类型(TCP或者UDP),并去掉数据包的IP头,将数据包交给上图中得传输层处理。

  • 当我们采用的是TCP协议时,数据包到达传输层时,会在内核协议栈中的tcp_rcv函数处理,在tcp_rcv函数中去掉TCP头,根据四元组(源IP,源端口,目的IP,目的端口)查找对应的Socket,如果找到对应的Socket则将网络数据包中的传输数据拷贝到Socket中的接收缓冲区中。如果没有找到,则发送一个目标不可达icmp包。

  • 内核在接收网络数据包时所做的工作我们就介绍完了,现在我们把视角放到应用层,当我们程序通过系统调用read读取Socket接收缓冲区中的数据时,如果接收缓冲区中没有数据,那么应用程序就会在系统调用上阻塞,直到Socket接收缓冲区有数据,然后CPU内核空间(Socket接收缓冲区)的数据拷贝用户空间,最后系统调用read返回,应用程序读取数据。

性能开销

  • 应用程序通过系统调用用户态转为内核态的开销以及系统调用返回时从内核态转为用户态的开销。

  • 网络数据从内核空间通过CPU拷贝用户空间的开销。

  • 内核线程ksoftirqd响应软中断的开销。

  • CPU响应硬中断的开销。

  • DMA拷贝网络数据包到内存中的开销

网络包发送流程

 性能开销:

  • 和接收数据一样,应用程序在调用系统调用send的时候会从用户态转为内核态以及发送完数据后,系统调用返回时从内核态转为用户态的开销。

  • 用户线程内核态CPU quota用尽时触发NET_TX_SOFTIRQ类型软中断,内核响应软中断的开销。

  • 网卡发送完数据,向CPU发送硬中断,CPU响应硬中断的开销。以及在硬中断中发送NET_RX_SOFTIRQ软中断执行具体的内存清理动作。内核响应软中断的开销。

  • 内存拷贝的开销。我们来回顾下在数据包发送的过程中都发生了哪些内存拷贝:

    • 在内核协议栈的传输层中,TCP协议对应的发送函数tcp_sendmsg会申请sk_buffer,将用户要发送的数据拷贝sk_buffer中。

    • 在发送流程从传输层到网络层的时候,会拷贝一个sk_buffer副本出来,将这个sk_buffer副本向下传递。原始sk_buffer保留在Socket发送队列中,等待网络对端ACK,对端ACK后删除Socket发送队列中的sk_buffer。对端没有发送ACK,则重新从Socket发送队列中发送,实现TCP协议的可靠传输。

    • 在网络层,如果发现要发送的数据大于MTU,则会进行分片操作,申请额外的sk_buffer,并将原来的sk_buffer拷贝到多个小的sk_buffer中。

阻塞与非阻塞模型

经过前边对网络数据包接收流程的介绍,在这里我们可以将整个流程总结为两个阶段:

  • 数据准备阶段: 在这个阶段,网络数据包到达网卡,通过DMA的方式将数据包拷贝到内存中,然后经过硬中断,软中断,接着通过内核线程ksoftirqd经过内核协议栈的处理,最终将数据发送到内核Socket的接收缓冲区中。

  • 数据拷贝阶段: 当数据到达内核Socket的接收缓冲区中时,此时数据存在于内核空间中,需要将数据拷贝用户空间中,才能够被应用程序读取。

阻塞与非阻塞的区别主要发生在第一阶段:数据准备阶段。

 

同步与异步 

 

同步异步主要的区别发生在第二阶段:数据拷贝阶段

前边我们提到在数据拷贝阶段主要是将数据从内核空间拷贝到用户空间。然后应用程序才可以读取数据。当内核Socket的接收缓冲区有数据到达时,进入第二阶段。

同步模式在数据准备好后,是由用户线程内核态来执行第二阶段。所以应用程序会在第二阶段发生阻塞,直到数据从内核空间拷贝到用户空间,系统调用才会返回。Linux下的 epoll和Mac 下的 kqueue都属于同步 IO

异步模式下是由内核来执行第二阶段的数据拷贝操作,当内核执行完第二阶段,会通知用户线程IO操作已经完成,并将数据回调给用户线程。所以在异步模式下 数据准备阶段数据拷贝阶段均是由内核来完成,不会对应用程序造成任何阻塞。

基于以上特征,我们可以看到异步模式需要内核的支持,比较依赖操作系统底层的支持。

 

 IO多路复用

  • 多路:我们的核心需求是要用尽可能少的线程来处理尽可能多的连接,这里的多路指的就是我们需要处理的众多连接。

  • 复用:核心需求要求我们使用尽可能少的线程尽可能少的系统开销去处理尽可能多的连接(多路),那么这里的复用指的就是用有限的资源,比如用一个线程或者固定数量的线程去处理众多连接上的读写事件。换句话说,在阻塞IO模型中一个连接就需要分配一个独立的线程去专门处理这个连接上的读写,到了IO多路复用模型中,多个连接可以复用这一个独立的线程去处理这多个连接上的读写。

IO多路复用(阻塞IO,非阻塞IO,select,poll,epoll)_量子学习法的博客-CSDN博客

深入理解epoll

其中进程内打开的所有文件是通过一个数组fd_array来进行组织管理,数组的下标即为我们常提到的文件描述符,数组中存放的是对应的文件数据结构struct file。每打开一个文件,内核都会创建一个struct file与之对应,并在fd_array中找到一个空闲位置分配给它,数组中对应的下标,就是我们在用户空间用到的文件描述符.

对于任何一个进程,默认情况下,文件描述符 0表示 stdin 标准输入,文件描述符 1表示stdout 标准输出,文件描述符2表示stderr 标准错误输出

 

 

  1. 当我们调用accept后,内核会基于监听Socket创建出来一个新的Socket专门用于与客户端之间的网络通信。并将监听Socket中的Socket操作函数集合inet_stream_opsops赋值到新的Socketops属性中。

  2. 接着内核会为已连接的Socket创建struct file并初始化,并把Socket文件操作函数集合(socket_file_ops)赋值给struct file中的f_ops指针。然后将struct socket中的file指针指向这个新分配申请的struct file结构体。

  3. 然后调用socket->ops->accept,从Socket内核结构图中我们可以看到其实调用的是inet_accept,该函数会在icsk_accept_queue中查找是否有已经建立好的连接,如果有的话,直接从icsk_accept_queue中获取已经创建好的struct sock。并将这个struct sock对象赋值给struct socket中的sock指针。

  4. struct sockstruct socket中是一个非常核心的内核对象,正是在这里定义了我们在介绍网络包的接收发送流程中提到的接收队列发送队列等待队列数据就绪回调函数指针内核协议栈操作函数集合

之前提到的对Socket发起的系统IO调用,在内核中首先会调用Socket的文件结构struct file中的file_operations文件操作集合,然后调用struct socket中的ops指向的inet_stream_opssocket操作函数,最终调用到struct socksk_prot指针指向的tcp_prot内核协议栈操作函数接口集合。

本小节我们就来看下用户进程是如何阻塞Socket上,又是如何在Socket上被唤醒的。理解这个过程很重要,对我们理解epoll的事件通知过程很有帮助

 

  • 首先我们在用户进程中对Socket进行read系统调用时,用户进程会从用户态转为内核态

  • 在进程的struct task_struct结构找到fd_array,并根据Socket的文件描述符fd找到对应的struct file,调用struct file中的文件操作函数结合file_operationsread系统调用对应的是sock_read_iter

  • sock_read_iter函数中找到struct file指向的struct socket,并调用socket->ops->recvmsg,这里我们知道调用的是inet_stream_ops集合中定义的inet_recvmsg

  • inet_recvmsg中会找到struct sock,并调用sock->skprot->recvmsg,这里调用的是tcp_prot集合中定义的tcp_recvmsg函数。

  •  

 

epoll_create创建epoll对象

  • epoll中的等待队列,队列里存放的是阻塞epoll上的用户进程。在IO就绪的时候epoll可以通过这个队列找到这些阻塞的进程并唤醒它们,从而执行IO调用读写Socket上的数据。

  •  epoll中的就绪队列,队列里存放的是都是IO就绪Socket,被唤醒的用户进程可以直接读取这个队列获取IO活跃Socket。无需再次遍历整个Socket集合。

  • struct rb_root rbr : 由于红黑树在查找插入删除等综合性能方面是最优的,所以epoll内部使用一颗红黑树来管理海量的Socket连接。

首先要在epoll内核中创建一个表示Socket连接的数据结构struct epitem

socket等待队列中类型是wait_queue_t无法关联到epitem。所以就出现了struct eppoll_entry结构体,它的作用就是关联Socket等待队列中的等待项wait_queue_tepitem

 

 

  • 当网络数据包在软中断中经过内核协议栈的处理到达socket的接收缓冲区时,紧接着会调用socket的数据就绪回调指针sk_data_ready,回调函数为sock_def_readable。在socket的等待队列中找出等待项,其中等待项中注册的回调函数为ep_poll_callback

  • 在回调函数ep_poll_callback中,根据struct eppoll_entry中的struct wait_queue_t wait通过container_of宏找到eppoll_entry对象并通过它的base指针找到封装socket的数据结构struct epitem,并将它加入到epoll中的就绪队列rdllist中。

  • 随后查看epoll中的等待队列中是否有等待项,也就是说查看是否有进程阻塞在epoll_wait上等待IO就绪socket。如果没有等待项,则软中断处理完成。

  • 如果有等待项,则回到注册在等待项中的回调函数default_wake_function,在回调函数中唤醒阻塞进程,并将就绪队列rdllist中的epitemIO就绪socket信息封装到struct epoll_event中返回。

  • 用户进程拿到epoll_event获取IO就绪的socket,发起系统IO调用读取数据

 

水平触发和边缘触发

  • 水平触发:在这种模式下,用户线程调用epoll_wait获取到IO就绪的socket后,对Socket进行系统IO调用读取数据,假设socket中的数据只读了一部分没有全部读完,这时再次调用epoll_waitepoll_wait会检查这些Socket中的接收缓冲区是否还有数据可读,如果还有数据可读,就将socket重新放回rdllist。所以当socket上的IO没有被处理完时,再次调用epoll_wait依然可以获得这些socket,用户进程可以接着处理socket上的IO事件。

  • 边缘触发: 在这种模式下,epoll_wait就会直接清空rdllist,不管socket上是否还有数据可读。所以在边缘触发模式下,当你没有来得及处理socket接收缓冲区的剩下可读数据时,再次调用epoll_wait,因为这时rdlist已经被清空了,socket不会再次从epoll_wait中返回,所以用户进程就不会再次获得这个socket了,也就无法在对它进行IO处理了。除非,这个socket上有新的IO数据到达,根据epoll的工作过程,该socket会被再次放入rdllist中。

参考文献

聊聊Netty那些事儿之从内核角度看IO模型

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【AUTOSAR】BMS开发实际项目讲解(十三)----电池管理系统碰撞安全功能和SFR

SG-BMS-7 : BMS系统应避免碰撞保护功能异常引起的安全事故(ASIL A) 功能框图(SG-BMS-7) 功能组件说明 功能组件ID 功能组件名称 描述 ASIL等级 FSC-FC-05 Relay Drive 驱动继电器开启和关断 ASIL A FSC-FC-11 Detection …

【vue】可选链运算符(?.)和空值合并运算符(??):

文章目录 一、问题一:二、问题二:三、使用:【1】空值合并运算符(??)【2】可选链运算符(?.) 一、问题一: http://www.codebaoku.com/question/question-sd-1010000042870944.html //1、npm安装 npm install babel/plugin-propo…

批量修改文件命名的shell脚本

Android 制作开机动画的方法参考:linux开机动画制作教程 其中往往会把里面的png图片命名位XX_0001.png , 002.png……等 Window批量修改文件名时会带有空格和括号。 这里写了一个脚本,可以在批量修改文件名后,将文件名转换为XX_00001 格式&…

基于matlab使用 YOLO V2深度学习进行多类对象检测(附源码)

一、前言 此示例演示如何训练多类对象检测器。 深度学习是一种强大的机器学习技术,可用于训练强大的多类对象检测器,例如 YOLO v2、YOLO v4、SSD 和 Faster R-CNN。此示例使用该函数训练 YOLO v2 多类室内对象检测器。经过训练的物体检测器能够检测和识…

ModaHub魔搭社区:Milvus 监控指标和使用 Grafana 展示 Milvus 监控指标

目录 Milvus 监控指标 Milvus 性能指标 系统运行指标 硬件存储指标 Milvus 监控指标 Milvus 会生成关于系统运行状态的详细时序 metrics。你可以通过 Prometheus、Grafana 或任何可视化工具展现以下指标: Milvus 性能指标系统运行指标:CPU/GPU 使用…

单片机学习 11-中断系统(定时器中断+外部中断)

中断系统 中断介绍 ​ 中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的,中断功能的存在,很大程度上提高了单片机处理外部或内部事件的能力。它也是单片机最重要的功能之一,是我们学习单片机必须要掌握的。很多初学者被困在…

全网超全,Pytest自动化测试框架pytest-xdist分布式测试插件(实战)

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 平常我们功能测试…

Markdown 扩展语法

✅作者简介:人工智能专业本科在读,喜欢计算机与编程,写博客记录自己的学习历程。 🍎个人主页:小嗷犬的个人主页 🍊个人网站:小嗷犬的技术小站 🥭个人信条:为天地立心&…

Python在命令行模式下如何退出命令行

文章目录 python退出命令行模式结语 刚学习python的时候是用的命令行的方式,刚接触不知道如何退出命令行,百度参考了好几篇文章,这里记录一下,希望能帮助到有需要的小伙伴们。 python退出命令行模式 总结一下,共有三种…

Redis处理⾼并发 实现分布式锁

Redisson Redisson是架设在Redis基础上的⼀个Java驻内存数据⽹格(In-Memory Data Grid)。 Redisson在基于NIO的Netty框架上,充分的利⽤了Redis键值数据库提供的⼀系列优势,在Java实⽤⼯具包中常⽤ 接⼝的基础上,为使⽤…

高压功率放大器在光学测量中的应用有哪些

高压功率放大器在光学测量中有许多应用,例如在激光器和LED驱动、光电探测器和光电转换器中等。这些应用大多需要将输入信号放大到高电平输出,以便驱动高电压或大功率负载。 在激光器和LED驱动应用中,高压功率放大器可以将低电平的控制信号放大…

nginx纳入skywalking调用链监控

nginx纳入skywalking调用链监控 一、说明二、nginx部署2.1 OpenResty介绍2.2 准备SkyWalking Nginx Agent2.3 docker方式部署OpenResty2.3.1 修改配置文件2.3.2 启动OpenResty容器 2.4 验证 一、说明 服务器中已部署好skywalking,并将tomcat纳入skywalking监控(tom…

JavaSE基础语法--接口

接口在现实生活中比比皆是。比如电脑的USB接口,插座的接口。这些接口我们发现都是一样的规范。比如插座的有双孔插,有三孔插。那么对应就有双脚设备,和三脚的设备。从这我们就能摸清楚规律:接口就是统一规范的提供服务。Java中接口…

七年老Android推荐 : 日常开发中好用的工具 (二)

1. 前言 作为一名拥有七年经验的Android开发工程师,在日常开发中,总希望能提升自己的开发效率,对此也积累了一些工具,本文对此总结了一些好用的工具。 2. draw.io draw.io用来编写流程图非常好用,是一个免费的在线图…

Splunk Enterprise 9.1.0 (macOS, Linux, Windows) - 机器数据管理和分析

Splunk Enterprise 9.1.0 (macOS, Linux, Windows) - 机器数据管理和分析 请访问原文链接:https://sysin.org/blog/splunk-9/,查看最新版。原创作品,转载请保留出处。 作者主页:sysin.org 混合世界的数据平台 快速、大规模地从…

【海思SS528】MPP媒体处理软件V5.0 | 音频模块 - 学习笔记

目录 🎄一、概述🎄二、音频输入(AI) 和 音频输出(AO)✨2.1 音频接口和 AI、 AO 设备✨2.2 录音和播放原理✨2.3 AI、AO 通道✨2.4 重采样 🎄三、音频编码和解码✨3.1 音频编解码流程✨3.2 音频编解码协议✨3.3 语音帧结构 🎄四、总…

【Spring | 事件监听概述】

本篇主要对Spring 的 事件监听机制简单介绍下。 事件监听 概述 概述 ApplicationContext中的事件处理是通过ApplicationEvent 类和ApplicationListener接口提供的。如果将实现该 ApplicationListener 接口的 bean 部署到上下文中,那么每当 ApplicationEvent 发布到 …

Android Jetpack Compose - 深入了解 AlertDialog

在开发 Android 应用时,对话框是一个常见的 UI 元素,用于向用户显示信息或获取用户的反馈。在 Jetpack Compose 中,我们可以使用 AlertDialog 组件来创建对话框。在这篇博客中,我们将深入了解如何使用 Jetpack Compose 的 AlertDi…

【Python爬虫+数据分析】采集电商平台数据信息,并做可视化演示(带视频案例)

前言 随着电商平台的兴起,越来越多的人开始在网上购物。而对于电商平台来说,商品信息、价格、评论等数据是非常重要的。因此,抓取电商平台的商品信息、价格、评论等数据成为了一项非常有价值的工作。 接下来就让我来教你 如何使用Python编写…

互动酷投票平台网络投票链接做网络链接投票

关于微信投票,我们现在用的最多的就是小程序投票,今天的网络投票,在这里会教大家如何用“活动星投票”小程序来进行投票。 我们现在要以“垃悦享端午”为主题进行一次投票活动,我们可以在在微信小程序搜索,“活动星投票…