Linux的IO模型介绍

阻塞:用户进程访问数据时,如果未完成IO,等待IO操作完成或者进行系统调用来判断IO是否完成

非阻塞:用户进程访问数据时,会马上返回一个状态值,无论是否完成

同步:用户进程发起IO(就绪判断)后,轮询内核状态

异步:用户进程发起IO后,可以做其他事情,等待内核通知

由于进程无法直接操作I/O设备,因此必须通过系统调用请求kernel来协助完成I/O操作,内核会为每一个I/O设备维护一个buffer。其工作流程为:

对于输入而言,等待(wait)数据输入至buffer中需要时间,再从buffer复制(copy)到用户进程缓存区中也需要时间。

因此,根据等待模式不同,I/O动作可以分为5种模式:

网络IO模型和文件IO模型是一样的,包括阻塞IO、非阻塞IO、多路复用IO、信号驱动的IO、异步IO,5种模型。

一次IO包括两个过程,内核数据准备、把数据从内核空间copy到用户空间。

1、阻塞IO(recvfrom系统调用)是在两个过程应用都处于阻塞状态。

当进程执行系统调用时,如果在用户进程空间的缓存区中没有找到相应的数据,则进程将处于阻塞状态。当内核从磁盘上读取数据到内核空间的buffer中,并且buffer中的数据复制到用户进程空间的缓存区中这段时间里,进程都将处于阻塞状态。当数据到达用户进程缓存区中,进程就会解除阻塞状态,再有进程将数据返回给客户端。

由于进程处于阻塞状态,因此很少占用cpu,只是等待cpu的响应。因此,这种模型可以提高cpu的使用效率。

2、非阻塞IO(recvfrom系统调用)是应用发出IO操作后可以立刻返回,通过轮询盘判断数据是否准备好,在copy数据阶段阻塞应用。

进程执行系统调用,如果在用户进程空间缓存区中没有找到数据,则进程不会处于阻塞状态。而是进程不断的询问用户进程空间缓存区中是否有数据。如果没有,立即返回EWOULDBLOCK,此时由于进程处于非阻塞状态,因此,该进程可以做其他的事情。

在这种模型下,进程需要不断的询问用户进程缓存区中是否有数据,因此,会造成cpu大量的进行上下文切换(进程切换),会大量的消耗cpu。

3、多路复用IO(select、recvfrom系统调用)是阻塞调用select,查找可用的套接字,如果有套接字可用,那么就阻塞调用(recvfrom)完成数据的copy过程。linuxselect就是这种模型,缺点是一次select会扫描所有的socket。

4、信号驱动的IO(SIGIO、recvfrom)是应用发出SIGIO后立刻返回,内核中数据准备好后,通知应用,由应用进行阻塞recvfrom调用从内核copy数据。linuxepoll就是基于事件的就绪通知方式,省去了所有socket的扫描开销。

epoll,kqueue比select高级,select是在内核里做轮询操作,epoll是使用回调机制,消耗的资源更少.套接字比较多的时候,每次select()都要通过遍历Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。epoll给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询。

5、异步IO(aio-read)是应用发出aio-read后马上返回,数据准备好后,由操作系统把数据copy到应用,并通知应用数据copy完成。

当然除了以上的IO模式,还有直接IO(应用绕过内核直接访问磁盘文件),内存映射MMap(建立内存和文件的映射关系),这两种模式在linux2.6上本质上没有差异。

直接IO在数据库引擎中用的比较多,数据库的buffer绕过文件系统cache,直接访问硬盘。Mongodb中就是用到mmap,无需调用read、write系统调用。

同步、异步、阻塞、非阻塞一般有这么几种组合:

同步非阻塞,典型代表是javaNIO

异步阻塞,典型代表是select,epoll

异步非阻塞,典型代表是aio