五种IO模型
- 同步阻塞IO
- 同步非阻塞IO
- IO多路复用(select、poll、epoll)
- 信号驱动IO(SIGIO)
- 异步IO(POSIX 的 aio_系列函数)
首先先说明一点概念,IO执行的两个阶段
在进行IO操作时,通常会经历两个阶段:
第一个阶段:系统内核缓冲区先等待数据准备好。
第二个阶段:当系统内核数据准备好之后,将内核空间的数据拷贝到用户空间(制定进程缓冲区)。
针对于网络套接字方面的IO操作,第一步需要先将网络中的数据从网络中送达至网卡,然后再将其复制到内核缓冲区,第二步就是把内核中的数据拷贝至指定的用户进程缓冲区。
同步 & 异步
同步描写的是被调用者选择的方式
同步机制:A调用B,B的处理是同步的,所以B在处理完成之前不会通知A,只有处理在处理完成之后才会通知A。
异步机制:A调用B,B的处理是异步的,所以B在接收到A的请求之后,会首先告知A“我已经接收到你的请求了”,然后当B处理完成之后通过回调等方式通知A。
阻塞 & 非阻塞
阻塞非阻塞描述的是调用方的状态
阻塞请求:A调用B,在A调用之后,A一直等着B返回,不做其他事情。
非阻塞请求:A调用B,在A调用之后,A先去忙A所要做的其他事情,不需要等B的返回。
同步阻塞IO
在linux下,默认的情况下所有的网络socketIO都是阻塞式IO,一般情况下是这样的
在进行recvfrom读取系统内核缓冲区数据的时候,进入系统调用,然后内核缓冲区并没有数据,所以,进程什么事情都不做,阻塞式等待系统完成的两个阶段。
在IO执行的两个阶段,进程都处于阻塞状态,在等待数据返回的过程中,不做任何的事情,阻塞的等在那里
同步非阻塞IO
在非阻塞IO中,进行recvfrom的调用时, 进程并没有被阻塞,在数据没有准备好时,内核告知用户区进程数据未就绪,然后返回一个errno(EAGAIN or EWOULDBLOCK),用户区进程在返回之后,可以处理其他业务逻辑,过一会儿再调用recvfrom函数,进行系统调用,采用轮询的方式检查内核数据是否就绪直到内核数据就绪,然后内核将其拷贝到进程缓冲区。
在非阻塞状态下,IO执行的第一个阶段用户区进程可以进行其业务逻辑,但是在IO执行的第二个阶段,用户区还是需要进行阻塞等待内核拷贝数据。
同步非阻塞与同步阻塞优缺点
优点:非阻塞能够在等待的时间内完成其他任务。
缺点:任务处理的时间增大了,因为要没过一段时间轮询的访问read操作。
IO多路复用
IO多路复用的意思是可以单进程同时处理多个网络IO操作基本原理不是有应用程序自己监视数据是否就绪,而实有内核代替其进行监视多个socket文件描述符。
以select为例,select可以一次创建多个文件描述符,并将其交予内核监控当用户调用select时,进程阻塞,同时,内核会监管所有select负责的socket文件描述符,当有任何一个文件描述符就绪时,就会返回给用户,用户在调用read操作,将该文件描述符下的内核中的数据读入进程缓冲区。
优点
与传统的多线程/多进程服务器相比,IO多路复用的最大优势就是系统开销小,系统不需要创建新的额外的线程/进程,即可对多个网络连接进行控制操作。
同样会阻塞在IO的第二阶段。
信号驱动IO
应用进程首先先创建一个信号处理函数,进程进行系统调用read时,可以不阻塞继续运行,当数据准备好时,内核发送要给SIGIO的信号给应用程序,可以在信号处理函数中进行IO相关操作。
阻塞在IO的第二个阶段。
异步IO
因为异步IO不是顺序执行的,所以在进行异步IO时需要用到异步IO接口,aio_read系统调用,在用户调用该函数之后,可以继续执行其他逻辑,无论是否准备好,内核都会通知用户可以做其他事情。然后当内核数据准备好时,内核即可发送通知告知用户处理数据。
IO的两个阶段都是非阻塞的。
Linux下,通知的方式有三种
- 强行打断:在进程处理其他逻辑时,在该点进行标记,然后优先处理内核数据。
- 等待返回:如果进程在内核态进行操作,则内核发送的信号就会等待到内核处理完成,在发送给用户进程。
- 进程唤醒:如果进程被挂起,则内核会将其唤醒,等待CPU的调度。