IO模型详解
前言么是IO呢?什么是阻塞非阻塞IO?什么是同步异步IO?什么是IO多路复用?select/epoll跟IO模型有什么关系?有几种经典IO模型呢?BIO、NIO、AIO到底有什么区别的?
https://static.52pojie.cn/static/image/hrline/1.gif
什么是IO
IO,英文全称是Input/Output,翻译过来就是输入/输出
https://static.52pojie.cn/static/image/hrline/1.gif
计算机角度的IO
比较直观的意思就是 计算机的输入输出 , 计算机就是主体 。 冯.诺依曼结构 ,它将计算机分成分为5个部分:运算器、控制器、存储器、输入设备、输出设备。
输入设备是向计算机输入数据和信息的设备,键盘,鼠标都属于输入设备;输出设备是计算机硬件系统的终端设备,用于接收计算机数据的输出显示,一般显示器、打印机属于输出设备。
鼠标、显示器这只是直观表面的输入输出,回到计算机架构来说,涉及计算机核心与其他设备间数据迁移的过程,就是IO。如磁盘IO,就是从磁盘读取数据到内存,这算一次输入,对应的,将内存中的数据写入磁盘,就算输出。这就是IO的本质。
https://static.52pojie.cn/static/image/hrline/1.gif
操作系统的IO我们要将内存中的数据写入到磁盘的话,主体会是什么呢?主体可能是一个应用程序,比如一个Java进程(假设网络传来二进制流,一个Java进程可以把它写入到磁盘)。
[*]什么是用户空间?什么是内核空间?
[*]以32位操作系统为例,它为每一个进程都分配了4G(2的32次方)的内存空间。这4G可访问的内存空间分为二部分,一部分是用户空间,一部分是内核空间。内核空间是操作系统内核访问的区域,是受保护的内存空间,而用户空间是用户应用程序访问的内存区域。
我们应用程序是跑在用户空间的,它不存在实质的IO过程,真正的IO是在操作系统执行的。即应用程序的IO操作分为两种动作:IO调用和IO执行。IO调用是由进程(应用程序的运行态)发起,而IO执行是操作系统内核的工作。此时所说的IO是应用程序对操作系统IO功能的一次触发,即IO调用。
操作系统的一次IO过程应用程序发起的一次IO操作包含两个阶段:
[*]IO调用:应用程序进程向操作系统内核发起调用。
[*]IO执行:操作系统内核完成IO操作。
操作系统内核完成IO操作还包括两个过程:
[*]准备数据阶段:内核等待I/O设备准备好数据
[*]拷贝数据阶段:将数据从内核缓冲区拷贝到用户进程缓冲区
其实IO就是把进程的内部数据转移到外部设备,或者把外部设备的数据迁移到进程内部。外部设备一般指硬盘、socket通讯的网卡。一个完整的IO过程包括以下几个步骤:
[*]应用程序进程向操作系统发起IO调用请求
[*]操作系统准备数据,把IO外部设备的数据,加载到内核缓冲区
[*]操作系统拷贝数据,即将内核缓冲区的数据,拷贝到用户进程缓冲区
阻塞IO模型假设应用程序的进程发起IO调用,但是如果内核的数据还没准备好的话,那应用程序进程就一直在阻塞等待,一直等到内核数据准备好了,从内核拷贝到用户空间,才返回成功提示,此次IO操作,称之为阻塞IO。
非阻塞IO模型如果内核数据还没准备好,可以先返回错误信息给用户进程,让它不需要等待,而是通过轮询的方式再来请求。这就是非阻塞IO,流程图如下:非阻塞IO的流程如下:
[*]应用进程向操作系统内核,发起recvfrom读取数据。
[*]操作系统内核数据没有准备好,立即返回EWOULDBLOCK错误码。
[*]应用程序进程轮询调用,继续向操作系统内核发起recvfrom读取数据。
[*]操作系统内核数据准备好了,从内核缓冲区拷贝到用户空间。
[*]完成调用,返回成功提示。
非阻塞IO模型,简称NIO,Non-Blocking IO。它相对于阻塞IO,虽然大幅提升了性能,但是它依然存在性能问题,即频繁的轮询,导致频繁的系统调用,同样会消耗大量的CPU资源。
IO多路复用模型在这之前,我们先来说一下,什么是文件描述符fd(File Descriptor),它是计算机科学中的一个术语,形式上是一个非负整数。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。IO复用模型核心思路:系统给我们提供一类函数(如select、poll、epoll函数),它们可以同时监控多个fd的操作,任何一个返回内核数据就绪,应用进程再发起recvfrom系统调用。IO多路复用之select应用进程通过调用select函数,可以同时监控多个fd,在select函数监控的fd中,只要有任何一个数据状态准备就绪了,select函数就会返回可读状态,这时应用进程再发起recvfrom请求去读取数据。非阻塞IO模型(NIO)中,需要N(N>=1)次轮询系统调用,然而借助select的IO多路复用模型,只需要发起一次询问就够了,大大优化了性能。但是呢,select有几个缺点:
[*]监听的IO最大连接数有限,在Linux系统上一般为1024。
[*]select函数返回后,是通过遍历fdset,找到就绪的描述符fd。(仅知道有I/O事件发生,却不知是哪几个流,所以遍历所有流)
因为存在连接数限制,所以后来又提出了poll。与select相比,poll解决了连接数限制问题。但是呢,select和poll一样,还是需要通过遍历文件描述符来获取已经就绪的socket。如果同时连接的大量客户端,在一时刻可能只有极少处于就绪状态,伴随着监视的描述符数量的增长,效率也会线性下降。IO多路复用之epoll为了解决select/poll存在的问题,多路复用模型epoll诞生,它采用事件驱动来实现,流程图如下:epoll先通过epoll_ctl()来注册一个fd(文件描述符),一旦基于某个fd就绪时,内核会采用回调机制,迅速激活这个fd,当进程调用epoll_wait()时便得到通知。这里去掉了遍历文件描述符的坑爹操作,而是采用监听事件回调的机制。我们一起来总结一下select、poll、epoll的区别
selectpollepoll
底层数据结构数组链表红黑树和双链表
获取就绪的fd遍历遍历事件回调
事件复杂度O(n)O(n)O(1)
最大连接数1024无限制无限制
fd数据拷贝每次调用select,需要将fd数据从用户空间拷贝到内核空间每次调用poll,需要将fd数据从用户空间拷贝到内核空间使用内存映射(mmap),不需要从用户空间频繁拷贝fd数据到内核空间
epoll明显优化了IO的执行效率,但在进程调用epoll_wait()时,仍然可能被阻塞。
IO模型之信号驱动模型信号驱动IO不再用主动询问的方式去确认数据是否就绪,而是向内核发送一个信号(调用sigaction的时候建立一个SIGIO的信号),然后应用用户进程可以去做别的事,不用阻塞。当内核数据准备好后,再通过SIGIO信号通知应用进程,数据准备好后的可读状态。应用用户进程收到信号之后,立即调用recvfrom,去读取数据。
信号驱动IO模型,在应用进程发出信号后,是立即返回的,不会阻塞进程。它已经有异步操作的感觉了。但是你细看上面的流程图,发现数据复制到应用缓冲的时候,应用进程还是阻塞的。回过头来看下,不管是BIO,还是NIO,还是信号驱动,在数据从内核复制到应用缓冲的时候,都是阻塞的。IO 模型之异步IO(AIO)前面讲的BIO,NIO和信号驱动,在数据从内核复制到应用缓冲的时候,都是阻塞的,因此都不算是真正的异步。AIO实现了IO全流程的非阻塞,就是应用进程发出系统调用后,是立即返回的,但是立即返回的不是处理结果,而是表示提交成功类似的意思。等内核数据准备好,将数据拷贝到用户进程缓冲区,发送信号通知用户进程IO操作执行完毕。异步IO的优化思路很简单,只需要向内核发送一次请求,就可以完成数据状态询问和数据拷贝的所有操作,并且不用阻塞等待结果。阻塞、非阻塞、同步、异步IO划分
IO模型
阻塞I/O模型同步阻塞
非阻塞I/O模型同步非阻塞
I/O多路复用模型同步阻塞
信号驱动I/O模型同步非阻塞
异步IO(AIO)模型异步非阻塞
一个通俗例子读懂BIO、NIO、AIO
[*]同步阻塞(blocking-IO)简称BIO
[*]同步非阻塞(non-blocking-IO)简称NIO
[*]异步非阻塞(asynchronous-non-blocking-IO)简称AIO
一个经典生活的例子:
[*]小王去吃北京烤鸭,就这样在那里排队,等了一小时,然后才开始吃。(BIO)
[*]小张也去北京烤鸭,她一看要等挺久的,于是去逛会商场,每次逛一下,就跑回来看看,是不是轮到她了。于是最后她既购了物,又吃上鸭子了。(NIO)
[*]小赵一样,去吃北京烤鸭,由于他是高级会员,所以店长说,你去商场随便逛会吧,等下有位置,我立马打电话给你。于是小赵不用干巴巴坐着等,也不用每过一会儿就跑回来看有没有等到,最后也吃上了美味的鸭子。(AIO)
本帖最后由 BaiPiaoGuaiNO1 于 2021-12-11 09:40 编辑
{:301_993:}牛啊,讲的很好,学习了 感谢楼主分享,想抽时间好好的学一下epoll,没有想到看到一个demo 谢谢分享~ 谢谢分享 最后的例子很生动 good,一看就是花了心思的银啊 来学习了
页:
[1]