您的位置:首页 > 其它

IO五种模型

2016-03-24 10:35 323 查看
首先我们看看IO的五种模型:



同步和异步仅仅是关于所关注的消息如何通知的机制,而不是处理消息的机制.
也就是说:
同步的情况下,是由处理消息者自己去等待消息是否被触发
异步的情况下是由触发机制来通知处理消息者

阻塞,非阻塞:进程/线程要访问的数据是否就绪,进程/线程是否需要等待;

同步,异步:访问数据的方式,同步需要主动读写数据,在读写数据的过程中还是会阻塞;异步只需要I/O操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写。

一般来说,程序进行输入操作有两步:

1.等待有数据可以读

2.将数据从系统内核中拷贝到程序的数据区。

对于socket编程来说:

第一步: 一般来说是等待数据从网络上传到本地。当数据包到达的时候,数据将会从网络层拷贝到内核的缓存中;

第二步: 是从内核中把数据拷贝到程序的数据区中。
-----阻塞:



在linux中,默认情况下所有的socket都是阻塞的,一个典型的读操作流程大概是这样:
当用户进程调用了recvfrom这个系统调用,内核就开始了IO的第一个阶段:准备数据。对于network
io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候内核就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当内核一直等到数据准备好了,它就会将数据从内核中拷贝到用户内存,然后内核返回结果,用户进程才解除block的状态,重新运行起来。
所以,blocking IO的特点就是在IO执行的两个阶段都被block了。

-----非阻塞:常用于管道(非阻塞模式的使用并不普遍,因为非阻塞模式会浪费大量的CPU资源)



当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不听的测试是否一个文件描述符有数据可读(称做
polling(轮询))。应用程序不停的 polling 内核来检查是否 I/O操作已经就绪。这将是一个极浪费
CPU资源的操作。这种模式使用中不是很普遍。

linux下,可以通过设置socket使其变为非阻塞。当对一个非阻塞 socket执行读操作时,流程是这个样子:

当用户进程发出read操作时,如果内核中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲
,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户进程的系统调用,那么它马上就将数据拷贝到了用户内存(这段时间用户进程是阻塞的),然后返回。

所以,用户进程其实是需要不断的主动询问内核数据好了没有。对管道的操作,最好使用非阻塞方式!

-----多路复用:(针对批量IP操作时,使用I/O多路复用)



IO
多路复用这个词可能有点陌生,但是如果我说select,epoll,大概就都能明白了。有些地方也称这种IO方式为event driven IO。我们都知道,select/epoll的好处就在于单个应用进程就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。

当用户进程调用了select,那么整个进程会被block,而同时,内核会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

这个图和阻塞IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个系统调用 (select 和 recvfrom),而阻塞IO只调用了一个系统调用
(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)

在IO 多路复用中,实际中,对于每一个socket,一般都设置成为非阻塞,但是,如上图所示,整个用户的process其实是一直被block的。只不过进程是被select这个函数block,而不是被socket
IO给block。

IO 多路技术一般在下面这些情况中被使用:

1、当一个客户端需要同时处理多个文件描述符的输入输出操作的时候(一般来说是标准的输入输出和网络套接字),I/O
多路复用技术将会有机会得到使用。

2、当程序需要同时进行多个套接字的操作的时候。

3、如果一个 TCP 服务器程序同时处理正在侦听网络连接的套接字和已经连接好的套接字。

4、如果一个服务器程序同时使用
TCP 和 UDP 协议。

5、如果一个服务器同时使用多种服务并且每种服务可能使用不同的协议(比如 inetd就是这样的)

-----信号驱动IO模型:



所谓信号驱动,就是利用信号机制,安装信号SIGIO的处理函数(进行IO相关操作),通过监控文件描述符,当其就绪时,通知目标进程进行IO操作(signal handler)。

为了在一个套接字上使用信号驱动 I/O 操作,下面这三步是所必须的。

(1)一个和 SIGIO信号的处理函数必须设定。

(2)套接字的拥有者必须被设定。一般来说是使用 fcntl 函数的 F_SETOWN 参数来

进行设定拥有者。

(3)套接字必须被允许使用异步 I/O。一般是通过调用 fcntl 函数的 F_SETFL 命令,O_ASYNC为参数来实现。

虽然设定套接字为异步 I/O 非常简单,但是使用起来困难的部分是怎样在程序中断定产生 SIGIO信号发送给套接字属主的时候,程序处在什么状态。

