文章还会涉及到同步I/O、异步I/O、阻塞I/O和非阻塞I/O首先我们需要了解以下几个概念:Linux用户态和内核态在目前的操作系统中,CPU通常工作在两种不同的模式:内核态在这种模式下,程序代码可以完全不受限制地访问底层硬件,执行任意CPU指令,访问任意内存地址。内核模式通常保留给最低级别的、受信任的系统功能。一个程序在内核模式下崩溃是灾难性的,它甚至可以拖垮整个PC。用户模式??在用户模式下,程序代码不能直接访问硬件和内存。在用户模式下执行的代码必须委托系统功能来访问硬件和内存。由于这种隔离机制的保护,用户态的程序崩溃通常是可以恢复的。PC中的大多数程序也是在用户模式下执行的。进程切换是指操作系统进程调度的切换,从一个进程到另一个进程。切换进程需要保存当前进程的所有状态,包括寄存器状态、关联的内核状态、虚拟内存配置等。它会经过以下步骤:保存处理器上下文,包括程序计数器和其他寄存器,更新进程控制块(PCB)将进程的PCB移动到合适的队列,比如就绪队列,事件阻塞队列选择其他进程,更新他的PCB来更新内存数据结构,恢复PCB上下文。阻塞的进程通常是在等待一个事件,例如信号释放或消息到达。在多任务系统中,被阻塞的进程会通过系统调用通知调度器它处于等待状态,以便将其从定时队列中移除。如果进程在wait状态下继续占用CPU,这就称为busy-waiting(空等待?)。显然这是不合理的,因为它浪费了CPU时钟周期,而这些时钟周期可以被其他进程使用。所以当一个进程进入阻塞状态时,不应该继续占用CPU资源。BufferedI/O当我们写入数据(到文件系统)时,I/O系统会将数据累积到一个中间缓冲区,当缓冲区累积足够的数据(或调用flush())时,数据将被发送到文件系统,减少文件系统访问次数。因为访问文件系统(磁盘)通常是非常昂贵的(相对于内存之间的复制),缓冲I/O可以有效提高性能,尤其是那种多次小数据写入操作。对于大数据写入操作,非缓冲I/O会更好,因为缓冲I/O不会显着减少(对文件系统)系统调用,而是引入额外的内存复制工作,这些数据复制操作带来更高的CPU和内存高架。文件描述符(FD)在Unix及其派生操作系统中,文件描述符(FD)是一个抽象的指标(原文:indicator,handle,大部分文章翻译成句柄),用于访问文件或其他I/OO资源,如管道、套接字等。FD是POSIX编程接口的一部分,是一个非负的索引值。许多底层程序将使用FDI/O模型。当一个读操作发生时,会经历以下两个阶段:数据准备阶段——比如等待网络数据到达,当数据包到达时,将其拷贝到内核缓冲区中数据传输阶段——从内核中拷贝数据kerneltotheuserprocess由于这两个阶段的存在,Linux提供了以下5种I/O模型BlockingI/OModel——BlockingI/OBlockingI/O是最常见的I/O模式。默认情况下,所有套接字都是阻塞的。这里我们以UDP协议和recvfrom系统调用为例。在上图中,进程调用recvfrom,系统函数在数据报到达并已复制到应用程序缓冲区时,或发生错误时(最常见的错误是被信号中断)返回。我们认为进程在从调用recvfrom到它返回的整个期间内被阻塞。当recvfrom成功返回时,应用程序将处理数据报。NonblockingI/OModel——非阻塞I/O如果一个(数据准备阶段)I/O调用没有完成,内核在第一次调用recvfrom并且没有数据到达,所以内核在第四次调用recvfrom时立即返回错误标志EWOULDBLOCK,数据报已经到达。复制到应用程序缓冲区后,recvfrom成功返回,然后程序将处理数据。像这样在非阻塞FD上循环调用recvfrom的程序称为轮询。这通常是对CPU周期的浪费,但偶尔会使用这种模型,例如当一个系统只专注于一个功能时。I/OMultiplexingModel——I/O多路复用在LinuxI/O多路复用模型中,我们会阻塞在select、poll、epoll等系统函数中,而不是阻塞在真正的I/O调用中。在上图中,我们阻塞在select()函数上,等待读取套接字数据。select()的返回表明socket数据是可读的,然后我们调用recvfrom将数据复制到应用程序缓冲区。缺点:这里我们使用了两个系统调用(select和recvfrom),而阻塞I/O只使用了一次recvfrom优点:我们可以监听多个fd准备就绪。I/O多路复用模型与阻塞I/O模型非常相似。阻塞I/O使用多线程(每个线程负责一个FD),每个线程可以自由调用(阻塞)系统函数recvfrom,而不是使用select监听多个FD。Signal-DrivenI/OModel-Signal-drivenI/O告诉内核,当某个FD就绪时,释放SIGIO信号通知应用程序。我们先让socket使用信号驱动的I/O方式,使用sigaction系统函数注册一个信号处理器(signalhandler),系统函数立即返回,是非阻塞的。当数据可读并且SIGIO信号被释放并被进程接收到时,我们可以执行以下操作之一。在signalhandler中调用recvfrom读取数据,然后通知主循环数据准备好了。信号处理程序通知主循环读取数据。该模型的优点是等待数据到来的阶段不会阻塞,主循环可以继续执行其他任务,等待信号处理程序的通知(数据可读或可处理)异步I/OModel——AsynchronousI/OAsynchronousI/O模型告诉内核执行I/O操作,等到整个I/O操作(包括数据准备阶段和数据传输阶段)完成后才通知我们。这种模式与信号驱动I/O非常相似,主要区别在于:在信号驱动I/O中,内核通知进程可以开始I/O操作(数据仍然需要从内核拷贝过来给进程),而在异步I/O中,内核通知我们I/O操作已经完成(数据已经在进程缓冲区中),我们调用系统函数aio_read,将以下信息传递给内核:FD,缓冲区指针,缓冲区大小文件偏移I/O执行完成aio_read的通知方法会立即返回,在等待I/O操作完成的整个阶段不会阻塞进程。I/O模型比较前四种模型的主要区别在第一阶段(数据准备阶段),第二阶段(数据传输阶段)相同:进程在数据传输阶段被阻塞(copyingfrom内核到应用程序缓冲区)。异步I/O内核负责这两个阶段,无需应用程序干预。同步I/O和异步I/OPOSIX定义如下同步I/O使请求I/O操作的进程阻塞,直到I/O操作完成异步I/O不会使请求I/O操作的进程阻塞block根据这些定义,前面的四种I/O模型(阻塞、非阻塞、I/O多路复用、信号驱动I/O)都是同步I/O,因为实际的I/O操作会阻塞进程(例如:signal-drivenI/OO在等待数据时是非阻塞的,但在传输数据时是阻塞的,这就是同步I/O),只有异步I/O模型符合定义。select,poll,epoll建设中
