I/O复用:select和poll函数

3 篇文章 0 订阅

《UNIX网络编程卷1:套接字联网API(第3版)》 - 第6章- I/O复用

I/O模型

在介绍select和poll两个函数之前, 整体回顾下Unix下5种I/O模型的基本区别

  • 阻塞式I/O
  • 非阻塞式I/O
  • I/O复用(select 和 poll)
  • 信号驱动式I/O(SIGIO)
  • 异步I/O(POSIX的aio_系列函数)

一个输入操作通常包含两个不同阶段:

  1. 等待数据准备好
  2. 从内核向进程复制数据

对于一个套接字上的输入操作:
第一步通常涉及等待数据从网络中到达,当所等待分组到达时,它被复制到内核的某个缓冲区
第二步,就是数据从内核缓冲区复制到应用程序缓冲区

阻塞式I/O模型

最流行的I/O模型是**阻塞式I/O(blocking I/O)**模型。 默认情况下,所有套接字都是阻塞的。 下面以UDP套接字作为例子:
在这里插入图片描述

本例中,把 recvfrom函数视为系统调用
进程调用recvfrom,其系统调用直到数据报达到且被复制到应用进程的缓冲区中或者发生错误才返回。
从进程调用recvfrom开始到它返回整段时间内是被阻塞的

最常见的错误是:系统调用被中断。

非阻塞式I/O模型

进程把一个套接字设置为非阻塞,是在通知内核:当所有请求的I/O操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误

  • 前三次调用recvfrom时没有数返回,因此内核转而立即返回一个EWOULDBLOCK错误。
  • 第四次调用recvfrom时,已有一个数据报准备好,它被复制到应用程序缓冲区,于是recvfrom成功返回,我们接着处理数据。

当一个应用进程像这样对一个非阻塞描述符循环调用recvfrom时,我们称之为轮询(polling).
应用进程持续轮询内核,以查看某个操作是否就绪。 这么做往往耗费大量CPU时间。
在这里插入图片描述

I/O复用模型

有了I/O复用(I/O multiplexing),我们就可以调用select 或poll,阻塞在这两个系统调用的某一个之上而不是阻塞在真正的I/O系统调用上。
在这里插入图片描述
我们阻塞于select调用,等待数据报套接字变为可读。 当select返回套接字可读这一条件时,我们调用recvfrom把所读数据报复制到应用程序缓冲区。

I/O复用并不显得有什么优势,事实上由于使用select需要两个系统调用(select + recvfrom),反而更有劣势
它的优势在于: select可以等待多个描述符就绪。

在这里插入图片描述

信号驱动式I/O模型

我们也可以使用信号, 让内核在描述符就绪时发送SIGIO信号通知我们。 这种模式称之为信号驱动式I/O(signal-driven I/O)
在这里插入图片描述

  • 首先,开启套接字的信号驱动I/O功能,并通过sigaction系统调用安装一个信号处理函数。 该系统调用将立即返回,我们的进程继续工作 —也就是说 没有被阻塞
  • 当数据报准备好读取时,内核就为该进程产生一个SIGIO信号
  • 随后,可以在信号处理函数中 调用recvfrom 读取数据报。

使用SIGIO信号模型,优势在于等待数据报到达期间进程不被阻塞,主循环可以继续执行,只要等待来自信号处理函数的通知。

异步I/O模型

异步I/O(asynchronous I/O)有POSIX规范定义。
一般的说,该模型的工作机制是:告知内核启动某个操作, 并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。
它与信号驱动式I/O模型的
主要区别
在于:信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作,而异步模型是由内核通知我们I/O操作何时完成
在这里插入图片描述

  • 我们调用aio_read函数(POSIX异步I/O函数以aio_或lio_开头),给内核传递描述符、缓冲区指针、缓冲区大小和文件偏移(与lseek类似),并告知内核当整个操作完成时如何通知我们
  • 系统调用立即返回,而且在等待I/O完成期间,我们的进程不被阻塞
  • 本例,假设要求内核操作在操作完成时产生某个信号,该信号直到数据已经复制到应用进程缓冲区才产生。

各I/O模型的比较

在这里插入图片描述

可以看出:前4中模型的主要区别在于第一阶段, 因为它们第二阶段是一样的:在数据从内核复制到调用者缓冲区期间, 进程阻塞于recvfrom调用. ---- 我们将前4中模型定义为同步I/O操作(synchronous I/O operation)
后一种称之为异步I/O操作(asynchronous I/O operation)



select 函数

该函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或经历一段时间后才唤醒它。


在这里插入图片描述

timeout
timeout,它告知内核等待指定描述符中的任何一个就绪,最大的等待时间。

struct timeval {
	long tv_sec;  // 秒数
	long tv_userc; //毫秒数
}

这个参数有三种可能:

  1. 永远等待下去: 仅在有一个描述符准备好I/O时才返回。
  2. 等待一段固定时间:在有一个描述符准备好I/O时返回,但是不超过由该参数指向的timeval结构中的描述和好描述。
  3. 根本不等待: 检查描述符后立即返回,这称为轮询(polling).该参数必须指向一个timeval结构,且其中的值必须为0.

前两种情形的等待通常会被 进程在等待期间补获的信号中断,并从信号处理函数返回。

中间三个参数:readset, writeset 和 exceptset指定我们要让内核测试读、写和异常的描述符。

描述符就绪条件

我们一直在讨论某个描述符准备好I/O(读或写)或是等待其上一个待处理的异常条件.
我们对于select返回套接字**“就绪”**的条件必须讨论更明确些:

读就绪

满足下列四个条件中任意一个,套接字准备好.
在这里插入图片描述

写就绪

满足下列四个条件中任意一个,套接字准备好.
在这里插入图片描述

异常

如果一个套接字存在带外数据仍处于带歪标记,那么它有异常条件待处理。

总结

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值