常见的io模型

基于unix网络编程那本书上的IO模型的介绍,常见的IO模型:
常见的io模型_第1张图片

对于阻塞IO模型
常见的io模型_第2张图片

对于非阻塞IO模型
常见的io模型_第3张图片

对于IO复用模型
常见的io模型_第4张图片

对于基于事件驱动的IO模型
常见的io模型_第5张图片

最后一个异步IO模型
常见的io模型_第6张图片

五种IO模型的比较
常见的io模型_第7张图片

在理解这些模型之前,先介绍几个概念:
用户空间和内核空间:
对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。但是为了保证内核的安全,避免误操作内核,系统将进程的虚拟空间分为两个部分,用户空间和内核空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。

进程的切换:
为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。
从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:
保存处理机上下文,包括程序计数器和其他寄存器。
更新PCB信息。
把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。
选择另一个进程执行,并更新其PCB。
更新内存管理的数据结构。
恢复处理机上下文。
进程切换会造成系统较大的开销

进程的阻塞:
正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。

缓存 IO:
缓存 IO 又被称作标准 IO,大多数文件系统的默认 IO 操作都是缓存 IO。在 Linux 的缓存 IO 机制中,操作系统会将 IO 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。当数据在内核空间拷贝到应用程序地址空间,会造成较大开销。

对于linux IO模型来说,本质上是对socket的读写。
当一次read完成,经历了两个阶段:
等待网络上数据的到来,系统将它放入内核缓冲区
有数据后,将数据从内核缓冲区读到应用程序缓冲区

接下来介绍着5个模型:
对于最简单的阻塞IO,在默认情况下,linux中的socket都是阻塞的,如果想要设定非阻塞,需要自己强制设定。在这个模型中,调用一次recv,首先系统会判断是否有数据在内核缓冲区中,如果没有,就等待数据的到来,当数据到来后,系统将数据从内核空间拷贝到用户空间,在这个阶段,该进程全程阻塞,不能处理其他事情,直到数据拷贝到用户空间结束。
所以阻塞IO最大的特点是IO的两个阶段都是阻塞的。这个模型优缺点都非常明显,适用于简单,少量的io环境。

对于非阻塞IO,通过调用ioctl,或者fcntl等函数强制将socket转成非阻塞。
非阻塞调用recv会出现什么情况,如果内核缓冲区没有数据,recv会立刻返回一个EAGAIN 或 EWOULDBLOCK,表示内核数据还没有准备好。你需要持续不断调用recv来试探数据到达了内核缓冲区了没有。如果内核缓冲区有数据,那么读取内核缓冲区数据到用户空间这一阶段和阻塞io没有任何区别,也是阻塞的。
所以阻塞IO和非阻塞IO的最大区别就是第一阶段系统的处理,第二阶段都一样。非阻塞IO以不断轮训的方式来试探有没有数据到达内核缓冲区的这个方式比较浪费系统资源。

IO多路复用:
在非阻塞方式下不断轮训试探是否有数据的方式很浪费系统资源,那么能不能有一个机制,就是当内核空间有数据到达时,由系统告知我这个信息,不需要我主动去问询。IO多路复用就是干这个的,而且IO多路复用能监测多个socket上的IO事件。这是IO多路复用最大的两个特点。
对于linux来说,select,poll, epoll这三个函数都可以完成这个功能。
IO多路复用在监测IO事件的到来,是阻塞的。对于从内核空间读取数据到用户空间也是阻塞的。这是io多路复用是不可避免的。

基于信号的IO模型:
允许Socket进行信号驱动IO,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。这个模型没咋见过,也很少用。

异步非阻塞 IO:
相对于同步IO,异步IO不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的。
前有很多开源的异步IO库,例如libevent、libev、libuv。

以上五个IO模型状态明显,区别也很明显。

下面在介绍同步和异步,阻塞和非阻塞的概念。
对于同步和异步来说,指的是消息的通知机制。
对于同步IO来说,完成一次recv,系统干完了等待数据和数据提取这两个阶段后,告知该次recv动作完成,接到了这个消息,这次同步IO完成,但是在过程中是在一直等待的。
对于异步IO来说,完成一次recv,你只需要告知我要完成一次recv,中间不用等待,直接返回,系统完成两个阶段后会主动告知。
当一个同步调用发出后,调用者要一直等待返回消息(结果)通知后,才能进行后续的执行;当一个异步过程调用发出后,调用者不能立刻得到返回消息(结果)。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能够执行其他业务。函数只有在得到结果之后才会返回。
(a) 如果这个线程在等待当前函数返回时,仍在执行其他消息处理,那这种情况就叫做同步非阻塞;
(b) 如果这个线程在等待当前函数返回时,没有执行其他消息处理,而是处于挂起等待状态,那这种情况就叫做同步阻塞;

所以同步/异步关注的是消息通知的机制,而阻塞/非阻塞关注的是程序(线程)等待消息通知时的状态。

你可能感兴趣的:(网络编程)