Linux系统为我们提供了五种可用的IO模型, 分别是阻塞式IO、非阻塞式IO、IO多路复用、信号驱动式IO和异步IO。这些模型的作用是让应用程序能够更好地管理和处理输入输出操作。
- 阻塞式IO: 简单易用,但效率不高。
- 非阻塞式IO:可以让应用程序在等待数据准备完成的过程中执行其他操作,但需要应用程序不停轮询内核缓冲区。
- IO多路复用:可以同时监控多个文件描述符,提高了引用程序对输入输出操作的管理能力。
- 信号驱动式IO:可以让应用程序在数据准备好时接收到通知,避免了轮询。
- 异步IO:可以让应用程序在发起异步读写操作后立即返回,并不等待操作完成。
每种模型都有其优缺点,具体使用哪种模型取决于应用场景。
Linux的I/O迷信可以应用到所有的文件读写。在Linux中,一切设备皆文件,我们可以使用系统调用中的I/O函数(如open()、close()、write()、read())对文件进行相应的操作。下面是以网络请求作为例子。
网络请求过程
当一个网络请求到达操作系统时,涉及到多个步骤,下面是一个典型的流程:
- 网络请求被网卡接受:当一个网络请求达操作系统是,它首先被网卡接受。网卡会将请求包转发给操作系统内核进行处理。
- 内核处理网络请求:操作系统内核会对网络请求进行处理,包括验证网络协议、校验和解析IP地址和端口号等。
- 内核将请求包交给合适的套接字:一旦内核验证了网络请求,它会将请求包交给操作系统内部的套接字。套件字是操作系统体哦那个的一种抽象接口,它允许用户进程通过网络进行通信。
- 用户进程获取请求:当请求包被套接字接受时,它回触发一个中断,操作系统内核会将请求包中的数据复制到用户进程的内存中。这个过程是通过系统调用实现的。
- 用户进程处理请求:一旦用户进程接收到请求,它可以进行相应的处理,比如解析请求包中的内容、处理请求、生成响应等。在处理请求的过程中,用户进程可以读取和写入套接字。
- 用户进程响应请求:一旦用户进程接收到请求,它会生成一个响应,并将响应写入套接字。响应包将由操作系统内核转发回客户端。
- 内核处理响应包:当响应包被套接字接收时,它回触发一个终端。操作系统内核会将响应包中你数据复制到内核缓冲区中,并开始处理响应。内核会根据响应包中的地址和端口号将响应转发给正确的客户端。
当一个网络请求到达操作系统时,它需要经过多个步骤才能到达用户进程,并最终生成响应。操作系统内核负责处理网络请求、管理套接字和转发响应,而童话进程负责处理请求和生成响应。

什么是网络监听
在上面中有一个步骤是网络请求到达网卡和内核将网卡的网络数据封装到对应的套接字中。用户线程并不知道网卡有数据到达操作系统的套接字(因为对应的套接字在系统内核空间中),所以需要进行网络监听,及向操作系统询问对应的套接字是否有数据到达,所以监听的过程实际上是向操作系统询问是否有树到达讷河空间的套接字。
什么是读取套接字
网络监听只是知道了有数据到达系统内核的套接字,但是对应的数据并不在用户空间中,所以,用户空间需要调用系统调用,将操作系统内核空间的套接字数据复制到用户空间,这个过程也需要花费时间。
I/O模型的优化
Linux的I/O模型是对用户进程从套接字读取和写入数据的步骤进行优化。在一个典型的网络应用中,数据通常通过套接字进行传输,而套接字是一个系统调用接口,它允许用户进程从操作系统内核中读取和写入数据。因此,套接字通信的效率直接影响到整个应用的性能。
不同的I/O模型提供给了不同的优化策略,比如阻塞I/O模型会阻塞用户进程直到数据传输完成,非阻塞I/O模型会通过轮询的方式实现数据传输,I/O复用模型会允许用户进程同时处理多个套接字,信号驱动I/O模型会通过信号通知进程数据传输完成。而异步I/O模型则通过回调函数的方式通知用户进程数据传输完成。
因此,Linux的I/O模型是对用户进程从套接字读取和写入数据的步骤进行优化,以提高应用程序的性能和可伸缩性。

