前言:
在Linux的网络编程中,同步IO(synchronous IO)、异步IO(asynchronous IO)、阻塞IO(blocking IO)、非阻塞IO(non-blocking IO)究竟是什么?它们之间又有什么联系和区别? 本文是我对这个问题的答案整理的笔记,参考《UNIX网络编程.卷1》
一、IO模型
在《UNIX网络编程.卷1》第6.2节介绍了五种IO模型,分别是:
- 阻塞式IO(blocking IO)
- 非阻塞式IO(non-blocking IO)
- IO复用(IO multiplexing)
- 信号驱动式IO(signal driven IO)
- 异步IO(asynchronous IO)
通常一个 socket 上的读操作包含两个阶段:
- 等待数据准备好;
- 将数据从内核拷贝到进程中。
上述几种IO模型就是在这两个阶段上各有不同的情况。
1.1 阻塞式IO
默认情况下,Linux下的所有socket都是阻塞的。以 UDP 的recvfrom
调用为例:
当进程调用recvfrom
时,该函数直到①数据报到达且被复制到应用进程缓冲区;②或者发生错误(比如被信号中断)才返回。
所以,阻塞式IO的特点就是在I/O执行的两个阶段都被阻塞了——阻塞等待数据,阻塞拷贝数据。
1.2 非阻塞式IO
Linux下可以通过fcntl
将 socket 设置为非阻塞模式。
当对一个非阻塞 socket 执行读操作时,如果内核中的数据还没有准备好,那么它并不会阻塞用户进程,而是立刻返回一个EWOULDBLOCK
错误;如果内核中有数据准备好了,它会立即将数据拷贝到用户内存,并成功返回。
由于非阻塞I/O在没有数据时会立即返回,故用户进程通常需要循环调用recvfrom
,不断地主动询问内核数据是否ready。
所以,非阻塞式IO的特点是在I/O执行的第一个阶段不会阻塞线程,但在第二阶段会阻塞。
1.3 IO复用
IO复用(IO multiplexing),也称事件驱动IO(event-driven IO),就是在单个线程里同时监控多个套接字,通过 select 或 poll 轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
可以看出,进程阻塞在select
调用上,等待有套接字变为可读;当有套接字可读以后,调用recvfrom
把数据报从内核复制到用户进程缓冲区,此时进程阻塞在IO执行的第二个阶段。
如上图整个用户进程其实是一直被阻塞的,但IO复用的优势在于可以等待多个描述符就绪。
所以,IO复用的特点是进行了两次系统调用,进程先是阻塞在 select/poll 上,再是阻塞在读操作的第二个阶段上。
1.4 信号驱动式IO
信号驱动式IO(signal-driven IO),就是让内核在描述符就绪时发送SIGIO
信号通知用户进程。
首先需要开启 socket 的信号驱动式IO功能,然后通过sigaction
系统调用注册SIGIO信号处理函数 —— 该系统调用会立即返回。当数据准备好时,内核会为该进程产生一个SIGIO信号,这时就可以在信号处理函数中调用 recvfrom 读取数据了。
所以,信号驱动式IO的特点就是在等待数据ready期间进程不被阻塞,当收到信号通知时再阻塞并拷贝数据。
1.5 异步IO
异步IO(asynchronous IO)其实用得很少,在Linux 2.5 版本的内核中首次出现,在 2.6 版本的内核中才成为标准特性。
用户进程在发起aio_read
操作后,该系统调用立即返回 —— 然后内核会自己等待数据ready,并自动将数据拷贝到用户内存。整个过程完成以后,内核会给用户进程发送一个信号,通知IO操作已完成。
异步IO与信号驱动式IO的主要区别是:信号驱动式IO是由内核通知我们何时启动一个IO操作,而异步IO是由内核通知我们IO操作何时完成。
所以,异步IO的特点是IO执行的两个阶段都由内核去完成,用户进程无需干预,也不会被阻塞。
1.6 五种IO模型的比较
可以看出,前4种模型的主要区别在于第一阶段,因为它们的第二阶段是一样的:都是阻塞于recvfrom
调用,将数据从内核拷贝到用户进程缓冲区。
二、阻塞vs非阻塞,同步vs异步
回到本文开头的那个问题:同步IO、异步IO、阻塞IO、非阻塞IO究竟是什么?它们之间又有什么联系和区别?
阻塞IO vs 非阻塞IO
上面介绍阻塞式IO模型、非阻塞式IO模型时已经说明了两者的区别:
- 阻塞I/O会一直阻塞用户进程直到操作完成
- 非阻塞I/O在内核的数据还没准备好的情况下会立即返回
同步IO vs 异步IO
POSIX是这样定义的:
- A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes. —— 同步IO操作导致进程阻塞,直到IO操作完成。
- An asynchronous I/O operation does not cause the requesting process to be blocked. —— 异步IO操作不导致进程阻塞。
上面定义中的I/O operation
是指真正的I/O系统调用,比如recvfrom
,所以阻塞式I/O模型、非阻塞式I/O模型、I/O复用模型、信号驱动式I/O模型都属于同步I/O。—— 只有异步I/O模型属于POSIX定义的异步I/O,因为在异步I/O模型中,用户进程是将整个I/O操作都交给内核来完成,内核完成后发信号通知,在此期间用户进程完全不用去理会。