五种IO模型

五种I/O模型

         Linux下基本的I/O模型有5种:阻塞IO模型、非阻塞IO模型、IO复用、信号驱动、异步IO。

1.阻塞IO(Blocking IO)模型:在这个模型中,应用程序为了执行read()操作,会调用相应的一个系统调用函数,将控制权交给内核,然后就进行等待,即阻塞。内核开始执行这个系统调用函数,执行完毕后回想应用程序返回响应,应用程序得到响应后就不再阻塞,并进行后面的工作。

         在调用recvfrom()函数或recv()函数时,发生在内核中等待数据和复制数据的过程。阻塞IO模型是最通用的IO模型,使用这种模型进行数据接收的时候,在数据没有到达之前程序会一直死等,比如recvfrom()函数,内核会一直阻塞该请求直到有数据到来才返回。

         当调用recv()函数时,系统首先检查是否有准备好的数据,如果没有数据准备好,那么系统就处于等待状态。当有数据准备好时,将数据从系统缓冲区复制到用户空间,然后该函数返回。在套结应用程序中,当调用recv()函数时,未必用户空间就已经存在数据,此时recv()函数就会处于等待状态。

 

2.非阻塞IO(NonBlocking IO)模型:此模型下,程序通过设置文件描述符的属性为O_NONBLOCK,IO操作可以立即返回,但是并不保证IO操作成功。也就是说,当程序设置了O_NONBLOCK之后,执行write操作,调用相应的系统回调函数,这个系统回调函数会从内核中立即返回,但是在这个返回的时间点,数据可能还没有被真正的写入到指定位置,即内核只是很快的返回了这个回调函数,但是这个函数要进行的操作并没有完成,内核只有很快的返回,程序才不会被IO操作阻塞。而对于程序来说,虽然这个IO操作很快返回了,但是它并不知道这个IO操作是否真的执行成功了,要判断IO操作是否执行成功,一般有两种策略:一是需要程序主动地循环去问内核,即同步非阻塞IO,二是采用IO通知机制,比如,IO多路复用(异步阻塞IO)或信号驱动IO(异步非阻塞IO)。

         当把套接字设置为非阻塞的IO,则对每次请求内核都不会阻塞,会立即返回;当没有数据的时候,会返回一个错误。如对于recvfrom()函数,前几次都没有数据返回,直到最后内核才向用户层的空间复制数据。

         非阻塞IO通过进程反复调用IO函数,多次调用,并立即返回,在数据复制过程中,进程是阻塞的。我们把一个socket 接口设置为非阻塞就是告诉内核,当所请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误。这样我们的I/O操作函数将不断地测试数据是否已经准备好,如果没有准备好则继续测试,直到数据准备好为止。在这个不断测试的过程中,会大量占用CPU时间。

NonBlockIO.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
void SetNonBlock(int fd)
{
    int flag;
    flag = fcntl(fd,F_GETFL,0);
    if(flag < 0)
    {
        perror("fcntl:");
        return;
    }
    fcntl(fd,F_SETFL,flag | O_NONBLOCK);
}
int main()
{
    //将标准输入设置为非阻塞
    SetNonBlock(0);
    //循环等待数据输入
    while(1)
    {
        char buffer[1024] = {0};
        ssize_t read_size = read(0,buffer,sizeof(buffer) - 1);
        if(read_size < 0)
        {
            perror("read:");
            sleep(1);
            continue;
        }
       printf("input:%s\n",buffer);

    }
    return 0;
}

         当不设置非阻塞时,程序会一直等待数据输入,不显示任何内容;当设置非阻塞时,程序每隔一秒输出Resource temporarily unavailable(资源临时不可达),直到有数据输入时,程序会输出此输入的内容,然后继续报read的错误,等待下次数据的到来。

 

3.IO复用(IOMultiplexing)模型:在这种模型下,应用程序要执行read操作时,先调用系统回调函数,这个回调函数被传递给了内核。但是对于程序来讲,它在调用系统回调函数之后,并不等待内核的返回结果,而是直接返回,虽然立即返回的调用函数是一个异步的方式,但应用程序会被像select()、poll()、epoll()等具有复用多个文件描述符的函数阻塞住,一直等到这个回调函数有结果返回了,再通知应用程序。

         使用IO复用模型可以在等待的时候加入超时的时间,当超时时间没有达到的时候与阻塞的情况一致,而当超时时间达到却仍然没有数据接收到的话,系统会返回,不再等待。select()函数按照一定的超时时间轮询,直到需要等待的套接字有数据到来,利用recvfrom()函数将数据复制到应用层。

         IO复用主要针对的是select和epoll,对于一个IO端口,两次调用,两次返回,比阻塞IO并没有什么优越性,关键是能实现同时对多个IO端口进行监听;I/O复用模型会用到select()、poll()、epoll()函数,这几个函数也会使进程阻塞,但是和阻塞IO所不同的是这些函数可以同时阻塞多个IO操作,而且可以同时对多个读操作、写操作的IO函数进行检测,直到有数据可读或可写时才真正调用IO操作函数。

 

4.信号驱动IO(Signal Driven IO)模型:应用程序提交read()请求的系统回调函数,然后内核开始处理相应的IO操作,而同时,应用程序并不等待内核返回响应,就会开始执行其他的处理操作(应用程序没有被IO操作所阻塞)。当内核执行完毕后,返回read的响应,就会产生一个信号或执行一个基于线程的回调函数来完成这次IO处理过程。

         信号驱动的IO在进程开始的时候注册一个信号处理的回调函数,进程继续执行,当信号发生时,即有了IO的事件,即有数据到来,利用注册的回调函数将到来的函数用recvfrom()接收到。

         信号驱动IO模型首先我们允许套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。

 

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

         异步IO与信号驱动IO很相似,其区别就在于信号驱动IO是当数据到来的时候使用信号通知注册的信号处理函数,而异步IO则是在数据复制完成的时候才发送信号通知注册的信号处理函数。

         异步IO的工作机制是:告知内核启动某个操作,并让内核在整个操作完成后通知我们,这种模型与信号驱动的IO区别在于,信号驱动IO是由内核通知我们何时可以启动一个IO操作,这个IO操作由用户自定义的信号函数来实现,而异步IO模型是由内核告知我们IO操作何时完成。为了实现异步IO,专门定义了一套以aio开头的API,如:aio_read。

 

         总结:这五种IO模型,其中阻塞IO、非阻塞IO、多路复用IO和信号驱动IO都属于同步模式,因为其真正的IO操作都将会阻塞进程,只有异步IO模型真正实现了IO操作的异步性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值