Linux I/O复用笔记

2022-08-31  本文已影响0人  牛奶言兼

什么是I/O复用

当应用程序需要处理多个输入时,比如同时处理标准输入和TCP套接字,应用程序在调用fgets时会阻塞等待用户输入,而此时TCP可能有新的数据接收或被动关闭,那么受fgets的阻塞影响,TCP套接字的数据会过很长时间才会被处理,这就大大降低了应用程序的执行效率。而这也就衍生出进程需要一种预先告知内核的能力,使得内核一旦发现进程指定的一个或多个I/O条件就绪(输入已准备好被读取,或者描述符已能承接更多的输出),它就通知进程。这个能力称为I/O复用(I/O multiplexing)

这里涉及的核心在于,进程是向内核进行订阅,而订阅的状态通知则可以分为“”和“”。

Linux中,I/O复用的支持API是通过selectpoll两个函数支持的,随着Linux内核的演进,有更加完善的I/O复用API提供,如:pselectppollepoll

Unix I/O模型

一般的I/O两个阶段包括:内核准备数据与内核与用户空间交换数据(复制)。当然,这里有例外的,比如用户空间使用DMA或用户空间驱动等。

unix下存有5种I/O模型:

  1. 阻塞式I/O
  2. 非阻塞式I/O
  3. I/O复用
  4. 信号驱动式I/O
  5. 异步I/O

参考:《Unix 网络编程 卷1》

阻塞式I/O
阻塞式I/O模型示意图

如图所示,在用户进程中调用系统函数后,用户进程就进入阻塞状态,直到内核将进程所需的数据准备完成(对于网络数据请求,可能需要等待对端传递数据过来)后,内核将用户空间所要的数据拷贝到用户空间中,系统函数再返回,用户进程开始处理相关数据。

非阻塞式I/O
非阻塞式I/O模型示意图

如图所示,在用户进程中轮询recvfrom 系统调用,直到返回成功指示后,用户进程开始处理数据。此模型轮询调用,CPU的消耗较大,通常是在特定应用场景中使用。
基于该模型所表达的含义,当系统调用未准备好数据时返回特定状态码,用户进程再根据状态码自行确定轮询策略的方式,是非阻塞式I/O的关键特征。
但一旦系统调用内核数据准备好时,该系统调用仍会阻塞用户进程,当数据从内核拷贝到用户空间完成后,系统调用才会返回指示状态,用户进程才会继续处理。所以非阻塞式I/O仍是同步的I/O模型。

I/O复用模型
I/O复用模型示意图

首先,什么是I/O复用(I/O multiplexing)?个人理解,所谓复用是指当执行系统调用时,可以得到可用I/O资源状态数量而言的。与前面的I/O模型相比,一次系统调用只能获取一个I/O资源,而复用模型,则是通过一次系统调用,向内核请求多个I/O资源状态数据,当所请求的I/O资源中有一个或多个可用时,系统调用返回,用户进程再次调用系统函数请求内核将数据拷贝到用户进程,拷贝时,仍是阻塞的。所以,I/O复用也是同步状态的。
借鉴I/O复用,其实通过多线程,每个线程执行前面的I/O模型,也可伪装达到复用的效果,但当量级较大时,此时线程资源则不足以支持。

可不可以将二阶段的请求内核数据拷贝也变成非阻塞的呢?

信号驱动式I/O模型
信号驱动式I/O模型示示意图

与复用I/O模型相比,信号驱动式I/O模型则是向内核递交监听请求信号,当内核I/O数据准备好后,通过信号进行交互,用户进程再向内核请求数据。在请求数据时,系统调用仍然阻塞用户进程。所以,该模型仍是同步状态。

有个问题,信号驱动是单I/O资源监听还是多I/O资源?如果是单I/O监听,能不能修改为多I/O?与I/O复用有什么差异?

异步I/O模型

异步I/O(asynchronous I/O)由POSIX规范定义。

异步I/O模型示意图

与信号驱动式I/O模型相比,异步I/O模型则是彻底将数据请求和数据拷贝全交给内核进行处理,然后借由信号完成内核空间与用户进程间的交互。

仍然有个问题,移步是单I/O资源监听还是多I/O资源?如果是单I/O监听,能不能修改为多I/O?与I/O复用有什么差异?

select I/O复用

select 函数声明

// sys/select.h

/* Check the first NFDS descriptors each in READFDS (if not NULL) for read
   readiness, in WRITEFDS (if not NULL) for write readiness, and in EXCEPTFDS
   (if not NULL) for exceptional conditions.  If TIMEOUT is not NULL, time out
   after waiting the interval specified therein.  Returns the number of ready
   descriptors, or -1 for errors.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int select (int __nfds, fd_set *__restrict __readfds,
           fd_set *__restrict __writefds,
           fd_set *__restrict __exceptfds,
           struct timeval *__restrict __timeout);

如函数声明中所表达的,select 可以监听三类io资源(可读、可写、异常),并辅以时间控制。
参数含义:

参数名 含义
__nfds 监听的io资源描述符的最大值
__readfds 可读io资源监听集合
__writefds 可写io资源监听集合
__exceptfds 异常io资源监听集合
__timeout 监听时间控制

辅以操作fd_set 结构的四个函数式宏为:

/* Access macros for `fd_set'.  */
#define FD_SET(fd, fdsetp)  __FD_SET (fd, fdsetp)
#define FD_CLR(fd, fdsetp)  __FD_CLR (fd, fdsetp)
#define FD_ISSET(fd, fdsetp)    __FD_ISSET (fd, fdsetp)
#define FD_ZERO(fdsetp)     __FD_ZERO (fdsetp)

poll I/O复用

pselect I/O复用

ppoll I/O复用

epoll I/O复用

上一篇 下一篇

猜你喜欢

热点阅读