I/O复用

什么是I/O复用?


操作系统为你提供一个功能,当内核发现进程指定的一个或多个I/O条件就绪时(即输入已经准备好被读取,获取描述符能够接受更多的输出),他就会给进程一个通知。这种功能就称之为“I/O复用”。I/O复用主要是由select和poll两个函数提供。

I/O复用典型使用在一下场景:
  • 当客户处理多个描述符(通常是交互式输入和网络套接字)时,必须使用I/O复用。
  • 一个客户同时处理多个I/O套接字。
  • 一个TCP服务器既要处理监听套接字,又要处理已连接套接字。
  • 一个服务器既要处理TCP,又要处理UDP。
  • 一个服务器要处理多个服务或多个协议。

I/O模型


在Unix中有五种I/O模型:

  • 阻塞式I/O;
  • 非阻塞式I/O;
  • I/O复用(select和poll);
  • 信号驱动式I/O(SIGIO);
  • 异步I/O(POSIX的aio_系列函数);
对于一个网络I/O,它会涉及两个系统对象,一个是发起这个I/O操作的进程(或线程),另一个就是操作系统内核。通常一个输入(read)操作会包含两个不同的阶段:
  • 等待数据准备好;
  • 从内核向进程复制数据;
对于一个socket上的read操作,第一步通常涉及等待数据从网络中到达,当所有等待分组到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。


阻塞式I/O


最流行的I/O模型是阻塞式I/O(blocking I/O)模型。默认情况下,所有的socket都是阻塞的。

其示意图如下:


当应用进程发起了recvfrom系统调用,如果此时内核中的数据还没有准备好,对于网络I/O来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),那么内核需要等待数据准备好。而在应用进程这边,整个进程都会被阻塞。当数据准备好且从内核复制到应用进程缓冲区中或者发生错误,用户进程才解除阻塞状态并返回。最常见的错误是系统调用被信号中断。

所以,对于阻塞I/O而言,其在I/O执行的两个阶段都被block了。


非阻塞式I/O


在Linux下,可以设置socket使其变为non-blocking。

模型示意图如下:


从图中可以看出,当应用进程发起了一个recvfrom系统调用时,如果内核中的数据还没有准备好,那么它并不会阻塞应用进程,而是立刻返回一个EWOULDBLOCK的错误。从应用进程的角度来看,当它发起了一个I/O系统调用后,并不需要等待,而是立刻得到一个结果。当返回结果是EWOULDBLOCK时,应用进程就知道数据还没有准备好,于是它可以再次发起一个recvfrom的系统调用。一旦内核中的数据准备好了,并且又再次收到了用户进程发起的I/O系统调用,那么它就马上将数据拷贝到应用进程缓冲区中,然后返回一个成功指示。

所以,应用进程其实是需要不断地主动询问内核数据是否准备好。


I/O复用


I/O复用(I/O multiplexing)主要通过select/poll函数实现。I/O复用的好处在于单个应用进程就可以同时处理多个网络连接的I/O。其基本原理就是select/poll函数会不断轮询其所负责的所有socket,当某个socket的数据准备好了,就通知应用进程。

其I/O模型如下:


当应用进程发起了select的系统调用,那么整个应用进程就会被阻塞。同时内核会不断轮询select所负责的所有socket,当其中任何一个socket中的数据准备好了,select就会返回。此时应用进程再发起一个recvfrom系统调用,内核就会将数据复制到应用进程缓冲区中。

在I/O复用模型中,其实际上和阻塞I/O并没有太大的不同,同时由于它需要发起两次系统调用,性能可能更差。I/O复用的优势在于它可以同时处理多个connection。所以,如果处理的连接数不是很高的话,使用I/O复用的性能不一定比使用多线程+阻塞I/O性能更好。


信号驱动式I/O模型


信号驱动式模型是让内核在描述符就绪时发送SIGIO信号通知我们。

其I/O模型如下:


在信号驱动I/O模型中,应用进程发起一个sigaction系统调用,该系统调用将立即返回,此时应用进程不会被阻塞,而是继续工作。当数据准备好被读取时,内核就为该进程产生一个SIGIO信号。随后即可调用recvfrom读取数据,并通知主循环数据已经准备好等待处理,也可以立即通知主循环,让它读取数据。

这种模型的优势在于,等待数据到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据已经准备好被处理,也可以是数据已准备好被读取。



异步I/O模型


异步I/O(asynchronous I/O)有POSIX规范定义。其工作机制是,告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。这种模型与信号驱动模型的主要区别在于,信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O是由内核通知我们I/O操作何时完成。

其模型示意图如下:


当应用进程发起aio_read的系统调用后,会立即返回。从内核角度来看,当它接收到一个异步I/O的系统调用,首先它会立即给应用进程一个返回,所以并不会对应用进程产生任务阻塞。然后内核会等待数据准备好,并将数据拷贝到应用进程,当拷贝完成之后,内核会给应用进程发送一个指定的信号。



各种I/O模型的比较


各种I/O模型的比较如下图所示:


可以看出,前四种模型的主要区别在于第一阶段,它们的第二阶段都是一样的:在数据从内核复制到调用者缓冲区期间进程阻塞于recvfrom系统调用。相反异步I/O模型在这两个阶段都要处理,从而不同于其他四种模型。


阻塞I/O与非阻塞I/O的比较


由之前的说明可知,当发起一个I/O系统调用,阻塞I/O会一直阻塞应用进程直到整个I/O操作完成,而非阻塞I/O会在内核中的数据没有准备好的情况下立即返回。


同步I/O和异步I/O对比


POSIX对两者的定义如下:

  • 同步I/O操作导致请求进程阻塞,直到I/O操作完成;
  • 异步I/O操作不导致请求进程阻塞;
根据以上定义可知,前四种模型——阻塞式I/O模型,非阻塞式I/O模型,I/O复用模型以及信号驱动模型都是同步I/O模型,因为其中真正的I/O操作(recvfrom)将阻塞进程。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值