IO多路复用——Poll底层原理深度分析

Poll是IO多路复用的一种实现方式,它允许一个进程同时监听多个文件描述符,当其中任何一个文件描述符就绪时,进程就可以进行相应的IO操作。

  • 核心特点:
    • 非阻塞:不会因为某个文件描述符未就绪而阻塞整个进程
    • 轮询机制:通过轮询检查所有被监听的文件描述符状态
    • 事件驱动:基于事件通知机制,只有文件描述符状态发生变化时才返回

1. 工作流程

POLLIN
POLLOUT
POLLERR
应用程序启动
创建pollfd数组
设置要监听的文件描述符和事件
调用poll函数
检查所有文件描述符
是否有文件描述符就绪?
超时或继续等待
返回就绪的文件描述符数量
遍历pollfd数组
检查revents字段
处理读事件
处理写事件
处理错误事件
执行相应的IO操作
是否继续监听?
结束程序

2. 详细文字描述

2.1 初始化阶段

  1. 创建pollfd结构体数组:为每个需要监听的文件描述符分配一个pollfd结构体
  2. 设置监听参数
    • fd:要监听的文件描述符
    • events:要监听的事件类型(读、写、异常等)
    • revents:实际发生的事件(由内核填充)

2.2 监听阶段

  1. 调用poll函数:将pollfd数组传递给内核
  2. 内核处理
    • 检查所有文件描述符的状态
    • 将就绪的文件描述符标记在revents字段中
    • 返回就绪的文件描述符数量

2.3 事件处理阶段

  1. 遍历pollfd数组:检查每个文件描述符的revents字段
  2. 事件分类处理
    • POLLIN:数据可读
    • POLLOUT:数据可写
    • POLLERR:发生错误
    • POLLHUP:连接断开

2.4 循环监听

处理完当前事件后,重新调用poll函数继续监听,形成事件循环。

3.Poll与Select底层原理对比

3.1 系统调用层面

Select系统调用

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

Poll系统调用

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

3.2 核心数据结构差异

Select的数据结构

typedef struct {
    long fds_bits[FD_SETSIZE / NFDBITS];
} fd_set;

// 位图表示,每个bit代表一个文件描述符
// 例如:fds_bits[0] = 0x00000001 表示文件描述符0被设置

Poll的数据结构

struct pollfd {
    int   fd;      // 文件描述符
    short events;  // 请求的事件
    short revents; // 返回的事件
};

3.3 底层实现机制对比

都有两次用户空间、内核空间之间的拷贝。

Poll机制
内核空间
用户空间
遍历pollfd数组
检查每个fd的状态
修改revents字段
返回就绪数量
用户遍历pollfd数组
Select机制
内核空间
用户空间
遍历所有文件描述符
检查每个fd的状态
修改位图
返回就绪数量
用户再次遍历位图

3.4 详细底层原理分析

文件描述符传递方式

Select:

  • 使用三个独立的位图(readfds, writefds, exceptfds)
  • 每个位图使用long数组存储,每个bit代表一个文件描述符
  • 文件描述符范围限制:0 到 FD_SETSIZE-1(通常是1024)

Poll:

  • 使用pollfd结构体数组
  • 每个结构体包含文件描述符和事件信息
  • 无文件描述符数量限制

内核处理流程

Select内核处理:

// 伪代码展示select内核处理
int do_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds) {
    // 1. 遍历所有文件描述符(0到nfds-1)
    for (int fd = 0; fd < nfds; fd++) {
        // 2. 检查每个fd是否在对应的位图中
        if (FD_ISSET(fd, readfds)) {
            // 3. 检查该fd是否可读
            if (is_readable(fd)) {
                // 4. 设置对应的位
                FD_SET(fd, readfds);
            } else {
                // 5. 清除对应的位
                FD_CLR(fd, readfds);
            }
        }
        // 类似处理writefds和exceptfds
    }
    return ready_count;
}

Poll内核处理:

// 伪代码展示poll内核处理
int do_poll(struct pollfd *fds, nfds_t nfds) {
    // 1. 遍历pollfd数组
    for (int i = 0; i < nfds; i++) {
        int fd = fds[i].fd;
        short events = fds[i].events;
        
        // 2. 检查该fd的状态
        short revents = 0;
        if (events & POLLIN && is_readable(fd)) {
            revents |= POLLIN;
        }
        if (events & POLLOUT && is_writable(fd)) {
            revents |= POLLOUT;
        }
        
        // 3. 设置revents字段
        fds[i].revents = revents;
    }
    return ready_count;
}

3.5 性能差异分析

时间复杂度

Select:

  • 遍历复杂度:O(n),其中n是最大文件描述符值
  • 即使只监听文件描述符1000,也要检查0-999的所有fd

Poll:

  • 遍历复杂度:O(n),其中n是实际监听的文件描述符数量
  • 只检查用户实际设置的pollfd结构体

内存使用

Select:

// 内存布局示例
fd_set readfds;  // 128字节(1024位)
fd_set writefds; // 128字节
fd_set exceptfds; // 128字节
// 总计:384字节固定大小

Poll:

// 内存布局示例
struct pollfd fds[100];  // 每个结构体12字节
// 总计:1200字节(可动态调整)

3.6 系统调用开销对比

Poll开销
Select开销
系统调用
用户空间
内核遍历实际fd数量
修改revents字段
返回用户空间
用户遍历pollfd数组
系统调用
用户空间
内核遍历1024个fd
修改位图
返回用户空间
用户遍历位图

3.7 实际性能测试场景

场景1:监听少量文件描述符(< 100)

  • Select性能:较好,因为位图操作效率高
  • Poll性能:相当,结构体数组开销不大

场景2:监听大量文件描述符(> 1000)

  • Select性能:较差,需要遍历大量无效fd
  • Poll性能:较好,只处理实际监听的fd

场景3:高并发场景

  • Select性能:每次都要重新设置位图
  • Poll性能:可以复用pollfd数组

3.8 底层实现细节对比

特性SelectPoll
数据结构位图结构体数组
文件描述符限制1024无限制
内核遍历方式线性扫描位图线性扫描数组
用户空间处理位操作数组遍历
内存效率固定大小动态大小
系统调用参数多个独立参数单个结构体数组
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TracyCoder123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值