秋招突击——7/29——操作系统——网络IO
文章目录
- 引言
- 基础知识
- 零拷贝
- 传统文件读取
- 传统文件传输
- 零拷贝
- mmap + write
- sendifle
- 网络通信IO模型
- 阻塞IO
- 非阻塞IO
- IO多路复用模型
- select
- poll
- select和poll的总结
- epoll
- 边缘触发ET和水平触发LT
- 信号驱动IO模型
- 异步IO
- 面试题库
- 1、说一下Linux五种IO模型
- 2、阻塞IO和非阻塞IO应用场景问题,有一个计算密集型场景,和一个给用户传视频的场景,分别应该使用什么IO?
- 3、谈谈你对IO复用的理解
- 4、select、poll、epoll有什么区别?
- 5、epoll ET模式和LT模式有什么区别?哪个更加高效?
- 6、零拷贝技术了解过吗?说一下原理
- 总结
引言
- 头一次在面试的时候被问到,之前总觉得不会考,就没有看,导致灵魂三连问,全部懵逼!
- 还是和以前的思路一样,先整理一下基础知识,然后再整理一下面试题!
- 这章感觉还是挺难的,当初学操作系统的时候,就是直接跳过了,没想到在面试这里等着我!
- 下述信息是参考以下资料
- 100%弄明白多路复用
基础知识
零拷贝
传统文件读取
原始技术
- 需要CPU参与,并且不能干其他的事情
- 针对此,就有了下文,直接内存访问技术
DMA直接内存访问技术
- 使用DMA替代CPU的工作
- 在进行IO设备和内存的数据传输时,数据搬运工作交给DMA控制器,CPU不再参与任何与数据搬运相关的事情,这样CPU可以去处理别的事情了
传统文件传输
- 在传统的情况下,如果需要通过网络应用传输数据,需要经过如下的流程
- 特点
- 4次用户态和内核态的上下文交换
- 4次数据拷贝
- 第一次:将磁盘中的数据拷贝到操作系统的内核缓冲区中,通过DMA搬运
- 第二次:将内核缓冲区的数据拷贝到用户缓冲区中,应用程序可以使用这部分数据,由CPU完成
- 第三次:将刚才拷贝到用户缓冲区的数据,再次拷贝到socket缓冲区中,CPU完成
- 第四次:将内核缓冲区的数据拷贝到网卡缓冲区的数据中,DMA完成
专门针对网络传输,不需要将数据拷贝到用户缓冲区,因为不会修改数据,所以就有了零拷贝技术
零拷贝
- 两种实现方式
- mmap + write
- sendfile
mmap + write
-
使用mmap替代read
- 直接将内核缓冲区的数据映射到用户空间,而不是复制到用户空间
- 操作系统内核与用户空间不再进行任何数据拷贝操作
- 应用进程和操作系统内核共享缓冲区
- 直接将内核缓冲区的数据映射到用户空间,而不是复制到用户空间
-
调用write函数,将内核缓冲区的数据复制到Socket缓冲区中
- 在内核态中,将内核缓冲区的数据复制到Socket缓冲区,都是在内核态
- 在内核态中,将内核缓冲区的数据复制到Socket缓冲区,都是在内核态
通过上述过程,减少一次拷贝的过程
sendifle
- 不涉及到用户态的操作,直接将内核态的数据复制到内核态的Socket缓冲区中,专门用来网络传输的命令
网络通信IO模型
- 网络通信中,双方应用都是通过各自TCP发送缓冲区和TCP接受缓冲区,进行发送消息和接受消息。
- IO通信模型主要是解决某一端应用和TCP接受缓冲区和发送缓冲区的交互情况。
- 发送数据和接受数据是需要时间的,应用需要知道缓冲区的数据是否已经准备好了,以便于及时获取数据。
图片链接
- 阻塞IO和非阻塞IO也都是针对应用从缓冲区获取数据和接受数据而设计的一些模式,具体如下。
阻塞IO
阻塞的IO的基本流程
- 应用调用recvfrom读取数据时,询问内核是否准备好数据
- 内核准备数据报,应用程序陷入阻塞等待
- 内核准备好数据报,并将数据复制到应用空间
- 返回revfrom的调用结果,成功返回,线程从阻塞中恢复。
缺点
- 一个程序能够开辟的线程是有限的,而且开辟线程的开销是比较大的
- 会导致一个应用程序可以处理的客户端请求优先,针对百万级连接也是无法处理的
非阻塞IO
非阻塞和阻塞的最大区别,是调用了recvfrom会立刻返回结果,线程可以执行别的工作,不用一直阻塞等待
- 应用程序向内核发起recvfrom请求读取数据
- 数据没有准备好,返回EWOULDBLOCK错误码
- 线程去干别的,知道数据没有准备好,下次再问
- 数据准备好了,同上面的阻塞IO准备数据的过程
- 内核将数据报准备好
- 复制数据到应用空间
- 返回成功指示
- 数据没有准备好,返回EWOULDBLOCK错误码
非阻塞IO的优点
- 解决了每一个连接一个线程处理的问题,最大的有带你是一个线程可以处理多个连接
非阻塞IO的缺点
- 用户需要多次发起系统容调用,频繁调用会消耗系统资源
IO多路复用模型
图片链接
共用的TCP缓冲区的必要性
- 之前将的所有的样例都是一个线程对应一个TCP缓冲区,但是实际应用中,会有多个线程共用一个TCP缓冲区,一般来说这种情况在服务器中很常见,服务器会为每一个客户端链接创建一个线程,这些线程会共享一个TCP缓冲区尽进行数据的读取和处理。
- 但是对于客户端而言,每一个TCP连接都是有独立的缓冲区。
- IO多路复用的情况,常出现在如下应用中
- 高性能的网络服务器
- 数据采集服务器
- 实时流媒体服务器
- 具体如上图,就是多个线程共用一个TCP缓冲区,应用B可能是一个服务器程序!但是如果像上图一样,每次都创建对应的线程去询问对应的TCP缓冲区,会出现浪费了很多资源,并且线程数有限。
- 所以将这部分工作抽象出来,单独交特定的几个线程完成
- 监控并询问多个网络请求(fd文件描述付),数据准备完毕后,分配对应的应用线程来读取数据。
多路复用用的是什么?
-
通过有限次的系统调用来实现管理多个网络连接
- 复用的是系统调用==》通过有限次系统调用,判断海量连接的数据是否准备好!!!
-
具体见下图
图片链接
- 应用程序会让询问线程调用select函数
- 这个函数实现上述多路复用的功能,他会同时检测多个fd操作,其所监视的fd只要有一个数据准备完毕,就会返回可读状态,
- 然后该询问线程再去通知处理数据的线程
- 对应线程在发起recvform请求读取数据。
图片链接
总结
- 复用的基本思路就是通过设计不同的监听函数,来监控多个fd,不必为每一个fd都创建一个监控线程,减少线程资源创建的目的。
- 常见实现版本有select、poll、epoll
select
具体过程
-
将已连接的socket都放到一个文件描述符集合,然后调用select函数将文件描述符拷贝到内核里
-
让内核通过轮询遍历的方式来检查是否有网络事件的产生
- 检查到事件之后,将socket标记为可读可写,然后把整个文件描述符集合拷贝回用户态中
-
用户态再通过遍历的方法找到可读可写的Socket方法,对其进行处理
特点
- 需要两次遍历文件描述符集合
- 用户态和内核态
- 两次拷贝文件描述符集合
- 用户空间传入内核空间,然后内核空间修改之后,在传回用户空间
特点
- 跨平台性好
- 能够监视的文件描述符数量有限制,1024
poll
监视对象
- 使用动态数据,以链表的形式进行组织,没有数量的限制
特点
- 需要遍历文件描述符来返回已经就绪的socket
- 一般来说效率很低,连接数很多,但是有效的连接就只有几个
select和poll的总结
本质
- 将批量socket fds通过系统调用传递给内核,让内核循环遍历判断哪些fd上的数据准备就绪了
- 然后将就绪readyfds返回给用户,再由用户进行遍历读取好的fds,读取或者写入数据
缺点
- 海量数据开销大:
- 用户需要将海量的socket fds从用户态传到内核态,由内核态检测那些网络连接数据就绪了,开销大
select和poll的区别
- select能够处理的最大连接数有限,poll理论上能够支撑无限个
- select和poll在处理海量连接的时候,会频繁的从用户态拷贝到内核态,比较消耗资源
- 都是使用线性结构存储进程关注的Socket集合,都需要遍历文件描述符集合才能找到可读可写的Socket,时间复杂度是O(n)
epoll
epoll对于select和poll的改进
-
第一步
- 在内核里使用红黑树来跟踪进程所有待检测的文件描述符
- 将需要监控的socket加入到内核中的红黑树中,红黑树的增删改查效率是O(logn)
- 可以将Socket保存在内核中,不需要每次都复制
- 在内核里使用红黑树来跟踪进程所有待检测的文件描述符
-
第二步
- 使用事件驱动替代遍历轮询
- 内核里维护了一个链表来记录就绪事件
- 有事件发生时,通过回调函数,内核会将其加入到就绪事件列表中,用户调用epoll_wait函数,返回有事件发生的文件描述符链表
- 使用事件驱动替代遍历轮询
具体流程如下
边缘触发ET和水平触发LT
边缘触发ET
- 当被监控的Socket描述符有可读事件发生时,服务端只会从epoll_wait中苏醒一次
- 即使没有调用read函数,也依然只会苏醒一次,
- 保证一次性将内核缓冲区的数据读取完
水平触发LT
- 被监控的Socket上有刻度事件发生时,服务端不断从epoll_wait中苏醒,直到内核缓冲区数据被read函数读完才结束
- 只要满足条件,就会苏醒,告诉要取数据
区别
- 持续性传递和一次性传递
- 水平触发,是只要内核中有数据读,就会一直不断把这个事件传递给用户
- 会循环读取数据,因而搭配非阻塞IO一块使用
- 边缘触发,只有第一次满足条件才会触发,之后就不会传递同样的事件了
- 水平触发,是只要内核中有数据读,就会一直不断把这个事件传递给用户
信号驱动IO模型
- 之前的多路复用IO中的select是通过暴力轮询的方式实现fd监控,效率低下,于是提出基于信号的IO模型。
- 运作原理
- 对应网络IO的数据准备完毕后,主动通知对应的绑定的询问线程,准备好了,然后询问线程再去通知数据处理线程。
- 类似设计模式中的观察者模式
- 具体实现
- 监控链接建立:
- 询问线程调用sigaction函数和对应的需要检测的网络IO(fd描述符),建立SIGIO信号联系
- 通知询问线程
- 内核准备好数据后,通过SIGIO信号通知询问线程准备好后的可读状态
- 读取数据
- 对应线程调用recvform读取数据。
- 监控链接建立:
- 运作原理
注意
- 信号驱动IO模型下,询问线程建立监控链接后,即刻返回,不会陷入阻塞。
具体如下图
图片链接
- 具体实现流程如下图
图片链接
总结
- 不同于IO多路复用中select是需要线程去轮询每一个fd,不能干别的事,而且很多都是无效的轮询。
- 通过建立这种信号驱动IO模型,建立线程和fd之间的信号关联关系,避免大量无效的数据状态轮询操作
异步IO
提出原由
- 无论是多路复用IO还是信号驱动IO,读取数据都需要发送两段请求
- 多路复用,第一次select请求,第二次rcvform
- 信号驱动,第一次sigaction建立链接,第二次rcvform
- 如何将整个过程压缩为一个请求?不要进行两次访问,直接发送一个读数据请求,内核什么时候准备好数据,什么时候复制过来,然后告诉我复制完成即可。
- 上述过程可以通过异步IO实现
运行过程
- 读数据请求
- 应用向内核发送read请求,告诉内核读取数据后,立刻返回
- 内核返回数据
- 内核收到请求建立信号连接
- 当数据准备就绪,内核会主动把数据从内核复制到用户空间
- 所有操作完成后,内核发起通知告诉应用数据完成复制
具体如下图
图片链接
具体流程见下图
图片链接
总结
- 异步IO解决了应用程序需要连续两次发送请求才能获取数据的模式
- 在异步IO中只需要向内核发送一次请求,就可以完成状态询问和数据拷贝的操作
面试题库
1、说一下Linux五种IO模型
-
五种分别是
- 阻塞IO
- 应用程序执行IO操作时,数据还没有准备好,应用程序会阻塞,直到数据准备好,让出CPU使用权
- 非阻塞IO
- 应用程序执行IO操作时,数据还没准备好,会返回一个错误,而不是阻塞应用程序,应用程序可以执行其他的操作
- 信号驱动式IO
- 应用程序向操作系统注册一个信号处理函数,当数据准备好时,才做系统会发送一个信号,应用程序可以在接受到信号的时候读取数据,避免阻塞和轮询
- IO多路复用
- 事件驱动IO,应用程序可以同时监控多个IO描述符,当一个IO描述符准备好数据时,应用程序可以对其进行处理。select、poll、epoll都是模型的实现
- 异步IO
- 应用程序发起IO操作之后,可以立刻去做其他的事情,当数据准备好之后,操作系统会将数据复制到应用程序的缓冲区中,并通知应用程序。
- 阻塞IO
-
阻塞IO和非阻塞IO区别
- 进程发起系统调用后,是会被挂起直到收到数据后在返回,还是立即返回成功货错误
-
同步IO和异步IO的区别在于
- 将数据从内核复制到用户空间时,用户进程是否会阻塞
- 如果用户进程会阻塞,就是同步IO
- 如果用户进程不会阻塞,就是异步IO
2、阻塞IO和非阻塞IO应用场景问题,有一个计算密集型场景,和一个给用户传视频的场景,分别应该使用什么IO?
阻塞IO
- 读写过程中发生阻塞现象,当用户线程发出IO请求后,内核会去查看数据是否准备就绪,如果没有就绪就会等待数据就绪
- 线程处于阻塞状态,并交出CPU
- 数据就绪后,内核会将数据拷贝到用户线程,并返回结果给用户线程,重新等待获取CPU
非阻塞IO
- 读写过程中,发起read请求之后,并不会一直等待,会立刻返回。
- 不间断询问内核数据是否准备就绪,非阻塞IO不会交出CPU
计算密集型场景
- 该场景下需要消耗的是CPU资源,用阻塞IO比较好,非阻塞IO会一直占用CPU做无意义的轮询
用户传输视频
- 瓶颈不是CPU资源,是数据传输过程,所以使用非阻塞IO会更好,避免线程阻塞在数据传输上,提高程序的并发性和响应时间。
3、谈谈你对IO复用的理解
不使用IO复用,服务端支持多客户端IO存在什么问题
- 如果没有IO复用,每一连接都需要使用使用一个子进程或者线程来处理,但是进程创建和线程创建开销很大,不能维护太多IO。
- 系统每一次进行IO都需要进行一次IO请求,直到获取数据为为止,如何降低系统调用地次数十分关键
多路复用IO是怎么解决上述问题的
- IO多路复用可以实习那多个IO复用一个进程,只需要一个进程或者线程就能并发处理多个客户端地IO事件
- 进程可以通过select、poll、epoll这类IO多路复用系统调用接口从内核获取发生事件的Socket集合。
- 应用程序可以遍历这个集合,对每一个Socket进行处理。
redis单线程实现高性能,就是IO多路复用地典型实现
4、select、poll、epoll有什么区别?
select和poll
-
select和poll都是使用线性结构来存储进程关注的Socket集合,在使用的时候
- 首先把需要关注的Socket集合通过select/poll系统调用从用户态拷贝到内核态
- 然后,在由内核态进行遍历检测事件,并设置对应状态为可读/可写
- 接着,将从内核态复制到用户态,由用户态遍历找到其中可读可写的Socket,进行处理
-
缺点
- 客户端越多,Socket集合越大
- Socket集合的遍历和拷贝都要很大的开销
epoll
-
内核中使用红黑树关注Socket,避免大量复制和拷贝
- 在内核中使用红黑树来关注进程所偶待检测Socket
- 红黑树增删改查的时间复杂度是O(logn)
- 通过对于红黑树的管理,不用每次都传入整个集合,减少内核和用户空间大量的数据拷贝和内存分配
- 在内核中使用红黑树来关注进程所偶待检测Socket
-
事件驱动代理轮询扫描
- 使用事件驱动机制,内核维护链表记录就绪事件
- 只有将事件发生的socket传递给应用程序,不需要poll和select一样轮询整个Socket集合,提高了效率
5、epoll ET模式和LT模式有什么区别?哪个更加高效?
-
ET边缘触发
-
当描述符从未就绪变为就绪时,只会通知一次,之后就不会通知,保证程序能够一次性将事件处理完
-
效率更高,减少epoll_wait系统调用次数
-
-
LT水平触发
- 当文件描述符就绪时,就会触发通知,直到用户把所有数组读完为止,都会一直发通知
6、零拷贝技术了解过吗?说一下原理
传统文件传输
- 设计两次系统调用,read和write函数,4次用户态和内核态的切换以及数据拷贝。
零拷贝
- 只需要一次sendfile或者两次系统调用mmap和write,减少了两次用户态和内核态的切换次数,再加上DMA技术,数据拷贝只需要两次。
通过零拷贝能够提高文件传输的速率,Kafka消息队列IO的吞吐量高的原因,就是使用了零拷贝技术
总结
- 好家伙,终于结束了,下一个知识点,在不断补充完整!
- 这个终于结束了,耗时两天,明天抽时间好好背一下,后续不需要在花时间了!
- 能应付面试就行了!