【TPLI】63.1 整体概览 & 63.2 IO多路复用

63.1 整体概览

  • IO多路复用(select()以及poll()系统调用)
  • 信号驱动IO
  • Linux专有的epoll编程接口

实际上IO多路复用、信号驱动IO以及epoll都是用来实现一个目标的技术——同时检查多个文件描述符,看它们是否准备好了执行IO操作。

文件描述符就绪状态的转化是通过一些IO事件来触发的,比如输入数据到达,套接字连接建立完成,或者是之前满载的套接字发送缓冲区在TCP将队列中的数据传送到对端之后有了剩余空间。

63.1.1 水平触发和边沿触发

水平触发:如果文件描述符上可以非阻塞地执行IO系统调用,此时认为它已经就绪。
通俗说:只要文件描述符关联的读内核缓冲区非空,有数据可以读取,就
一直发出可读信号
进行通知;当文件描述符关联的内核写缓冲区不满,有空间可以写入,就一直发出可写信号进行通知。

边沿触发:如果文件描述符自上次状态检查以来有了新的I/O 活动(比如新的输入),此时需要触发通知。
通俗说:当文件描述符关联的读内核缓冲区由空转化为非空的时候,则发出可读信号进行通知;当文件描述符关联的内核写缓冲区由满转化为不满的时候,则发出可写信号进行通知。

在这里插入图片描述

特别地,当采用边沿触发模式时:

  • 在接收到一个I/O 事件通知后,程序在某个时刻应该在相应的文件描述符上应该尽可能多地执行I/O。
  • 每个被检查的文件描述符通常都应该置为非阻塞模式。
63.1.2 在备选的IO模型中采用非阻塞IO

63.2 IO多路复用

63.2.1 select()系统调用

系统调用select()会一直阻塞,直到一个或多个文件描述符集合成为就绪态。

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
// 成功返回就绪的文件描述符个数,超时返回0,失败返回-1并设置errno
文件描述符集合

参数readfdswritefds 以及exceptfds 都是指向文件描述符集合的指针,所指向的数据类型是fd_set

  • readfds 是用来检测输入是否就绪的文件描述符集合。
  • writefds 是用来检测输出是否就绪的文件描述符集合。
  • exceptfds 是用来检测异常情况是否发生的文件描述符集合。

通常,数据类型fd_set以位掩码的形式来实现,关于文件描述符集合的操作都是通过四个宏来完成的。

#include <sys/select.h>
void FD_ZERO(fd_set *fdset);                // 将fdset所指向的集合初始化为空
void FD_SET(int fd, fd_set *fdset);         // 将文件描述符fd添加到由fdset所指向的集合中
void FD_CLR(int fd, fd_set *fdset);         // 将文件描述符fd从fdset指向的集合中移除
int  FD_ISSET(int fd, fd_set *fdset);       // 如果文件描述符是fdset所指向的集合中的成员,FD_ISSET返回true

文件描述符集合有一个最大容量限制,由常量FD_SETSIZE决定,在Linux上,该常量的值为1024。

参数readfds, writefds, exceptfds都是“值-结果”参数,select会修改它们,如果在循环中调用select,每次都要重新初始化它们。

参数nfds必须设为比3个文件描述符集合中所包含的最大文件描述符号还要大1。该参数能提高select的效率,因为此时内核就不用去检查大于这个值的文件描述符号是否属于这些文件描述符集合。

timeout参数
struct timeval {
    time_t      tv_sec;  /* Seconds */
    suseconds_t tv_usec; /* Microseconds */
};
  • timeoutNULL时,select会一直阻塞。
  • 如果结构体timeval的两个域都为0时,select不会阻塞,它只是简单地轮询指定的文件描述符集合,看看其中是否有就绪的文件描述符并立刻返回。
  • 否则,timeout将为select()指定一个等待时间的上限值。
