阻塞、非阻塞、同步、异步 这四个概念很多人不能完全区别开,尤其是他们相互组合时同步阻塞,异步阻塞……
转自:http://blog.chinaunix.net/uid-26606708-id-3175228.html
前4种模型的主要区别在于第一阶段,因为它们的第二阶段都是一样的:在数据从内核缓冲区拷贝到进程缓冲区期间,进程阻塞与recvfrom这个系统调用中。
参考 Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking”。
Stevens一共提出了五种 IO Model:
- blocking IO
- nonblocking IO
- IO multiplexing (select and poll)
- signal driven IO (SIGIO)
- asynchronous IO (the POSIX aio_functions)
先说一下IO发生时所涉及的对象和步骤。
一个输入操作通常包括下面两个阶段:
- 等待数据准备好 (Waiting for the data to be ready)。对于一个套接口上的输入操作,通常涉及等待数据从网络到达,到达后它被拷贝到内核的某个缓冲区。
- 将数据从内核缓存区拷贝到进程缓冲区中 (Copying the data from the kernel to the process)
记住这两个阶段很重要,因为以下要讨论的五种IO Model的区别就是在两个阶段上各有不同的情况。
Blocking I/O Model(阻塞I/O)
默认情况下所有的套接口都是blocking。
进程调用recvfrom,其系统调用直到数据报到达(第一阶段)且被拷贝到应用进程的缓冲区中(第二阶段)或者发生错误(最常见的错误是系统调用被信号中断)才返回。进程在从调用recvfrom开始到它返回的整个过程是被阻塞的。 recvfrom成功返回后,应用进程开始处理数据报。
Nonblocking I/O Model(非阻塞I/O)
前三次调用recvfrom时数据还没准备好,这是内核立即返回一个EWOULDBLOCK错误。第四次调用recvfrom时数据已准备好,它被拷贝到应用进程缓冲区,recvfrom接着成功返回,然后应用进程开始处理数据报。
这里最关键的一个操作就是轮询(polling)。应用进程持续轮询内核,以查看数据是否就绪。这样做往往会耗费大量的CPU时间,这种模型通常会在专门提供某种功能的系统才有。
I/O Multiplexing Model(I/O复用模型)
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个 socket中的数据准备好了,select就会返回套接字可读这个条件,我们调用recvfrom把所读数据报拷贝到应用程序进程缓冲区。
和blocking IO的图比较,I/O复用并没有显示出什么优势。事实上,可能稍有劣势。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call。但是,用select的优势在于它可以同时处理多个connection。(peakflys注: 常用的服务器网络模型即是 多路复用IO调度+非阻塞fd+线程池)
Signal-Driven I/O Model(信号驱动I/O模型)
我们首先开启套接口的信号驱动I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回,我们的进程这是并没有被阻塞,而是继续执行。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。我们随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它来读取数据报。无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达(第一阶段)期间,进程可以继续执行,不被阻塞。
Asynchronous I/O Model(异步I/O模型)
进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
这个模型工作机制是:告诉内核启动某个操作,并让内核在整个操作(包括第二阶段,即将数据从内核拷贝到进程缓冲区中)完成后通知我们。
这种模型和前一种模型区别在于:信号驱动I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。
五种I/O模型介绍完了,下面来说说blocking和non-blocking的区别在哪,synchronous IO和asynchronous IO的区别在哪。
blocking I/Ovs non-blocking I/O :调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IO在kernel还准备数据的情况下会立刻返回。
synchronous I/O vs asynchronous I/O:
先看看这两个定义:
A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
An asynchronous I/O operation does not cause the requesting process to be blocked;
两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述前四种模型blocking I/O,non-blocking I/O,IO multiplexing,signal driven IO都属于synchronous IO。有人可能会说,non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个system call。non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从 kernel拷贝到用户内存中,这个时候(第二阶段)进程是被block了,在这段时间内,进程是被block的。而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。
各个IO Model的比较如图所示:
前4种模型的主要区别在于第一阶段,因为它们的第二阶段都是一样的:在数据从内核缓冲区拷贝到进程缓冲区期间,进程阻塞与recvfrom这个系统调用中。
(peakflys注:异步调用需要指定回调函数或者信号处理函数,所以和其他几个模型的函数调用有明显不同,linux下的异步调用库有两套,一套是linux内核提供的,一套是glibc提供的,前者执行异步调用时是进入系统内核,统一有IO调度器调度,后者是在调用进程里生成一个新的线程执行异步操作,同fd的异步操作在同一个线程执行)