1.UDP 套接字的 SIGIO 信号 (比较简单)

在 UDP 协议上使用SIGIO 非常简单.这个信号将会在这个时候产生:

1、套接字收到了一个数据报的数据包。

2、套接字发生了异步错误。

当我们在使用 UDP 套接字异步 I/O 的时候,我们使用 recvfrom()函数来读取数据报数据或是异步 I/O 错误信息。

2.TCP 套接字的 SIGIO 信号 (因为复杂,实际一般用的是socket的异步IO)

不幸的是,SIGIO 几乎对 TCP 套接字而言没有什么作用。因为对于一个 TCP 套接字来说,SIGIO 信号发生的几率太高了,所以 SIGIO 信号并不能告诉我们究竟发生了什么事情。

在 TCP 连接中, SIGIO 信号将会在这个时候产生:

l 在一个监听某个端口的套接字上成功的建立了一个新连接。

l 一个断线的请求被成功的初始化。

l 一个断线的请求成功的结束。

l 套接字的某一个通道(发送通道或是接收通道)被关闭。

l 套接字接收到新数据。

l 套接字将数据发送出去。

l 发生了一个异步 I/O 的错误。

一个对信号驱动 I/O 比较实用的方面是 NTP(网络时间协议 Network Time Protocol)服务器,它使用 UDP。这个服务器的主循环用来接收从客户端发送过来的数据报数据包,然后再发送请求。对于这个服务器来说,记录下收到每一个数据包的具体时间是很重要的。

因为那将是返回给客户端的值,客户端要使用这个数据来计算数据报在网络上来回所花费的时间。

-----异步: (比如写操作,只需用写,不一定写入磁盘(这就是异步I/O)的好处。异步IO的好处效率高)



用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronous
read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,内核会给用户进程发送一个signal,告诉它read操作完成了。

异步 I/O 和 信号驱动I/O的区别是:

1、信号驱动 I/O 模式下,内核在操作可以被操作的时候通知给我们的应用程序发送SIGIO 消息。

2、异步 I/O 模式下,内核在所有的操作都已经被内核操作结束之后才会通知我们的应用程序。

现在说说同步和异步的区别:



两者的区别就在于同步IO做”IO操作”的时候会将process阻塞。
按照这个定义,之前所述的阻塞IO,非阻塞IO,IO多路复用、信号驱动都属于同步IO。有人可能会说,非阻塞
IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO操作”是指真实的IO操作,就是例子中的recvfrom这个system
call。非阻塞 IO在执行recvfrom这个system call的时候,如果内核的数据没有准备好,这时候不会block进程。但是,当内核中数据准备好的时候,recvfrom会将数据从内核拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。而异步IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到内核发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。

经过上面的介绍,会发现非阻塞IO和异步IO的区别还是很明显的。在非阻塞IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而异步IO则完全不同。它就像是用户进程将整个IO操作交给了他人(内核)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。

有的地方说IO多路复用、信号驱动两种IO模式属于异步IO,原因是IO多路复用进程是被select这个函数block,而不是被socket
IO给block,但是仔细看看,IO多路复用、信号驱动在执行IO操作的时候(定义中所指的”IO操作”是指真实的IO操作,就是例子中的recvfrom这个system
call系统调用)请求的时候,都是被阻塞的,因此IO多路复用、信号驱动属于同步IO。

其实异步操作是可以被阻塞住的,只不过通常不是在处理消息时阻塞,而是在等待消息被触发时被阻塞.比如select函数,假如传入的最后一个timeout参数为NULL,那么如果所关注的事件没有一个被触发,程序就会一直阻塞在这个select调用处.而如果使用异步非阻塞的情况,比如aio_*组的操作,当我发起一个aio_read操作时,函数会马上返回不会被阻塞,当所关注的事件被触发时会调用之前注册的回调函数进行处理.如果在银行等待办理业务的人采用的是异步的方式去等待消息被触发,也就是领了一张小纸条,假如在这段时间里他不能离开银行做其它的事情,那么很显然,这个人被阻塞在了这个等待的操作上面;但是呢,这个人突然发觉自己烟瘾犯了,需要出去抽根烟,于是他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下(注册一个回调函数),那么他就没有被阻塞在这个等待的操作上面,自然这个就是异步+非阻塞的方式了.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: