用户空间及内核空间概念:
我们知道现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核,保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

Linux网络IO模型
我们都知道,为了OS的安全性等的考虑,进程是无法直接操作I/O设备的,其必须通过系统调用请求内核来协助完成I/O动作,而内核会为每个I/O设备维护一个buffer。 如下图所示:

请求流程:用户进程发起请求,内核接受到请求后,从I/O设备中获取数据到buffer中,再将buffer中的数据copy到用户进程的地址空间,该用户进程获取到数据后再响应客户端。
所以,我们知道IO其实分为两个阶段,第一个阶段:从I/O设备中获取数据到buffer中,第二个阶段:再将buffer中的数据copy到用户进程的地址空间。
阻塞I/O (Blocking I/O)
非阻塞I/O (Non-Blocking I/O)
I/O复用(I/O Multiplexing)
信号驱动的I/O (Signal Driven I/O)
异步I/O (Asynchrnous I/O)
阻塞I/O (Blocking I/O)
在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:

当用户进程调用了recvfrom (system call),之后用户进程就一直阻塞,等待数据准备好,也就是等待数据被copy到用户空间。而内核负责把IO设备的数据填入其buffer,然后再把buffer数据copy到用户空间,然后内核返回结果,用户进程才解除block的状态,重新运行起来。
Blocking IO的特点是:用户进程只使用了一次系统调用:recvfrom;用户进程在IO执行的两个阶段都是阻塞的。
非阻塞I/O (Non-Blocking I/O)
linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

当用户进程调用recvfrom(system call)时,系统不会阻塞用户进程,而是立刻返回一个ewouldblock错误,从用户进程角度讲 ,并不需要等待,而是马上就得到了一个结果。用户进程判断标志是ewouldblock时,就知道数据还没准备好,于是它就可以去做其他的事了。但是需要它主动地去再次发送recvfrom,一旦内核中的数据准备好了,那么内核进程马上就将数据拷贝到了用户内存,然后返回。
Non-Blocking IO的特点是:用户进程多次使用一个系统调用:recvfrom;在IO第一阶段中用户进程虽然是非阻塞的,但是需要去轮询发起recvfrom系统调用,看看是否已经准备好了某些操作,属于同步。
I/O复用(I/O Multiplexing)
IO multiplexing这个词可能有点陌生,但是如果我说select,epoll,大概就都能明白了。有些地方也称这种IO方式为event driven IO。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:

当用户进程调用了select,那么整个进程会被block,而同时,内核会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用recvfrom操作,将数据从内核拷贝到用户进程。 这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select和recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。) 在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
IO Multiplexing的特点是:用户进程使用了两种系统调用:select、recvfrom;在IO第一阶段利用select同时处理多个connection的优势,适用于连接数比较高的场景。
信号驱动的I/O (Signal Driven I/O)

很明显可以看出用户进程不是阻塞的。首先用户进程建立SIGIO信号处理程序,并通过系统调用sigaction执行一个信号处理函数,这时用户进程便可以做其他的事了,一旦数据准备好,系统便为该进程生成一个SIGIO信号,去通知它数据已经准备好了,于是用户进程便调用recvfrom把数据从内核拷贝出来,并返回结果。
Signal Driven IO的特点是:用户进程使用了两种系统调用:sigaction、recvfrom;在IO第一阶段利用SIGIO信号的方式做到了非阻塞,而且是一旦第一阶段的数据准备好了,内核还会去通知用户进程,做到了异步。
异步I/O
一般来说,这些函数通过告诉内核启动操作并在整个操作(包括内核的数据到缓冲区的副本)完成时通知我们。这个模型和前面的信号驱动I/O模型的主要区别是:在信号驱动的I/O中,内核告诉我们何时可以启动I/O操作,但是异步I/O时,内核告诉我们何时I/O操作完成。

当用户进程向内核发起某个操作后,会立刻得到返回,并把所有的任务都交给内核去完成(包括将数据从内核拷贝到用户自己的缓冲区),内核完成之后,只需返回一个信号告诉用户进程已经完成就可以了。
异步IO特点是:用户发起一次系统调用:aio_read;整个过程中用户进程为非阻塞异步的。
参考: