Linux--IO模式

1. 什么是IO

IO (Input/Output,输入/输出)即数据的读取(接收)或写入(发送)操作。

通常用户进程中的一个完整IO分为两阶段:
	1.用户进程空间<-->内核空间
	2.内核空间<-->设备空间(磁盘、网络等)
	
IO有内存IO、网络IO和磁盘IO三种,通常我们说的IO指的是后两者。

LINUX中进程无法直接操作I/O设备,其必须通过系统调用请求kernel来协助完成I/O动作;
内核会为每个I/O设备维护一个缓冲区。

对于一个输入操作来说,进程IO系统调用后,内核会先看缓冲区中有没有相应的缓存数据,没有的话再到设备中读取,
因为设备IO一般速度较慢,需要等待;内核缓冲区有数据则直接复制到进程空间。

所以,对于一个网络输入操作通常包括两个不同阶段:
	1.等待网络数据到达网卡 → 读取到内核缓冲区,数据准备好;
	2.从内核缓冲区复制数据到进程空间。

image

2. 五种IO模式

《UNIX网络编程》说得很清楚,5种IO模型分别是阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动的IO模型、异步IO模型;前4种为同步IO操作,只有异步IO模型是异步IO操作。

2.1 阻塞IO模型

image

调用方式:
	进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程。操作成功则进程获取到数据。
	阻塞IO操作只能对单个文件描述符进行操作,详见read或write。

阻塞IO意味着当我们发起一次IO操作后一直等待成功或失败之后才返回,在这期间程序不能做其它的事情。


典型应用:阻塞socket(read或write)、Java BIO;

特点:
	进程阻塞挂起不消耗CPU资源,及时响应每个操作;
	实现难度低、开发应用较容易;
	适用并发量小的网络应用开发;
	不适用并发量大的应用:因为一个请求IO会阻塞进程,所以,得为每请求分配一个处理进程(线程)以及时响应,系统开销大。

2.2 非阻塞IO模型

image

调用过程:
	进程发起IO系统调用后,如果内核缓冲区没有数据,需要到IO设备中读取,进程返回一个错误而不会被阻塞;
	进程发起IO系统调用后,如果内核缓冲区有数据,内核就会把数据返回进程。

与阻塞IO比较:
	对于阻塞IO模型来说,内核数据没准备好需要进程阻塞的时候,就返回一个错误,以使得进程不被阻塞。
	
	
典型应用:
	socket是非阻塞的方式(设置为NONBLOCK,一般利用for循环设计,会浪费cpu)

特点:
	进程轮询(重复)调用,消耗CPU的资源;
	实现难度低、开发应用相对阻塞IO模式较难;
	适用并发量较小、且不需要及时响应的网络应用开发;

2.3 IO复用模型(非常重要)

image
包括了三种方案select、poll、epoll,三者功能是类似的,但具体细节各有不同。

//以 select 为例
调用过程:
	多个的进程的IO可以注册到一个复用器(select)上,然后用一个进程调用该select, select会监听所有注册进来的IO;
	如果select没有监听的IO在内核缓冲区都没有可读数据,select调用进程会被阻塞;而当任一IO在内核缓冲区中有可数据时,select调用就会返回;
	而后select调用进程可以自己或通知另外的进程(注册进程)来再次发起读取IO,读取内核中准备好的数据。
	可以看到,多个进程注册IO后,只有另一个select调用进程被阻塞。
典型应用:
	select、poll、epoll三种方案,nginx都可以选择使用这三个方案;Java NIO;

特点:
	1.专一进程解决多个进程IO的阻塞问题,性能好;Reactor模式;
	2.实现、开发应用难度较大;
	3.适用高并发服务应用开发:一个进程(线程)响应多个请求;

Select:注册IO、阻塞扫描,监听的IO最大连接数不能多于FD_SIZE;
Poll:	原理和Select相似,没有数量限制,但IO数量大扫描线性性能下降;
Epoll :事件驱动不阻塞,mmap实现内核与用户空间的消息传递,数量很大,Linux2.6后内核支持;

2.4 信号驱动IO模型

image

调用过程:
	当进程发起一个IO操作,会向内核注册一个信号处理函数,然后进程返回不阻塞;
	当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用IO读取数据。

