Unix/Linux上的五种IO模型

204 阅读5分钟

I/O模式是操作系统上任何的I/O,内存I/O,磁盘I/O,网络I/O都可以应用。
我们主要考虑网络I/O。面试中如果问到五种IO模型,参考下文回答处理过程并画图解释。

一、阻塞(blocking)

同步阻塞的IO模型效率不高。

image.png

内核空间就是网络接收到远端发送过来数据的tcp接收缓冲区
应用进程调用read,系统调用,是系统的I/O接口。 在应用进程看来,是一直阻塞住,数据是否就绪、数据就绪后的从内核空间向用户空间的拷贝整个过程,应用程序时间都要在read上花费完,一直阻塞住。应用程序调用read这个I/O接口,内核把这一系列事情做完以后,然后再唤醒当前这个应用线程,可以起来处理数据了。这就是同步的阻塞I/O。

二、非阻塞(non-blocking)

非阻塞就是在调用read之前用setsockopt(sockfd)系统调用方法,把用sock函数创建的sockfd设置成non-blocking非阻塞,数据未就绪状态下是会不断返回的。

image.png

当应用进程调用read之前,用setsockopt系统调用方法把用socket创建的sockfd设置成EAGAIN,在数据未就绪状态下,它是不断的返回。通过返回值的判断,size==0&&errno==EAGAIN,表示正常的返回,内核的数据未就绪。如果数据准备好以后,数据从内核空间TCP接收缓冲区拷贝到用户空间的buf,花费的还是应用程序的时间,这个过程是同步非阻塞模型。

三、IO复用(IO multiplexing)

IO复用是同步过程。 image.png

默认的select/poll/epoll都是可以设置timeout超时时间的。如果不设置, 就是事件未发生,调用I/O复用接口的进程线程是阻塞住的。 设置超时时间,就是可以工作在非阻塞模式下,同样去检测返回值是不是EAGAIN,是错误返回还是正常非阻塞的返回。
如果数据准备好了以后,返回可读的fd,返回发生事件的event,接下来对发生事件的event调用accept或者read,上图中是发现是已建立连接的读写事件,发起系统调用,整个的数据从内核tcp接收缓冲区拷贝到用户空间的buf,还是要耗费应用程序的时间,还是同步的过程。 数据的未就绪到就绪调用了I/O复用接口,就绪以后根据具体的发生事件fd,进行相应的I/O接口的调用,好处是在一个线程里面,调用一个I/O复用接口,可以监听很多很多的套接字(高并发),当多个套接字有数据可读的话,I/O复用会给应用程序返回可读或者可写的socket列表,然后应用程序根据I/O复用返回的这些fd进行相应的读写操作。 不像阻塞和非阻塞,一个应用进程或者应用线程一次只能处理一个sock,这太浪费进程或线程了,不适合处理高并发操作请求,线程数量不是越多越好,所以IO复用是我们在设计高性能服务器必不可少的。

四、信号驱动(signal-driven,linux特有)

image.png

内核在第一个阶段是异步,在第二个阶段是同步;与非阻塞IO的区别在于它提供了消息通知机制,不需要用户进程不断的轮询检查(就是相当于非阻塞模式,或者是epoll设置了timeout),减少了系统API的调用次数(这就是说,如果你是非阻塞,或者是设置了timeout的I/O复用的话,当你检查返回值=0&&EAGAIN,会再次调动read或者I/O复用接口来检查内核的数据,是否从未就绪到就绪了)提高了效率。

在数据未就绪到数据就绪的过程中,应用进程是在继续执行,完全放飞自我了,可以做任何事清,它调用相应的方法注册SIGIO信号处理程序,相当于协商了通知的方式,相当于回调操作,给内核注册以后,内核就给应用进程返回“知道了”,知道应用进程对什么事件感兴趣了,知道事件发生时该怎么通知应用进程。等待数据的过程就是一个异步的过程。

但是应用进程在读或者写的过程中,不能放飞自我,调用read去读这个数据,是同步的,数据从内核TCP缓冲区拷贝到用户的buf,应用进程是要花费时间的,数据搬完了,应用进程才能进行向下执行。

五、异步(asynchronous)

image.png

image.png

典型的异步非阻塞状态,Node.js采用的网络IO模型。

整个过程从数据就绪到数据拷贝,不耗费应用程序的时间。应用进程继续执行的,可以去做其他事情。
内核处理好之后,通知应用程序“好了,该处理这个了”。读写事件发生了,该读该写的数据都写到buf了。 异步IO效率是非常不错的,但是编程也是最复杂的,出问题很难排除。