五种IO模型详解( 四 )

很明显 , 使用信号驱动IO模型时 , 进程对该描述符的读取是被动的 , 进程不会主动在描述符就绪前执行读取操作 。
其实信号驱动IO模型就像是小姐姐在闲逛 , 小姐姐本没有想过要买什么东西 , 但如果发现有合适的 , 也会去买下来 , 在逛了一段时间后 , 一件超短裙闪现到小姐姐的视线 , 小姐姐很喜欢这个款式 , 于是立即决定买下来 。
这里可以做出大胆的推测 , 并非所有文件描述符类型都能使用信号驱动的IO模型 。 如果某文件描述符想要开启信号驱动IO , 要求有某个另一端会主动向该描述符发送数据 , 比如管道、套接字、终端等都符合这种要求 。 显然 , 普通文件系统上的文件IO是无法使用信号驱动IO的 。
回到信号驱动IO模型 , 由于进程没有主动执行IO操作 , 所以不会阻塞 , 当数据就绪后 , 进程收到内核发送的SIGIO信号 , 进程会去执行SIGIO的信号处理程序 , 当进程执行read()时 , 由于数据已经就绪 , 所以可以直接将数据从kernel buffer复制到app buffer , read()过程中 , 进程将被阻塞 。
注意:sigio信号只是通知了数据就绪 , 但并不知道有多少数据已经就绪 。
如图:
五种IO模型详解文章插图
2.5 Asynchronous I/O模型即异步IO模型 。
异步IO来自于POSIX AIO规范 , 它专门提供了能异步IO的读写类函数 , 如aio_read() , aio_write()等 。
使用异步IO函数时 , 要求指定IO完成时或IO出现错误时的通知方式 , 通知方式主要分两类:
发送指定的信号来通知
在另一个线程中执行指定的回调函数
为了帮助理解 , 这里假设aio_read()的语法如下(真实的语法要复杂的多):
aio_read(x,y,z,notify_mode,notify_value)其中nofity_mode允许的值有两种:
当notify_mode参数的值为SIGEV_SIGNAL时 , notify_value参数的值为一个信号
当notify_mode参数的值为SIGEV_THREAD , notify_value参数的值为一个函数 , 这个函数称为回调函数
当使用异步IO函数时 , 进程不会因为要执行IO操作而阻塞 , 而是立即返回 。
例如 , 当进程执行异步IO函数aio_read()时 , 它会请求内核执行具体的IO操作 , 当数据已经就绪且从kernel buffer拷贝到app buffer后 , 内核认为IO操作已经完成 , 于是内核会根据调用异步IO函数时指定的通知方式来执行对应的操作:
如果通知模式是信号通知方式(SIGEV_SIGNAL) , 则在IO完成时 , 内核会向进程发送notify_value指定的信号
如果通知模式是信号回调方式(SIGEV_THREAD) , 则在IO完成时 , 内核会在一个独立的线程中执行notify_value指定的回调函数
回顾一下信号驱动IO , 信号驱动IO要求有另一端主动向文件描述符写入数据 , 所以它支持像socket、pipe、terminal这类文件描述符 , 但不支持普通文件IO的文件描述符 。
而异步IO则没有这个限制 , 异步IO操作借助的是那些具有神力的异步函数 , 只要文件描述符能读写 , 就能使用异步IO函数来实现异步IO 。
所以 , 异步IO在整个过程中都不会被阻塞 。 如图:
五种IO模型详解文章插图
看上去异步很好 , 但是注意 , 在复制kernel buffer数据到app buffer中时是需要CPU参与的 , 这意味着不受阻的进程会和异步调用函数争用CPU 。 以httpd为例 , 如果并发量比较大 , httpd接入的连接数可能就越多 , CPU争用情况就越严重 , 异步函数返回成功信号的速度就越慢 。 如果不能很好地处理这个问题 , 异步IO模型也不一定就好 。
2.6 同步IO和异步IO、阻塞和非阻塞的区分阻塞和非阻塞 , 体现在当前进程是否可执行 , 是否能获取到CPU 。
当阻塞和非阻塞的概念体现在IO模型上:
阻塞IO:从开始发起IO操作开始就阻塞 , 直到IO完成才返回 , 所以进程会立即进入睡眠态
非阻塞IO:发起IO操作时 , 如果当前数据已就绪 , 则切换到内核态由内核完成数据拷贝(从kernel buffer拷贝到app buffer) , 此时进程被阻塞 , 因为它的CPU已经被内核抢走了 。 如果发起IO操作时数据未就绪 , 则立即返回而不阻塞 , 即进程继续享有CPU , 可以继续任务 。 但进程不知道数据何时就绪 , 所以通常会采用轮循代码(比如while循环)不断判断数据是否就绪 , 当数据最终就绪后 , 切换到内核态 , 进程仍然被阻塞
同步和异步 , 考虑的是两边数据是否同步(比如kernel buffer和app buffer之间数据是否同步) 。 同步和异步的区别体现在两边数据尚未完成同步时的行为: