当前位置: 首页 > 科技观察

I-O多路复用底层原理Part1——五种IO模型

时间:2023-03-11 20:54:54 科技观察

前言BIO、NIO、AIO总结上一篇文章中BIO、NIO、AIO的基本概念和一些常见问题,介绍一下NIO是同步非阻塞的.服务器实现方式是一个线程可以处理多个请求(连接),客户端发送的连接请求会注册到多路复用器选择器上,多路复用器在轮询连接时处理IO请求。那么I/O多路复用器究竟是如何实现的呢?在本文中,让我们一探究竟。为了加深对I/O多路复用机制的理解,明白多路复用也有局限性,本着打破砂锅问到底的精神,这里先回顾一下Unix网络编程中的五种IO模型。下一篇文章将继续深入研究IO多路复用。BlockingIO-阻塞IONoneBlockingIO-非阻塞IOIOmultiplexing-IO多路复用signaldrivenIO-signaldrivenIOasynchronousIO-异步IOUnix网络编程中的五种IO模型BlockingIO-BlockingIO最传统的一种IO模型,即,在读写数据的过程中发生阻塞。当用户线程发送IO请求时,内核会检查数据是否就绪。如果没有就绪,会等待数据准备好,此时用户线程会处于阻塞状态,用户线程会交出CPU。当数据准备好后,内核会将数据复制到用户线程,并将结果返回给用户线程,用户线程释放block状态。可能有人会说多线程+阻塞IO可以解决效率问题,但是因为在多线程+阻塞IO中,每个socket对应一个线程,这样会造成很大的资源占用,尤其是对于长连接而言,线程的资源永远不会被释放。如果连续连接很多,就会造成性能瓶颈。Non-blockingIO-NoneBlockingIO当一个用户线程发起一个IO操作时,它不需要等待,而是立即得到一个结果。如果结果是错误的,它知道数据还没有准备好,所以它可以重新发送IO操作。一旦内核中的数据准备好,再次收到用户线程的请求,它立即将数据复制到用户线程并返回。在非阻塞IO模型中,用户线程需要不断询问内核数据是否就绪,也就是说非阻塞IO不会交出CPU,而是一直占用CPU。对于非阻塞IO,有一个很严重的问题。在while循环中,需要不断的询问内核数据是否就绪,这会导致CPU占用率非常高,所以一般情况下很少使用while循环来读取数据。非阻塞型主要体现在用户进程发起recvfrom系统调用时。此时系统内核还没有收到数据报,直接向用户进程返回错误,告诉“数据报还不可达,稍后再回来”。用户进程收到信息,但用户进程不知道数据报什么时候有,于是开始轮询(polling),向系统内核发起recvfrom系统调用“询问数据是否来了”,如果没有,则将继续返回一个错误用户进程轮询启动recvfrom系统调用,直到数据报可达。这时候就需要等待系统内核将数据报复制到用户进程的缓冲区中。复制完成后会返回IOmultiplexing-IOmultiplexing成功提示。所谓I/O多路复用机制就是说通过一种机制,可以监听到多个描述符,一旦一个描述符就绪(通常是ready或者writing就绪),就可以通知程序执行相应的读写操作。这种机制的使用需要select、poll、epoll的配合。在多路复用IO模型中,会有一个内核线程不断轮询多个socket的状态。只有发送实际的读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,可以只用一个线程来管理多个socket,系统不需要创建新的进程或线程,也不需要维护这些线程和进程,只有当真正有读写的时候事件进行中,会使用IO资源,所以大大减少了资源占用。IO多路复用方式是通过select或poll函数向系统内核发起调用,阻塞在这两个系统函数调用中,而不是真正阻塞在实际IO操作中(recvfromcall是真正阻塞IO操作的系统调用)和阻塞在select函数的调用等待数据报套接字变得可读。当selectsocket返回可读状态时,它可以发起recvfrom调用,将数据报复制到用户空间的缓冲区中。SignaldrivenIO——signaldrivenIOin在signal-drivenIO模型中,当用户线程发起IO请求操作时,会为对应的socket注册一个signal函数,然后用户线程继续执行。当内核数据准备好后,会向用户线程发送一个信号,用户线程会收到这个信号。之后在signal函数中调用IO读写操作,进行实际的IO请求操作。这个一般用在UDP中,对TCP套接字几乎没用,因为信号产生的太频繁了,信号的出现并不能告诉我们发生了什么请求。用户进程可以使用signal方法。当系统内核描述符就绪时,它会向用户空间发送SIGNO。此时会发起recvfrom系统调用,等待返回成功提示。流程如下:首先开启socket的signalIO启动功能,并通过一个内置安装信号处理功能的signaction系统调用,调用后直接返回;其次,等待内核从网络接收到数据报后,向信号处理函数发送一个当前数据对用户空间可用的信号;信号处理函数接收到信息后,发起recvfrom系统调用,等待内核数据将数据报复制到用户空间的缓冲区;应用进程收到复制完成成功返回提示后,就可以开始从网络上读取数据了。AsynchronousIO——异步IO异步IO的前四种IO模型其实都是同步IO,只有最后一种才是真正的异步IO,因为无论是多路复用IO还是信号驱动模型,第二阶段的IO操作都会导致用户线程阻塞,即内核拷贝数据的过程会阻塞用户线程。由POSIX规范定义,告诉系统内核开始一个操作,让内核在整个操作包括数据等待和数据拷贝过程完成后,通知用户进程数据已经准备好,可以读取数据;区别于上面提到的signalIO模型asynchrony是在IO操作完成的时候通知我们,signalIO是在可以开始IO操作的时候通知我们。总结现代计算机服务器操作系统大多基于linxu实现,采用NIO模型来处理高并发。对于具有异步IO模型的支持系统具有不确定性。有关详细信息,请参见BIO、NIO、AIO。同步和异步synchronous定义总结:发起一个fn调用,需要等待调用结果返回,调用结果要么是预期的结果,要么是抛出异常的结果,可以说是一个原子操作(返回成功或失败)异步:发起一个fn调用,不等待结果直接返回,只有被调用者执行完handler后,通过“唤醒”的方式通知调用者获取结果(唤醒方式包括回调、事件通知等)总结:同步和异步关注的是程序间通信的阻塞和非阻塞的定义。当前线程将无法获取到锁而被挂起,处于等待状态非阻塞更关注程序等待结果的状态从这里可以看出,同步异步和阻塞非阻塞没有关系,重点在于不同的同步IO和异步IO(基于POSIX规范)一直处于等待状态。此时进程阻塞,直到IO操作完成,返回成功。异步IO:是指应用进程发起真正的IO操作请求(recvfrom),进程会直接返回错误信息,“相当于告诉进程还没有处理完,好吧,我通知你”阻塞IO:主要反映发起IO操作请求通知内核,如果内核收到信号后让进程等待,则为阻塞非阻塞IO:发起IO操作请求时,直接告诉进程不管结果“Don'twait,comebacklater”,也就是非阻塞IO模型对比根据上面同步和异步IO的定义结合上面的模型,只有异步IO模型符合异步IO的POSIX规范,其他IO模型都有recvfrom系统调用被内核调用阻塞,属于同步IO操作可见阻塞IO和非阻塞IO可以summarized如下:也就是说要么叫同步IO和异步IO,要么叫上面5种模型的IO语句,注意上面同步和异步的概念大部分操作系统都是基于同步IO实现的.不确定支持异步IO模型的操作系统。在实际工作中,我们常说Blocking-IO(阻塞IO)和Non-Blocking-IO(非阻塞IO),很少叫同步IO用异步IO总结:同步和异步针对通信机制,阻塞和非阻塞针对程序调用等待结果的状态一句话总结:阻塞IO和非阻塞IO这是最简单的模型,通常用multi-threading实现多路复用(select/poll/epoll)一个线程解决了多连接的问题。信号驱动IO模型是一种同步IO模型,更加灵活。异步IO模型是一种高效的主流模型,效率高。