阻塞I/O
阻塞I/O(Blocking I/O)是一种I/O模型,它会阻塞用户进程知道数据传输完成。在阻塞I/O模型中,在阻塞式IO模型中,如果内核缓冲区中没有数据可读,那么read函数会一直阻塞,知道有数据可读为止。在这段时间里,应用进程无法执行其他操作。(在调用accpet的过程会阻塞直至有客户端和服务端建立连接)
在阻塞I/O模型中,用户进程会通过系统调用进入内核,系统调用会一直阻塞用户进程,直到数据传输完成。在这个过程中,用户进程会一直占用CPU时间片,无法处理其他任务,因此阻塞I/O模型通过会导致应用程序的性能和可伸缩性受到限制。
下面是阻塞I/O模型的一些特点:
- 阻塞I/O模型是一种简单的I/O模型, 易于实现和使用。
- 当用户进程执行I/O操作时,如果数据没有准备好或无法立即发送, 那么用户进程会被阻塞直到数据准备好或发送完成。
- 阻塞I/O模型会导致应用程序的性能和可伸缩性受到限制,因为在树传输的过程中,用户进程会一直占用CPU时间片,无法处理其他任务。
- 阻塞I/O模型通常适用于单线程、同步、串行的应用程序,比如文件传输、打印机等。
虽然阻塞I/O模型在某些情况下非常实用, 但是在高并发、大规模应用中,阻塞I/O模型的性能和可伸缩性会变得非常糟糕,因此需要采用其他I/O模型,比如非阻塞I/O、I/O复用、信号驱动I/O和异步I/O等。

非阻塞I/O模型
非阻塞IO是指在进行输入输出操作时,程序不会阻塞等待结果返回,而是可以继续执行其他操作,等到结果返回时再去获取结果。非阻塞IO的accept和read都不需要阻塞。这意味着当你调用这些函数时,它们会立刻返回一个结果。这种方式通过与异步IO配合使用,以提高程序的性能和响应速度。
在传统的阻塞IO中, 程序会一直等待IO操作完成, 直到结果返回后才会继续执行。这种方式会导致程序长时间处于等待状态,浪费CPU资源。而非阻塞IO可以在IO操作的同时,执行其他操作,从而提高程序的效率和吞吐量。
实现非阻塞IO的方式一般是通过轮询或实现驱动的方式。轮询是指程序会不断地询问IO操作是否完成,如果没有完成就会继续执行其他操作,直到IO操作完成为止。时间驱动则是通过注册时间回调函数,但IO操作暗藏时自动调用该回调函数, 从而实现非阻塞IO操作。
在使用非阻塞IO时,需要注意一些问题。首先,由于程序并不会等待IO操作完成,因此需要采用一些方式来判断IO操作是否完成,例如轮询或事件回调。其次,在使用非阻塞IO时,需要考虑线程安全和竞争条件等问题, 以保证程序的正确性和稳定性。

I/O多路复用
I/O多路复用是一种通用的高效I/O处理机制,它允许一个进程可以同时监视多个文件描述符(套接字),并且可以在其中任何一个文件描述符上等待数据可读或剋额,从而实现并发I/O操作。
I/O多路复用机制通常由slect,poll和epoll等系统调用实现。这些系统调用允许用户进程注册一组文件描述符,并且在这些文件描述符上等待某些事件的发送,例如是否由数据可读或可写。当由数据可读或可写时,进程可以进行相应的操作,例如读取数据或数据写入文件。
在网络编程中,I/O多路复用通常哦那个与同时监听多个套接字,以实现高效的并发网络通信。通过使用I/O多路复用机制,服务器进程开业同时处理多个服务端连接请求,并且可以避免使用阻塞I/O导致的性能瓶颈或资源浪费问题。相比之下,非阻塞I/O需要对每个套接字分别进行操作,而I/O多路复用可以在一个线程中处理多个套接字,从而减少系统调用的次数和CPU使用率。
I/O多路复用是一种高效的I/O处理机制,可以用于实现并发网络通信和其他需要同时监视多个文件描述符的应用场景。

