本文参考资料深入理解JAVA I/O系列六:Linux中的IO模型,五种网络IO模型
1. I/O模型基本概念
1.1 用户空间,内核空间
现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟储存空间)为4G(2^32)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核,保证内核的安全,操作系统将虚拟空间划分为两个部分,一个部分为内核空间,一部分为用户空间.
1.2 I/O模型的两个阶段
linux系统IO分为内核准备数据和将数据从内核拷贝到用户空间两个阶段,本文在后续的五种方式的区分中主要也是从这两方面进行区分的。
1.3 同步、异步、阻塞、非阻塞
同步与异步:描述的是用户线程与内核的交互方式,同步指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍然继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。
阻塞与非阻塞:描述是用户线程调用内核IO操作的方式,阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。
2. 五种常见的Linux I/O模型
2.1 阻塞IO(blocking IO)
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
所以,blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。
2.2 非阻塞IO(noblocking I/O)
从图中可以看出,当用户进程发出read操作时,如果 kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。 非阻塞的接口相比于阻塞型接口的显著差异在于,在被调用之后立即返回。
2.3 多路复用IO(IO multiplexing)
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作。与非阻塞I/O相比,select的优势在于它可以同时处理多个connection
在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。
2.4 信号驱动IO (signal driven IO)
信号驱动模型不常用,应用程序提交read请求,调用system call,然后内核开始处理相应的IO操作,而同时,应用程序并不等内核返回响应,就会开始执行其他的处理操作(应用程序没有被IO阻塞),当内核执行完毕,返回read响应,就会产生一个信号或执行一个基于线程的回调函数来完成这次IO处理过程。
也就是说在这个过程中用户进程不被阻塞,而是待内核操作完成后接受响应,才进行处理。
2.5 异步IO(Asynchronous I/O)
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
异步IO是真正非阻塞的,它不会对请求进程产生任何的阻塞,因此对高并发的网络服务器实现至关重要。
3. 总结
从上图可以看出,前四种I/O处理方式,均是同步I/O。只有异步I/O两个阶段都没有产生阻塞。阻塞IO不必说了。
- 非阻塞IO ,IO请求时加上O_NONBLOCK一类的标志位,立刻返回,IO没有就绪会返回错误,需要请求进程主动轮询不断发IO请求直到返回正确。
- IO复用同非阻塞IO本质一样,不过利用了新的select系统调用,由内核来负责本来是请求进程该做的轮询操作。看似比非阻塞IO还多了一个系统调用开销,不过因为可以支持多路IO,才算提高了效率。
- 信号驱动IO,调用sigaltion系统调用,当内核中IO数据就绪时以SIGIO信号通知请求进程,请求进程再把数据从内核读入到用户空间,这一步是阻塞的。
- 异步IO,如定义所说,不会因为IO操作阻塞,IO操作全部完成才通知请求进程