⟅UNIX网络编程⟆⦔IO复用概述及模型

说在前面

  • 环境:windows10
  • 参考: UNIX网络编程
  • 目录:这里
  • 吐槽:爷青回

问题引出

  • 之前的例子中有提到这种情况:
    客户端在处理两个输入(标准输入TCP套接字)时,若客户端阻塞在等待输入而服务器进程终止,那么客户端将无法读取到TCP套接字返回的EOF。见如下代码,
    // 客户端程序
    void
    str_cli(FILE *fp, int sockfd)
    {
        char    sendline[MAXLINE], recvline[MAXLINE];
    
        while(Fgets(sendline, MAXLINE, fp) != NULL) // 等待用户输入时,服务器进程终止
        {
            Writen(sockfd, sendline, strlen(sendline));
    
            if(Readline(sockfd, recvline, MAXLINE) == 0)
                err_quit("str_cli: server terminated prematurely");
    
            Fputs(recvline, stdout);
        }
    }
    
  • I/O复用便是为了处理类似上述的情况而提出的。这使得内核能够在发现一个或多个I/O就绪时通知对应的进程。

I/O模型

阻塞式I/O模型

  • 在该模型下,所有I/O(包括套接字)都是阻塞的。以UDP套接字为例,过程如下:
    在这里插入图片描述
  • 上述例子是以UDP为例,这是因为对于UDP来说,数据是否准备好的概念比较简单:数据报的接收状态只有两种,是或者否。但是在TCP中,诸如套接字低水平标记(low-water mark,详见这里)等额外变量的存在,使得数据准备完成这个概念变得复杂。
  • 这里,我们将recvfrom函数视作系统调用,并将整个过程划分成两个部分,应用进程(用户态)以及内核。不论这个系统调用是如何实现的,它一般都会从应用进程空间中切换到内核空间,之后再切换过来(详见操作系统原理)。
  • 上述例子中,进程调用recvfrom,其系统调用直到数据报到达并且讲数据从内核复制到应用进程的缓冲区中发生错误才会返回。最常见的错误是系统调用被信号中断,例如这里
  • 从上图应用进程的角度来看,进程在从调用recvfrom开始到它返回的整段时间内(黄色部分)是阻塞的。

非阻塞式I/O模型

  • 进程将一个套接字设置成非阻塞是在通知内核:当进程所请求的I/O操作不得不将本进程投入睡眠才能完成时,不要将进程投入睡眠,而是直接返回一个错误
    在这里插入图片描述
  • 如上图所示,前两次recvfrom无数据可返回,因此内核立即返回一个EWOULDBLOCK错误。在下一次调用时,内核发现有数据报准备完成,便开始将数据复制到应用进程缓冲区,之后recvfrom返回成功。
  • 当一个应用进程像上述过程那样对一个非阻塞描述符循环调用某个操作时,我们称之为轮询(polling)。应用进程会持续轮询内核,以此来确定某个操作是否就绪。但是这种方式通常会耗费大量的CPU时间。

I/O复用模型

  • 使用I/O复用,应用进程就可以调用select或者poll,阻塞在这两个系统调用之一,而不是阻塞在真正的I/O系统调用上。
    在这里插入图片描述
  • 应用进程阻塞于select调用,等待数据报套接字可用。当select返回套接字可读后,应用进程调用recvfrom把数据报复制到应用进程缓冲区(实际上是内核完成)。
  • 对比阻塞调用I/O复用并不显得有什么优势,并且由于使用select,我们需要用到两个系统调用,I/O复用还稍微劣势一些。但是,使用select的优势在于可以等待多个描述符就绪。

信号驱动式I/O模型

  • 使用信号,可以让内核在描述符就绪的时候通知应用进程,这种模型被称为信号驱动式I/O
    在这里插入图片描述
  • 首先开启套接字的信号驱动式I/O功能(后续),并使用sigaction系统调用安装一个信号处理函数。该系统调用会立即返回,这样应用进程可以继续运行,而不会阻塞。当数据报准备好后,内核会为进程产生一个SIGIO信号。应用进程会捕获到该信号。这样,我们既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已经准备好;也可以立即通知主循环,让其读取数据报。
  • 该模型的优势在于等待数据报准备期间,应用进程不会阻塞。

异步I/O模型

  • 异步I/O由POSIX规范定义。演变成当前POSIX规范的各种早期标准所定义的实时函数中存在的差异已经取得一致(早些年的那些函数已经被同化了)。一般来讲,这些函数的工作机制是:通知内核启动某个操作,并且让内核在完成整个操作后(包括内核将数据复制到应用进程缓冲区)通知我们。

  • 该模型与信号驱动模型的主要区别在于:信号驱动式I/O是内核告知何时可以启动一个I/O,而异步则是告知一个I/O完成
    在这里插入图片描述

  • 调用aio_read函数(POSIX异步I/O以aio_或lio_开头),给内核传递描述符、缓冲区指针、缓冲区大小(与read函数相同)和文件偏移(类似lseek),并告知内核当整个操作完成时如何通知应用进程。

  • 该函数会立即返回,这样,应用进程将不会阻塞。

  • 上述例子中,假设要求内核在操作完成时会产生某个信号,该信号在数据已经复制到应用进程缓冲区后才产生,不同于信号驱动模型。

对比

对比一

在这里插入图片描述

  • 前4种模型的区别主要在于第一阶段,他们的第二阶段都是相同的:即应用进程都阻塞在数据从内核复制到调用者的缓冲区。而异步I/O模型在这两个阶段都要处理,从而不同于其他4种模型(异步I/O模型中,应用进程没有阻塞阶段)。

同步、异步I/O对比

  • 同步I/O操作导致请求进程阻塞,直到I/O操作完成;(前4种)
  • 异步I/O操作不导致请求进程阻塞。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值