信号驱动式I/O
信号驱动式I/O是一种I/O多路复用的技术,可以用于异步处理I/O事件
在信号驱动式I/O中,进程使用系统调用sigaction()来注册一个信号处理函数,该函数会在I/O事件就绪时被内核调用。当进程调用sigaction()注册一个信号处理函数时,它需要指定一个描述符和一个事件,内核在检测到该描述符上发生指定事件时,会向进程发送指定信号,进程可以通过捕获该信号并执行相应操作来实现异步I/O
与其他I/O多路复用技术相比,信号驱动式I/O的主要优点是可以在处理多个描述符时避免阻塞进程,提高了程序的并发性能和响应能力。同时,信号驱动式I/O也避免了轮询机制的开销,从而减少了CPU的占用。
但是,信号驱动式I/O也存在一些缺点。首先,它对信号的处理需要一定的时间,因此它不适合高速I/O草。其次,由于信号的不可靠,因此在使用信号驱动式I/O时需要考虑到信号可能会丢失的情况。最好信号驱动式I/O在多个描述符之间切换时可能会存在竞争条件和死锁问题。

异步I/O模型
异步I/O是一种I/O处理方式,它允许一个进程在等待I/O操作完成时继续执行其他任务, 而不必阻塞等待I/O完成。异步I/O通过操作系统提供的通知机制,在I/O操作完成时通知进程,以实现异步处理。
异步I/O模型的实现通常涉及三个主要组件:
- 请求:异步I/O的请求由应用程序发起,通过包括描述符、缓冲区地址和数据长度等参数。在发起异步I/O请求后,应用程序可以继续执行其他任务。
- 内核:内核将异步I/O请求提交到I/O队列中,并将请求相关的数据据保存在内核中。当I/O操作完成时,内核会向应用程序发送通知,以便应用程序可以处理I/O操作的结果。
- 通知机制:通知机制用于将I/O完成的时间通知给应用程序,以便应用程序可以及时处理I/O操作的结果。通知机制通过采用信号、回调函数或者时间通知等方式。
异步I/O模型相比同步I/O模型的主要优势在于它可以让应用程序在等待I/O完成时继续执行其他任务, 提高了程序的并发性能和相应能力。但是,异步I/P模型的实现比同步I/O模型更加负责,需要使用操作系统提供的通知机制来处理I/O完成的时间,同时也需要考虑到异步I/O可能会引入的竞争条件和死锁问题。

异步 I/O 和非阻塞 I/O 都是一种非阻塞的 I/O 处理方式,它们都允许应用程序在等待 I/O 完成时继续执行其他任务。但是,它们之间有几个主要的区别:
实现方式:异步 I/O 由操作系统内核来处理 I/O 操作,并使用通知机制将 I/O 完成的事件通知给应用程序。而非阻塞 I/O 则需要应用程序在发起 I/O 请求后轮询检查 I/O 是否完成。
接口:异步 I/O 通常使用操作系统提供的系统调用,如 aio_read() 和 aio_write() 等,应用程序发起 I/O 请求后可以立即返回。而非阻塞 I/O 则需要使用相应的系统调用,并将文件描述符设置为非阻塞模式,这样在进行 I/O 操作时可以立即返回。
编程模型:异步 I/O 通常使用事件驱动编程模型,应用程序使用回调函数处理 I/O 完成的事件。而非阻塞 I/O 则需要应用程序自己实现轮询检查 I/O 完成的逻辑。
异步 I/O 与非阻塞 I/O 都是一种提高 I/O 性能的方式,但它们在实现方式、接口和编程模型上有所不同。异步 I/O 的实现更加复杂,需要操作系统内核来处理 I/O 操作和通知,但它可以让应用程序更加高效地处理 I/O 事件。而非阻塞 I/O 的实现相对简单,但需要应用程序自己实现轮询检查 I/O 完成的逻辑,较为繁琐。
总结
阻塞IO实现简单,但是性能不佳,非阻塞IO虽然不需要阻塞线程,但是他需要轮询操作低效。多路复用IO有三种实现,select和poll都是使用了轮询的方式,监听文件描述符不能太多。epoll的实现使用了红黑树,增删改文件描述符效率高,并且使用了事件触发机制,不需要进行轮询。信号IO依赖于信号机制,它对信号的处理需要一定的时间,因此它不适合高速 I/O 操作。异步 I/O 模型的实现比同步 I/O 模型更加复杂,需要使用操作系统提供的通知机制来处理 I/O 完成的事件,同时也需要考虑到异步 I/O 可能会引入的竞争条件和死锁问题。