IO模型的分类:主要有同步IO、异步IO、阻塞IO、非阻塞IO…
操作系统的IO交互模型
现代的操作系统对于存储空间都有一套访问限制控制,所以将存储空间分成了用户空间和内核空间。用户空间负责给应用程序使用,应用程序可以访问用户空间内的数据,但是不可以访问内核空间中的数据;而内核程序可以访问计算机的所有存储空间,包括用户空间、内核空间以及硬件设备上的数据。所以当应用程序需要访问硬件设备上的数据或者是内核空间的数据时,就必须要通过内核空间的程序来实现。所以内核空间对外也提供了很多的函数,提供给了应用程序使用,让应用程序可以通过内核程序来访问想要的数据。
整体的IO交互模型如下图示:
下面就以应用程序需要从网卡中读取数据为例,整体IO交互流程主要分成如下几个步骤:
1、应用程序调用内核提供的函数发起请求数据(请求内核函数)
2、内核访问网卡存储空间获取数据(内核获取数据)
3、内核将获取的到数据复制到用户空间(内核复制数据)
4、应用程序从用户空间中获取需要的数据(应用程序获取数据)
操作系统的IO模型
IO的类型
同步IO
应用程序调用内核函数到最终应用程序从用户空间中获取数据的整个流程是需要用户线程一次性完成的那么就是同步IO
异步IO
应用程序调用内核函数请求获取数据和最终从用户空间中拿到数据不是一次性完成的,而是先请求数据,等数据全部准备好了之后再获取的就是异步IO
阻塞IO
应用程序调用内核函数请求数据,如果此时还没有数据,那么应用程序就一直等待着,直到成功拿到数据为止,此时应用程序线程是一直处于等待状态的,那么就是阻塞IO
非阻塞IO
应用程序调用内核函数请求数据,如果此时还没有数据,那么应用程序就不等待先去处理其他事情,过一会再重新尝试请求,直到成功拿到数据为止,此时应用程序不会一直处于等待状态,那么就是非阻塞IO
操作系统IO模型
操作系统的IO模型也主要分成同步IO和异步IO两大类,而同步IO又分成了阻塞和非阻塞等类,异步IO不会出现阻塞IO情况,所以异步IO肯定是非阻塞的IO,操作系统IO模型主要分成如下几种类型
tips:操作系统给应用程序提供了recv函数,该函数用于从socket套接字中接收数据,默认情况下会等到网络数据接收完成并复制到用户空间之后才返回结果或者失败之后返回结果,可以通过flags参数设置如果没有数据的话立即返回结果
同步阻塞IO
应用程序调用操作系统的recv函数,recv函数默认会等待数据接收完成并复制到用户空间之后返回结果,而如果数据没有准备好的话,那么应用程序就一直处于等待状态,直到有数据返回,此时应用程序的线程处于阻塞状态,无法执行其他操作。
同步非阻塞IO
应用程序调用操作系统的recv函数,recv函数设置flags值为立即返回,那么如果内核发现没有数据时就立即返回,应用程序得到结果之后不再等待,而是先处理其他业务,然后轮训不断尝试获取数据,直到数据成功返回,此时应用程序不处于阻塞状态,可以先处理其他操作。
同步多路复用IO
应用程序先调用操作系统的select函数或者poll函数或者epoll函数,这几个函数的作用是监听网络套接字上的数据状态,如果有数据可读,那么就通知应用程序,此时应用程序再调用recv函数来读取数据,此时肯定是可以读取到数据的。可以发现多路复用IO的特点是不需要尝试获取数据,而是先开启另外一个线程来监控数据的状态,等到有数据的时候再同步获取数据,而在没数据的时候也是不需要等待的。多路复用IO调用select函数之后也会阻塞进程,但是不会真正的IO操作线程没有被阻塞,所以实质上是同步非阻塞IO。
同步信号驱动IO
通过调用sigaction函数注册信号函数,等内核数据准备好了之后会执行信号函数通知应用程序,应用程序此时再调用recv函数同步的获取数据。信号驱动IO和异步IO有点类似,都是异步通知,不同的是信号驱动IO的真正读取数据的操作还是同步操作的。
异步非阻塞IO
通过调用aio_read函数,那么内核会先将数据读取好,并且复制到用户空间之后,再执行回调函数通知应用程序,此时应用程序就可以直接从用户空间中读取数据,而不需要再从内核中读取数据了。
总结
IO操作主要可以分成两个阶段:
1、数据准备阶段;
2、数据从内核空间复制到用户空间阶段
而阻塞IO、非阻塞IO、多路复用IO和信号驱动IO只是在第一个阶段不同,而第二个阶段是相同的,都是需要阻塞当前线程等待数据复制完成,虽然阻塞的时间足够短,所以只要需要执行第二阶段的都是属于同步IO;
而异步IO模型的第一阶段和第二阶段都是内核主动完成,再两个阶段都不会阻塞当前线程去处理其他事情。