Open abbshr opened 10 years ago
前两篇Node学习笔记算是对Linux底层I/O的一个初步认识,但过后发现仍有许多不足和么棱两可之处,借着近日学习Linux系统编程学习的新知识,重新写了这篇日志。
Node最大亮点之一莫过于libuv提供的Async I/O模型了,我也曾在之前介绍过其大概实现原理——多线程模拟,同时谈到了Linux下高效的I/O事件通知机制epoll。epoll就属于这些I/O模型中的一个——I/O多路复用。
不涉及底层细节,这里仅谈Linux中各种I/O技术的差异与特点。
Linux有五大IO模型,分别是阻塞I/O、非阻塞I/O、I/O多路复用、信号驱动I/O以及异步I/O。
先说我们最关心的异步IO。用户空间的进程的IO函数(如read())执行后会立即返回,不管内核采用如何机制,再怎么折腾,都与应用进程无关了,因此可以继续执行后续代码。当内核进程监听到IO可用的事件通知后,会把附加数据从内核空间拷贝到用户空间,然后通知应用进程,应用进程有空时便调用回调函数,收集数据。这是最理想的IO方式,只可惜在Linux内核中原生支持的并不好(aio的性能不行),这也是后来Ryan编写libuv的原因之一。而Windows内核提供了完善的异步IO机制——IOCP模型,这里不作讨论。
read()
借用Unix网络编程中的说法,我们这里将I/O请求到结束这一过程分为两部分:
我们说的阻塞不阻塞全由用户进程是否等待内核进程完成这两部分决定!
之所以说异步IO是最理想的IO模型,因为应用进程不理会这两部分,我们的应用进程完全可以做自己想做的事,然后等操作系统的数据可用信号就行了。
这个IO模型相比异步IO要多一步阻塞过程:IO可用事件的监听交由内核,但内核的工作只是到此为止,接下来,将通知应用进程“IO可用”,将底层事件“冒泡”到用户级,然后应用进程再等待内核完成数据的拷贝。这部分是要阻塞进程的。
你可能不大明白啥叫“IO多路复用”,但类比计算机网络中的多路复用技术:在单一通信线路中传输多种信号…,可能仍然不明白。不过提到select、poll以及epoll我想你会恍然大悟。
是的,select、poll、epoll就是Linux系统的IO多路复用技术。那他们多路复用表现在哪方面呢?
按照manpage上的说法,他们会监听在多个文件描述符上发生的指定I/O相关事件,然后在至少一个事件触发时可处理多个文件描述符。这不同于传统的阻塞IO模型——只能处理一个文件句柄。因此说这是IO多路复用。
IO多路复用属于同步I/O模型。因为他们一直阻塞进程直到得到数据。
那么他们在那里发生的阻塞?其实若要使用这一模型,应用进程的函数调用应该选择非阻塞调用,然后才可进入模型的作用域。和前两种模型的最初阶段相同:IO函数的调用都是非阻塞的!调用了马上返回。但接下来情况有所不同:作为代理人的IO多路复用会将本次调用相关的文件描述符纳入事件监听范畴内,阻塞应用进程,等待IO可用事件(这里具体是监听fd上注册的IO事件),一旦有感兴趣的事件发生:对于select和poll机制来讲,他们会将所有监听的文件描述符遍历一次(不管是否有没有被监听的事件在那个fd上面发生);对于epoll机制,仅仅遍历那些触发了事件的文件描述符。因此说epoll的性能比前两者要高。但是这仅仅是就海量句柄中有少数活跃句柄的情况而言,如果句柄数量不多,或者说活跃的句柄很多的情况下,epoll的性能并不能体现出来,甚至不如poll。
这个IO模型的两部分都是用户进程等待内核进程完成调用,所以说都是阻塞的,但应用很广泛。libuv在Linux平台下就基于epoll机制和线程池在用户空间模拟了异步IO,并且性能很好。
Linux提供的一种可选的I/O方式。用户空间在IO函数调用后立马返回,这的确是非阻塞调用,用户进程也的确可以继续干别的,但为了获取数据,该进程不得不轮询。单单用这种模式的话,不仅性能得不到提升,还白白浪费CPU。
最原始最直接的IO方式。用户进程发起函数调用,函数进行系统调用,陷入内核,此时用户进程将等待内核操作的完成,即进程挂起,这时用户进程并不占用CPU的。直到内核完成了所有操作(上面提到的两步重要操作),然后系统调用返回,紧接着返回用户态,IO函数调用完成返回,用户进程得到数据。
不管怎样,这几种IO模型都是Linux的重要IO方式,各有利弊,大可不必摒弃某某。物尽其用,才是正确的做法。
前两篇Node学习笔记算是对Linux底层I/O的一个初步认识,但过后发现仍有许多不足和么棱两可之处,借着近日学习Linux系统编程学习的新知识,重新写了这篇日志。
Node最大亮点之一莫过于libuv提供的Async I/O模型了,我也曾在之前介绍过其大概实现原理——多线程模拟,同时谈到了Linux下高效的I/O事件通知机制epoll。epoll就属于这些I/O模型中的一个——I/O多路复用。
不涉及底层细节,这里仅谈Linux中各种I/O技术的差异与特点。
Linux有五大IO模型,分别是阻塞I/O、非阻塞I/O、I/O多路复用、信号驱动I/O以及异步I/O。
异步I/O
先说我们最关心的异步IO。用户空间的进程的IO函数(如
read()
)执行后会立即返回,不管内核采用如何机制,再怎么折腾,都与应用进程无关了,因此可以继续执行后续代码。当内核进程监听到IO可用的事件通知后,会把附加数据从内核空间拷贝到用户空间,然后通知应用进程,应用进程有空时便调用回调函数,收集数据。这是最理想的IO方式,只可惜在Linux内核中原生支持的并不好(aio的性能不行),这也是后来Ryan编写libuv的原因之一。而Windows内核提供了完善的异步IO机制——IOCP模型,这里不作讨论。借用Unix网络编程中的说法,我们这里将I/O请求到结束这一过程分为两部分:
我们说的阻塞不阻塞全由用户进程是否等待内核进程完成这两部分决定!
之所以说异步IO是最理想的IO模型,因为应用进程不理会这两部分,我们的应用进程完全可以做自己想做的事,然后等操作系统的数据可用信号就行了。
信号驱动I/O
这个IO模型相比异步IO要多一步阻塞过程:IO可用事件的监听交由内核,但内核的工作只是到此为止,接下来,将通知应用进程“IO可用”,将底层事件“冒泡”到用户级,然后应用进程再等待内核完成数据的拷贝。这部分是要阻塞进程的。
I/O多路复用
你可能不大明白啥叫“IO多路复用”,但类比计算机网络中的多路复用技术:在单一通信线路中传输多种信号…,可能仍然不明白。不过提到select、poll以及epoll我想你会恍然大悟。
是的,select、poll、epoll就是Linux系统的IO多路复用技术。那他们多路复用表现在哪方面呢?
按照manpage上的说法,他们会监听在多个文件描述符上发生的指定I/O相关事件,然后在至少一个事件触发时可处理多个文件描述符。这不同于传统的阻塞IO模型——只能处理一个文件句柄。因此说这是IO多路复用。
IO多路复用属于同步I/O模型。因为他们一直阻塞进程直到得到数据。
那么他们在那里发生的阻塞?其实若要使用这一模型,应用进程的函数调用应该选择非阻塞调用,然后才可进入模型的作用域。和前两种模型的最初阶段相同:IO函数的调用都是非阻塞的!调用了马上返回。但接下来情况有所不同:作为代理人的IO多路复用会将本次调用相关的文件描述符纳入事件监听范畴内,阻塞应用进程,等待IO可用事件(这里具体是监听fd上注册的IO事件),一旦有感兴趣的事件发生:对于select和poll机制来讲,他们会将所有监听的文件描述符遍历一次(不管是否有没有被监听的事件在那个fd上面发生);对于epoll机制,仅仅遍历那些触发了事件的文件描述符。因此说epoll的性能比前两者要高。但是这仅仅是就海量句柄中有少数活跃句柄的情况而言,如果句柄数量不多,或者说活跃的句柄很多的情况下,epoll的性能并不能体现出来,甚至不如poll。
这个IO模型的两部分都是用户进程等待内核进程完成调用,所以说都是阻塞的,但应用很广泛。libuv在Linux平台下就基于epoll机制和线程池在用户空间模拟了异步IO,并且性能很好。
非阻塞I/O
Linux提供的一种可选的I/O方式。用户空间在IO函数调用后立马返回,这的确是非阻塞调用,用户进程也的确可以继续干别的,但为了获取数据,该进程不得不轮询。单单用这种模式的话,不仅性能得不到提升,还白白浪费CPU。
阻塞I/O
最原始最直接的IO方式。用户进程发起函数调用,函数进行系统调用,陷入内核,此时用户进程将等待内核操作的完成,即进程挂起,这时用户进程并不占用CPU的。直到内核完成了所有操作(上面提到的两步重要操作),然后系统调用返回,紧接着返回用户态,IO函数调用完成返回,用户进程得到数据。
结语
不管怎样,这几种IO模型都是Linux的重要IO方式,各有利弊,大可不必摒弃某某。物尽其用,才是正确的做法。