特点:回调机制,实现、开发应用难度大;

信号驱动IO在网络编程的时候通常很少用到问题:
	因为在网络环境中,和socket相关的读写事件太多了,比如下面的事件都会导致SIGIO信号的产生:
	1.TCP连接建立
	2.一方断开TCP连接请求
	3.断开TCP连接请求完成
	4.TCP连接半关闭
	5.数据到达TCP socket
	6.数据已经发送出去(如:写buffer有空余空间)
上面所有的这些都会产生SIGIO信号,但我们没办法在SIGIO对应的信号处理函数中区分上述不同的事件,SIGIO只应该在IO事件单一情况下使用,
比如说用来监听端口的socket,因为只有客户端发起新连接的时候才会产生SIGIO信号。

2.5 异步IO模型

image

与信号IO模型比较:
	异步IO和信号驱动IO差不多,但它比信号驱动IO可以多做一步:
		相比信号驱动IO需要在程序中完成数据从用户态到内核态(或反方向)的拷贝,
		异步IO可以把拷贝这一步也帮我们完成之后才通知应用程序。
		我们使用 aio_read 来读,aio_write 写。

调用过程:
	当进程发起一个IO操作,进程返回(不阻塞),但也不能返回果结;
	内核把整个IO处理完后,会通知进程结果。如果IO操作成功则进程直接获取到数据。

典型应用:
	JAVA7 AIO、高性能服务器应用

特点:
	1.不阻塞,数据一步到位;Proactor模式;
	2.需要操作系统的底层支持,LINUX 2.5 版本内核首现,2.6 版本产品的内核标准特性;
	3.实现、开发应用难度大;
	4.非常适合高性能高并发应用

3. IO模型比较

image

3.1 阻塞IO调用和非阻塞IO调用、阻塞IO模型和非阻塞IO模型

注意这里的阻塞IO调用和非阻塞IO调用不是指阻塞IO模型和非阻塞IO模型。

阻塞IO调用:
	`在用户进程(线程)中调用执行的时候,进程会等待该IO操作,而使得其他操作无法执行`。
非阻塞IO调用:
	`在用户进程中调用执行的时候,无论成功与否,该IO操作会立即返回`,之后进程可以进行其他操作(当然如果是读取到数据,一般就接着进行数据处理)。

这个直接理解就好,进程(线程)IO调用会不会阻塞进程自己。所以这里两个概念是`相对调用进程本身状态来讲的`。
从上面对比图片来说,
	阻塞IO模型是一个阻塞IO调用,
	而非阻塞IO模型是多个非阻塞IO调用+一个阻塞IO调用,因为多个IO检查会立即返回错误,不会阻塞进程。

非阻塞IO模型对于阻塞IO模型来说区别就是:
	非阻塞IO模型内核数据没准备好需要进程阻塞的时候,就返回一个错误,以使得进程不被阻塞。
	实际上还是需要类似for循环,直到返回正常数据,还是会损耗资源。

4.同步IO和异步IO

同步IO:
	导致请求进程阻塞,直到I/O操作完成。
异步IO:
	不导致请求进程阻塞。
上面两个定义是《UNIX网络编程 卷1:套接字联网API》给出的。
扩展一下,先说说同步和异步,同步和异步关注的是双方的消息通信机制:
	同步:
		双方的动作是经过双方协调的,步调一致的。
	异步:
		双方并不需要协调,都可以随意进行各自的操作。
		
	这里我们的双方是指,用户进程和IO设备;明确同步和异步之后,我们在上面网络输入操作例子的基础上,进行扩展定义:
	同步IO:
		用户进程发出IO调用,去获取IO设备数据,双方的数据要经过内核缓冲区同步,完全准备好后,再复制返回到用户进程。
		而复制返回到用户进程会导致请求进程阻塞,直到I/O操作完成。
	异步IO:
		用户进程发出IO调用,去获取IO设备数据,并不需要同步,内核直接复制到进程,整个过程不导致请求进程阻塞。

所以,阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动的IO模型者为同步IO模型,
只有异步IO模型是异步IO。
posted @ 2023-03-14 14:35  Panor  阅读(106)  评论(0)    收藏  举报