最近看到OVS用户态的代码,在接收内核态信息时使用了epoll多路复用机制,很受用百思不得其解,于是从网上找了一些资料,研究了本书的相应章节《UNIX网络变成卷1:套接字联网API》。虽然网上关于这个话题的博文很多,解释的也很详细,但我还是在这里研究一下。做笔记并记录您的想法。IO模型《UNIX网络变成卷1:套接字联网API》一书中提到了五种I/O模型,分别是:阻塞I/O、非阻塞I/O、I/O多路复用(epoll和select都是I/O/O多路复用机制)、信息驱动I/O、异步I/O,下面分别详细介绍。阻塞I/O模型blocking,顾名思义,当一个进程在等待数据时,如果数据还没有产生,进程就会一直等待,直到等待数据产生,在这个过程中进程的状态是阻塞的.如上图所示,在Linux中,用户态进程调用recvfrom系统调用接收数据。当前内核没有数据就绪,用户态进程会一直在这里等待,直到内核态就绪后才会进行其他操作。data,将数据从内核态拷贝到用户空间内存,然后recvfrom返回成功指示。这时候用户态就会解除阻塞状态,对接收到的数据进行处理。从上面的流程可以看出,用户态接收内核态数据时,主要有两个过程:内核态获取数据-->将数据从内核态内存空间复制到用户态进程缓冲区非阻塞I/O模型中非阻塞I/O模型,当进程等待内核传来数据时,当数据没有到达时,进程会不断询问内核,直到内核准备好数据。如上图所示,用户态进程调用recvfrom接收数据。当前,没有生成任何数据消息。这时recvfrom返回EWOULDBLOCK。用户态进程会一直调用recvfrom来查询内核。当内核准备好数据时,用户态进程将不再询问。内核,数据从内核拷贝到用户空间后,recvfrom成功返回,用户态进程开始处理数据。需要注意的是,当数据从内核拷贝到用户空间时,用户态进程是被阻塞的。非阻塞I/O模型,个人觉得这个名字可能有点迷惑,它和阻塞模型并不完全相反,并不是说进程不等待数据,所以做其他的事情,但是此时进程一直在原地等待数据的到来,这与阻塞模型不同。非阻塞是指进程一直在敲门询问“数据准备好了吗,快给我”,然后门后的人说“还没准备好,请稍等!”,这个过程是一种轮询的状态,阻塞式是一种佛系态度。当我敲了一次门后,门后的人没有任何反应,于是我就去睡觉了,什么也没做,直到门后的人反应过来叫醒他,流程才进行下一步。ovs的用户态源码中使用了I/O??多路复用模型。在计算机网络中,“多路复用”的用法有很多,比如多路复用,就是指在一条链路上一次只能传输一个数据流。如果要在两个源之间同时传输多个数据流,则需要多个链路。然而,多路复用技术可以将一条链路划分为频率,或者划分传输时间,从而允许多个数据流在一条链路上同时传输。应用于I/O多路复用模型,可以对应以下应用场景:如果一个进程需要等待多个不同的消息,那么一般的做法是启动多个线程,每个线程接收一类消息。如果每个线程都采用阻塞I/O模型,那么每个线程在没有产生消息时都会阻塞,也就是说在多线程中使用阻塞I/O。I/O多路复用基于上述场景,不需要使用多线程监听消息,进程直接监听所有消息类型,涉及到select、poll、epoll等不同的方法。如上图所示,用户态进程采用select方式,通过select方式可以等待多个不同类型的消息。如果一类消息就绪,select会返回该信息,然后用户态进程调用recvfrom接收数据。选择复用机制可以看作是一个描述符集的管理。进程把不同的描述符放到这个集合中等待不同的消息产生,然后通过select统一管理,让它们同时等待。此集合中任何事件的生成。I/O多路复用类似于阻塞I/O。不同之处在于,I/O多路复用等待多种类型的事件,而阻塞I/O只等待一种类型的事件。另外,在I/O多路复用中,会有2个系统调用产生(如上图select和recvfrom),而阻塞I/O只产生1个系统调用。那么这就涉及到具体的性能问题。当只有一种类型的事件时,使用阻塞I/O模型的性能会更好。当有很多不同类型的事件时,I/O多路复用的性能会更好。有很多,因为阻塞I/O模型只能监听一种事件,所以这时候就需要使用多线程进行处理。信号驱动I/O模型在信号驱动I/O模型中,与阻塞和非阻塞有本质区别,即用户态进程不再等待内核态数据准备好,可以直接做别的事情。如上图,在等待数据的时候,用户态会先给内核发送一个信号,告诉内核我要什么数据,然后用户态就会忽略它,去做其他的事情,而当数据在内核态准备好后,内核立即向用户态发送一个信号,说“数据准备好了,过来检查”。用户态进程接收到后,立即调用recvfrom,等待数据从内核空间复制到用户空间,recvfrom完成后返回成功指示后,用户态进程再处理其他事情。从上图可以看出信号驱动的I/O模型有一种异步操作,但是在数据从内核拷贝到用户空间的这段时间里,用户态进程是阻塞的。异步I/O模型asynchronousI/O模型比信号驱动的I/O模型更彻底。如上图所示,首先用户态进程告诉内核态需要什么数据(通过上图中的aio_read),然后用户态进程就不管了。内核在做其他事情的时候,等待用户态需要的数据准备好,然后将数据拷贝给用户,这时候告诉用户态进程,“数据准备好了,请检查”,并且然后用户态进程直接处理用户空间中的数据。在向用户空间拷贝数据期间,用户态进程也是非阻塞同步I/O《UNIX网络变成卷1:套接字联网API》本书中并没有将同步I/O描述为一个单独的I/O模型。在阅读这些资料之前,我一直认为阻塞I/O相当于同步I/O,非阻塞I/O相当于异步I/O。可见,不能单纯从字面意思来判断。通过以上几种I/O模型的描述,可以得出一个结论:阻塞I/O、非阻塞I/O、I/O多路复用模型都是同步I/O模型,因为在等待的过程中data其中,这三种模型中的进程不做其他事情,即使是非阻塞轮询也可以看成是一种同步。同时,本书也认为信号驱动的I/O模型是同步I/O。书中说:POSIX将同步IO操作定义为“导致请求进程阻塞,直到I/O操作完成”,而书中认为在信号驱动的I/O模型中等待数据的时间段是不认为是真正的I/O操作(因为没有调用I/O相关的系统调用),从内核空间拷贝数据到用户空间才是真正的I/O操作(此时调用了recvfrom系统调用)。I/O模型对比书上的这个图表很清楚。从等待数据和数据复制这两个时间段,指出了不同I/O模型的区别,这里不再赘述。总结在网上看了很多资料,不同的博主对这五款机型有不同的结论。无一例外,他们基本上都是用一个生活场景来描述他们的不同,但我个人认为有些场景描述太简单了。完整描述不同型号之间的差异。这里我也举一个生活中的场景作为总结。当然,这只是我自己的想法。如有不妥之处,可以在评论区指出。我们去餐厅吃饭,都会经过以下几个步骤:先根据菜单点菜,然后等待厨房准备好,然后服务员上菜。在这个场景下,等待厨房上菜相当于等待数据,而服务员上菜相当于将数据从内核拷贝到用户空间。你是用户态进程,而服务员和餐厅被视为内核态进程。阻塞I/O模型:只点一个菜,然后开始在餐桌上等待,这个过程什么都不做,等服务员把菜上桌再开始吃饭。非阻塞I/O模型:只点一个菜,然后开始等待,什么都不做,等一会再问服务员,“我的菜做好了吗?”,不行,就等,等一会,然后我就跑了问....重复这个过程,直到服务员说“亲爱的,你的食物准备好了,我现在就把它送到你的桌子上”,然后你坐在桌子上等待服务员把食物送到你的桌子上,才开始吃。I/O多路复用模型:你点了很多菜,然后开始等待。某一刻,厨房里同时上了一道或几道菜,服务员跑过来说:“亲爱的,您的菜有些好了,您稍等一下。”上桌了吗?”你回答,现在上菜,所以服务员上一个菜(服务员一次只能上一个菜),你吃完一个,你吃完最后一个。。。信号驱动I/O模式:只点一个菜,然后把手机留给服务员,让他等菜做好了给你打电话,先不上菜,然后你出去玩,等到菜上来准备好了,服务员手机通知你,你马上回到餐厅,对服务员说,“现在可以上菜了”,于是你就在桌边等着服务员上菜,然后就可以吃饭了。异步I/O模型:只点一个菜,然后把手机留给服务员,先告诉他菜可以上了,上菜了给你打电话,然后你出去玩,等着直到上菜,服务员手机通知你,你马上回到餐桌开始用餐。参考资料UNIX网络成卷1:socket网络API网络IO阻塞、非阻塞、同步、异步总结://segmentfault.com/a/11...
