linux基础——linux下五种IO模型小结(阻塞IO、非阻塞IO、IO复用、信号驱动式IO、异步IO)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a987073381/article/details/52201200
一、阻塞IO模型 (同步I/O)
阻塞IO是指进程进行IO操作的时候,因为数据没准备好或者缓冲区里没有空间而无法进行IO操作会进入睡眠,直到数据准备或者缓冲区有空间才回被唤醒的行为。阻塞IO是最通用的IO类型,所有套接字默认情况下都是阻塞的。

输入操作:read、readv、recv、recvfrom和recvmsg,调用这些输入函数之一,如果缓冲区没有数据可读,该进程会投入睡眠,直到有一些数据可达才被唤醒,唤醒之后把相应数据复制到接受缓冲区或者发送错误才返回。
输出操作:write、writev、send、 sendto 和 sendmsg,调用这些输出函数之一,如果发送缓冲区里面没有空间,进程也将进入睡眠,直到有空间为止才被唤醒,唤醒之后把相应数据写到发送缓冲区才会返回,返回值将是内核能够复制到该缓冲区中的字节数。

二、非阻塞IO模型 (同步I/O)
非阻塞IO使我们进行IO操作时,不会因为这些操作阻塞,如果这个操作不能完成,则调用后立刻出错返回。

把套接字设置成非阻塞的代码如下:
int set_nonblock(int fd)
{
        int old_option = fcntl(fd, F_GETFL);
        int new_option = old_option | O_NONBLOCK;
        fcntl(fd, F_SETFL, new_option);

        return old_option;
}

输入操作:如果输入操作不能被满足(对于TCP套接字即至少有一个字节的数据可读,对于UDP套接字即有一个完整的数据报可读),内核中没有给返回相应的数据,那么,调用将立即返回一个-1错误。需要数据的话,我们必须持续的调用这个函数(也就是所谓的循环接收这种循环接收并不是阻塞,从而对CPU造成极大的浪费,也就是忙等,想要等待一定的数据,但是这些数据并没有到来,并且还占用着CPU,不太经常使用这种IO模型)。从而将内核空间的值拷贝到用户空间。一旦拷贝完成了,输入函数就可以返回了,返回的值也就不是-1了。
输出操作:对于一个非阻塞的TCP套接字,如果其发送缓冲区中根本没有空间,输出函数调用将立即返回一个-1错误。如果其发送缓冲区中还有一些空间,返回值将是内核能够复制到该缓冲区中的字节数。

三、IO复用 (同步I/O)
IO复用是一种预先告知内核的能力,使得内核一旦发现指定的描述符集合中有一个或多个触发IO条件,它就会通知进程。这个能力叫做IO复用。
使用IO复用模型,我们可以调用select或者epoll,阻塞在这两个系统调用(select、epoll_wait)之上,而不是阻塞在真正的I/O系统调用调用上(需要事先把监听的套接字设置成非阻塞)。可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数。
注意:
当一个服务器 在处理多个客户时,绝对不能阻塞于单个客户相关的系统函数调用。否则导致服务器端程序被挂起,不能为其它客户提供服务。解决方法如下:
(1)使用非阻塞式I/O (2)对每个客户有单独的控制进程提供服务 (3)对每个I/O操作设置一个超时。

四、信号驱动式IO (同步I/O)
信号驱动式IO就是指进程预先告知内核,当某个描述符上发送事件时,内核使用信号通知相关进程。信号驱动式IO并没有实现真正的异步,因为通知到进程之后,依然是由进程来完成IO操作。

对于TCP套接字:信号驱动式IO不适合处理TCP套接字,因为信号产生的过与频繁,在TCP中,连接请求完成、断开连接发起、断开连接完成、数据到达、数据送走。。。都会产生SIGIO。但是我们真正只需要它在数据到达或者数据送走的时候才产生信号。
对于UDP套接字:SIGIO信号在数据报到达套接字以及套接字上发生异步错误才会发生。(UDP套接字推荐使用)。
对于一个套接字使用信号驱动式IO,要求进程执行以下三个步骤:
(1)建立SIGIO信号的信号处理函数。
(2)设置该套接字的属主,通常使用fcntl的F_SETOWN命令设置。
(3)开启该套接字的信号驱动式IO,通常使用fcntl的F_SETFL命令打开O_ASYNC标志完成。

五、异步IO
 当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者的输入输出操作。

调用aio_read函数(POSIX异步IO函数一aio_或者lio_开头),给内核传递描述符、缓冲区指针、缓冲区大小(和read相同的三个参数)和文件偏移(与lseek类似),告诉内核当整个操作完成时,如何通知我们。该系统调用立刻返回,而且在等待IO完成期间,我们的进程不被阻塞。

信号驱动式IO和异步IO的区别:
信号驱动式IO是进程事先建立SIGIO的信号处理程序(用sigaction设置IO信号的处理方式),有如果有数据到来,内核会给进程发一个信号,通知进程,进程捕获到这个信号就会去执行处理函数部分,一般在这个函数中执行IO操作(信号发生在IO之前,IO由进程完成)。
异步IO是有进程请求异步读操作,会把套接字描述符,缓冲区指针、缓冲区大小和文件偏移一起发给内核,如果有数据到来,内核会把数据拷到相应的位置,然后给进程发一个信号,如果数据没有来,也必须等数据来了,内核把数据拷完之后,才给进程发一个信号,由内核通知我们IO操作何时完成(信号发生在IO之后,IO由内核完成)

阻塞与非阻塞的区别:
阻塞和非阻塞关注的是进程在等待调用结果(消息、返回值)时的状态。阻塞是指调用结果返回之前,当前进程会被挂起。调用进程只有在得到结果才会返回。非阻塞调用指不能立刻得到结果,该调用不会阻塞当前进程。

同步与异步的区别:
同步与异步关注的是进程之间的协作方式,同步是A进程必须得到B进程通知才能去执行某件事(A执行),异步是指A进程通知B进程去执行然后立刻得到返回,然后就可以去做自己的事,B完成之后会给A发一个通知(B执行)。

参考:
https://www.zhihu.com/question/19732473
http://blog.163.com/xychenbaihu@yeah/blog/static/13222965520112163171778/
http://blog.csdn.net/jay900323/article/details/18141217/
《UNIX网络编程》
《UNIX环境高级编程》
阅读更多

没有更多推荐了,返回首页