IO模型
本篇了解一下常见的IO模型:阻塞IO、非阻塞IO、信号驱动IO、异步IO
通常用户进程一个完整IO包括两个阶段:用户进程空间<->内核空间,内核空间<->设备空间(网络/磁盘)。IO有内存IO、磁盘IO、网络IO,通常所说的IO是指磁盘或网络IO。
在linux系统中,程序无法直接操作IO设备,必须进行系统调用通过kernel来协助完成IO操作,内核会为每个IO设备维护一个缓冲区。
就一个IO读操作来说,进程发起系统调用,内核会先去缓冲区检查有没有,没有话再去访问设备空间,因为IO设备一般速度较慢,需要等待。
对于一个网络IO操作来说,涉及到两个系统对象,一个是调用这个IO操作的进程,另一个是执行这个IO操作的系统内核(kernel)。
当一个IO操作发生时,需要经历两个阶段:
a)数据准备阶段(数据从设备拷贝到内核缓冲区,或者相反操作)
b)将数据从内核缓冲区拷贝到进程缓冲区(或者反过来)
这些IO模型的区别在于对这两个阶段的处理不同。以读操作为例
1 阻塞IO
进程发起系统调用后,在数据准备阶段被阻塞,系统内核没有任何返回,直到数据准备好后响应进程,接下来还需再次等待系统内核将数据从内核缓冲区拷贝到进程空间。
适用于并发量小的网站,在进程阻塞期间,不消耗cpu资源。
Java BIO
2 非阻塞IO
进程发起系统调用后,系统内核在数据准备阶段不会阻塞,会返回一个错误给进程。在数据准备阶段,进程没有被阻塞,但是进程要不停地轮询进行系统调用,直到数据准备好后系统内核返回准备好的消息。
接下来还需再次等待系统内核将数据从内核缓冲区拷贝到进程空间。
使用并发量小的网站,在轮询期间,消耗cpu资源。
3 IO复用模型
当进程调用了select,那么整个进程都会被阻塞。同时,系统内核会监视所有select负责的socket,当任何一个socket中的数据准备好后,select就会返回。这个时候,然后用户再调用read的系统调用,系统内核将数据从内核空间复制到用户空间。相比于BIO和NBIO,IO复用使用了两个系统调用select和recvfrom。select的优势在于可以同时处理多个socket连接,并不是对单个连接的处理速度快。
在多路复用模型中,对于每一个socket,一般都设置为NBIO,但是整个用户的进程实际上还是被阻塞的。进程实际是被select函数阻塞的,而不是被socket IO阻塞的。
select、poll、epoll都是多路复用IO。适用高并发服务开发,一个进程(线程)处理多个socket连接。
Java NIO。
4 信号驱动IO
当进程发起IO请求之后,会给对应的socket注册一个信号函数,在数据准备阶段,用户线程会继续执行,当系统内核数据准备就绪后会给用户线程发送一个信号通知。
回调机制,实现难度大。
5 异步IO
上述几种IO模型,都不是完全的异步IO,在内核数据复制到进程空间时都会被阻塞。
异步IO,是指当进程发起IO请求之后,就不管了,可以去执行其它操作了。然后当系统内容将内核数据准备好并且再将内核数据复制到进程空间后,通过进程处理成功,然后进程就可以直接使用了。
这才是真正的异步IO。
Java AIO。