一、这里IO是什么
操作系统为了保护自己,设计了用户态、内核态两个状态。应用程序一般工作在用户态,当调用一些底层操作的时候(比如 IO 操作),就需要切换到内核态才可以进行
服务器从网络接收的大致流程如下:
1、数据通过计算机网络来到了网卡
2、把网卡的数据读取到 socket 缓冲区
3、把 socket 缓冲区读取到用户缓冲区,之后应用程序就可以使用
核心就是两次读取操作,五大 IO 模型的不同之处也就在于这两个读取操作怎么交互
二、同步/异步和阻塞/非阻塞
-
同步/异步:这个是应用层面的概念,指的是调用一个函数,我们是等这个函数执行完再继续执行下一步,还是调完函数就继续执行下一步,另起一个线程去执行所调用的函数。关注的是线程间的协作。同步和异步关注的是消息通信机制。所谓同步,就是在发出一个调用时,自己需要参与等待结果的过程,则为同步,前面四个IO都自己参与了,所以也称为同步IO.异步IO,则指出发出调用以后,到数据准备完成,自己都未参与,则为异步.
-
阻塞/非阻塞:这个是硬件层面的概念,阻塞是指 cpu “被”休息,处理其他进程去了,比如IO操作,而非阻塞则是 cpu 仍然会执行,不会切换到其他进程。关注的是CPU会不会“被”休息,表现在应用层面就是线程会不会“被”挂起
三、五种io模型
1.阻塞io
应用调用recvfrom读取数据时,其系统调用直到数据包到达且被复制到应用缓冲区中或者发送错误时才返回,在此期间一直会等待,进程从调用到返回这段时间内都是被阻塞的称为阻塞IO。在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式
-
应用进程向内核发起recfrom读取数据
-
内核进行准备数据报(此时应用进程阻塞)
-
内核将数据从内核负复制到应用空间。
-
复制完成后,返回成功提示
2.非阻塞io
当应用发起读取数据申请时,如果内核数据没有准备好会即刻告诉应用B,不会让B在这里等待,如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码。非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符
-
应用进程向内核发起recvfrom读取数据。
-
内核数据报没有准备好,即刻返回EWOULDBLOCK错误码。
-
应用进程再次向内核发起recvfrom读取数据。
-
内核倘若已有数据包准备好就进行下一步骤,否则还是返回错误码
-
内核将数据拷贝到用户空间。
-
完成后,返回成功提示
3.io多路复用
由一个线程监控多个网络请求(fd文件描述符,linux系统把所有网络请求以一个fd来标识),来完成数据状态询问的操作,当有数据准备就绪之后再分配对应的线程去读取数据
-
应用进程向内核发起recvfrom读取数据
-
内核进行准备数据报(此时应用进程阻塞)
-
内核倘若已有数据包准备好则通知应用线程
-
内核将数据拷贝到用户空间
-
完成后,返回成功提示
下面给出一个应用系统和内核之间的流程图
4.信号驱动
信号驱动IO是在调用sigaction时候建立一个SIGIO的信号联系,当内核准备好数据之后再通过SIGIO信号通知线程,此fd准备就绪,当线程收到可读信号后,此时再向内核发起recvfrom读取数据的请求,因为信号驱动IO的模型下,应用线程在发出信号监控后即可返回,不会阻塞,所以一个应用线程也可以同时监控多个fd
-
应用进程向内核发起recvfrom读取数据
-
内核进行准备数据报,即刻返回
-
内核倘若已有数据包准备好则通知应用线程
-
应用进程向内核发起recvfrom读取数据
-
内核将数据拷贝到用户空间
-
完成后,返回成功提示
下面给出一个应用系统和内核之间的流程图
5.异步IO
应用只需要向内核发送一个读取请求,告诉内核它要读取数据后即刻返回;内核收到请求后会建立一个信号联系,当数据准备就绪,内核会主动把数据从内核复制到用户空间,等所有操作都完成之后,内核会发起一个通知告诉应用
-
应用进程向内核发起recvfrom读取数据
-
内核进行准备数据报,即刻返回
-
内核收到后会建立一个信号联系,倘若已有数据包准备好,内核将数据拷贝到用户空间
-
完成后,返回成功提示
四、五种io对比
可以看到,根据定义,前 4 种模型,在数据的读取阶段,全部都是阻塞的,因此是同步IO。而异步IO模型在整个IO过程中都不阻塞,因此是异步IO