select的返回值
  • 返回-1表示发生错误,并设置errno。比如返回EBADF表示3个文件描述符集合中有一个文件描述符是非法的(例如当前并没有打开)。
  • 返回0表示在任何文件描述符成为就绪态之前select()调用已经超时。此时每个返回的文件描述符集合都会被清空。
  • 返回一个正整数表示处于就绪态的文件描述符个数。(所有在 3 个集合中被标记为就绪态的文件描述符总数
63.2.2 poll()系统调用

select()中,我们提供三个集合,每个集合中标明我们感兴趣的文件描述符。而在poll()中我们提供一列文件描述符,并在每个文件描述符上标明我们呢感兴趣的事件。

#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
// 成功返回就绪文件描述符的个数,超时返回0,失败返回-1并设置errno
  • 参数fds列出了我们需要poll()来检查的文件描述符。

    struct pollfd
    {
        int fd;           // file descriptor
        short events;     // requested events bit mask
        short revents;    // returned evevnts bit mask
    }
    

    如果我们对某个特定的文件描述符不感兴趣了,只需要将events设为0或一个负值,这样可以关闭对单个文件描述符的检查,而不需要重新建立整个fds列表。

  • 参数nfds指定了数组fds中元素的个数。
    在这里插入图片描述

  • timeout参数决定了poll()的阻塞行为:

    • timeout == -1poll()一直阻塞直到有文件描述符就绪。
    • timeout == 0poll()不会阻塞,只是执行一次检查看看哪个文件描述符处于就绪态。
    • timeout > 0poll()至多阻塞timeout毫秒,直到有文件描述符处于就绪态。
  • poll()的返回值:

    • 返回-1表示出错。
    • 返回0表示超时(指定时间内没有文件描述符就绪)。
    • 返回一个正整数表示就绪的文件描述符个数,即数组fdsevents字段非0的pollfd结构体数量。
62.2.3 文件描述符何时就绪

对于普通文件,select()标记为可读和可写,poll()会在revents字段中返回POLLINPOLLOUT

对于套接字如下:
在这里插入图片描述

POLLEDHUP这个标志的实际形式是EPOLLRDHUP——主要被设计用于epoll API 的边缘触发模式下。当流式套接字连接的远端关闭了写连接时会返回该标志。使用这个标志能让采用了 epoll 边缘触发模式的应用程序使用更简洁的代码来判断远端是否已经关闭。(另一种可选的方法是,在应用程序中设定POLLIN 标志,然后执行一次read(),如果返回0 则表示远端已经关闭了。)

注意:当套接字对端close()关闭整个连接后,read()也返回0。对端shutdown()只是关闭对端的写端(发送方向)。

63.2.4 比较select()和poll()
  • 实现细节:在Linux内核层面,select()poll()都使用了相同的内核poll例程集合。每个poll例程都返回有关单个文件描述符就绪的信息。

  • API之间的区别:

    • select()使用的数据类型fd_set对于检查的文件描述符个数有上限FD_SETSIZE,Linux默认1024;而poll()对检查的文件描述符数量上本质没有限制。
    • select()的参数fd_set同时也是保存调用结果的地方,如果要在循环中重复调用select()的话,我们必须每次都要重新初始化fd_set;而poll()通过两个独立的字段来处理输入(events)和输出(revents)。
  • 性能:当如满足如下两条中任意一条时,poll()和select()将具有相似的性能表现。

    • 当待检查的文件描述符范围较小(即,最大的文件描述符号较低)。
    • 有大量的文件描述符待检查,但是它们分布得很密集(即,大部分文件描述符号都在0 到某个上限之间)。

    如果被检查的文件描述符集合很稀疏的话,poll()的性能表现将优于select()。因为在select()中,不管我们是否要检查范围0 nfds−1之间的所有文件描述符,nfds 的值都不变。无论哪种情况,内核都必须在每个集合中检查nfds个元素,以此来查明到底需要检查哪个文件描述符。与之相反,当使用 poll()时,只需要指定我们感兴趣的文件描述符即可,内核只会去检查这些指定的文件描述符。

63.2.5 select()和poll()存在的问题
  • 每次调用select()poll(),内核都必须检查所有被指定的文件描述符,看它们是否处于就绪态。
  • 每次调用select()poll()时,程序都必须传递一个表示所有需要被检查的文件描述符的数据结构到内核,内核检查过描述符后,修改这个数据结构并返回给程序。(此外,对于select()来说,我们还必须在每次调用前初始化这个数据结构。)对于poll()来说,随着待检查的文件描述符数量的增加,传递给内核的数据结构大小也会随之增加。
  • select()poll()调用完成后,程序必须检查返回的数据结构中的每个元素,以此查明哪个文件描述符处于就绪态了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值