同步IO与异步IO

前xia言che

昨天晚上携程笔试还有3分钟结束的时候,收到腾讯的面试电话,肯定接啊。三天前就因为网易笔试的时候腾讯打来电话重约了时间。问了挺多问题的,大概13 4个吧,有两个没答上来,一个没答好,而且可能是因为紧张吧,答的深度也不够。估计要凉凉。有一个问题我影响比较深刻,面试官问到同步IO与异步IO,前几天正好有看stevens的UNP里面有一节提到过这个,所以答的还算可以吧。今天特地总结下,为后面的epoll学习做个铺垫(也问epoll了,说不会…估计已经凉了一半)。

概述

IO模型可以分为两大类,一个是同步IO,一个是异步IO。同步IO中又可以分为:阻塞IO,非阻塞IO,IO多路复用和信号驱动式IO
对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。也就是说:IO发生时会涉及到两个对象,一个是调用这个IO函数的进程,另一个就是内核。当一个read操作发生时,它会经历两个阶段:

  1. 等待数据准备好 (Waiting for the data to be ready)
  2. 将数据从内核缓冲区拷贝到进程缓冲区中 (Copying the data from the kernel to the process)

若根据第二阶段的不同,可以划分出同步IO与异步IO;若根据这两个阶段的不同,可以划分出五种IO:

  • 阻塞 I/O(blocking IO)
  • 非阻塞 I/O(nonblocking IO)
  • I/O 多路复用( IO multiplexing)
  • 信号驱动 I/O( signal driven IO)
  • 异步 I/O(asynchronous IO)

同步IO

阻塞IO

在linux中,默认情况下所有的socket都是阻塞的,比如一个典型的读操作流程:

当用户进程调用了recvfrom系统调用,进程由用户态陷入内核态,此时内核开始IO的第一个阶段:准备数据。如果此时数据还没有准备好,这个过程需要等待,整个进程会被阻塞。直到内核将数据准备好,然后将数据从内核缓冲区拷贝到用户缓冲区,内核返回结果,此时用户进程才解除阻塞状态,重新运行。

所以,阻塞IO的特点是在IO两个阶段都发生阻塞。

非阻塞IO

linux下可以设置socket(通过fcntl函数)使其变为非阻塞。当对一个非阻塞socket执行读操作时:

用户进程发出read操作时,如果内核中的数据还没准备好,那么它不会阻塞用户进程,而是立刻返回一个error。用户进程不需要等待,调用后立刻得到一个结果。发现是一个error时,它便知道数据还没有准备好,于是再次发送read操作。直到内核中的数据准备好了,并且再次收到用户进程的read调用时,内核将内核缓冲区的数据拷贝到用户缓冲区,然后返回。

所以,非阻塞IO的第一阶段不阻塞,而是不停地轮询询问,只在第二阶段阻塞。

IO多路复用

这也就是常用的select,poll,epoll了(昨晚问我了解epoll么,我说不了解….但是今晚了解了。哭晕…)。它们的特点是单个进程可以同时处理多个网络连接的IO。因为select,poll,epoll可以不断的轮询所负责的所有socket,当某个socket有数据到达了或者准备好了,就通知用户进程。在调用select后,进程会被阻塞(不是被socket IO阻塞的,而是被select阻塞),同时内核会监视select负责的所有socket,当任何一个socket中的数据准备好了,select就会返回,进程唤醒,然后调用read操作,将数据从内核缓冲区拷贝到用户缓冲区。

所以,IO多路复用的特点是两个阶段都会阻塞。

信号驱动IO

信号驱动IO的原理是:当用户进程读取数据时,如果此时数据没有准备好,那么该调用会返回,进程不会阻塞,可以干其他的事情,但是此时内核已经在进行第一阶段的操作了:开始准备数据。当数据准备好时,内核会向用户进程发送一个信号,表示数据已经准备好可以读了,用户进程再调用read系统调用将内核缓冲区的数据拷贝到用户缓冲区中,拷贝完成才返回。

所以,信号驱动IO的特点是,在IO的第一阶段不阻塞,在第二阶段阻塞。

异步IO

异步IO的原理是:当用户进程发起read系统调用后,不会阻塞立即返回,它可以去做别的事情。此时内核会等待数据准备完成,然后将数据拷贝到用户内存,当这一切完成之后,内核给用户进程发送一个信号,通知用户进程数据已经拷贝完成了。

所以,异步IO的特点是,在IO的两个阶段都不会发生阻塞。

总结一下:
阻塞IO与非阻塞IO的区别在于在内核等待数据准备好的结段,阻塞IO会一直阻塞对应的进程直到数据准备好并且拷贝到用户缓冲区,而非阻塞IO在数据准备阶段是不阻塞的,用户进程会轮询(占用CPU),在数据拷贝阶段会发生阻塞。

同步IO与异步IO的区别是在数据拷贝的阶段,同步IO是阻塞的,直到数据拷贝完成,而异步IO不会阻塞,数据拷贝完成才通知用户进程。


参考资料:
《UNIX网络编程 v1》(UNP)
Linux IO模式及 select、poll、epoll详解