IO多路复用——Select底层原理深度分析(流程图)

Select 是 Linux 系统中最早的 I/O 多路复用机制,它允许程序同时监控多个文件描述符的状态变化。

Select 使用位图(fd_set)来表示要监控的文件描述符集合,通过系统调用进入内核后,内核会遍历位图中设置为 1 的每一位,对每个对应的文件描述符调用其 poll 方法来检查是否有数据可读、可写或发生异常。

当有事件就绪时,内核会将结果位图拷贝回用户空间,用户程序通过检查结果位图来确定哪些文件描述符发生了事件。

Select 的主要特点是实现简单、兼容性好,但也存在明显的性能瓶颈:每次调用都需要遍历所有文件描述符(时间复杂度 O(n)),最多只能监控 1024 个文件描述符,并且每次调用都要进行两次用户态和内核态之间的数据拷贝。

因此,Select 适用于连接数较少的场景,在高并发环境下性能较差,这也是后来出现 Poll 和 Epoll 等更高效 I/O 多路复用机制的原因。

  • Select 整体调用流程:
用户空间调用select方法
系统调用入口 SYSCALL_DEFINE5
kern_select 参数验证和初始化
do_select 核心实现
遍历文件描述符位图
对每个fd调用poll方法
检查事件状态
更新结果位图
有事件就绪?
返回结果到用户空间
用户空间轮询处理相应事件
等待事件或超时
被唤醒后重新检查

1.kern_select 参数验证和初始化流程

kern_select开始
声明数据结构
参数验证
n < 0 或 n > FD_SETSIZE?
返回 -EINVAL
初始化poll_wqueues
设置select_table
调用do_select
清理资源
返回结果
结束
  • poll_wqueues: Linux 内核中用于管理 poll/select 等待队列的核心数据结构。
  • select_table: Select 系统调用中连接用户空间和内核空间 poll 机制的桥梁数据结构。

2. do_select() 详细实现流程

全为0
有设置位
do_select开始
拷贝fd_set到内核空间
初始化变量和指针
遍历每个unsigned long 64位组
检查当前64位组
跳过整个64位组
下一个64位组
遍历当前64位组的每一位
当前位是否设置?
下一位
获取文件描述符fdget_i
文件描述符有效?
释放文件描述符
调用文件的poll方法
检查事件状态
设置结果位
遍历完当前64位组?
更新结果位图到用户空间
有事件就绪?
返回事件数量
调用poll_schedule_timeout等待
被唤醒或超时
重新检查事件状态
结束
  • fd_set: 表示文件描述符集合的位图数据结构
    • 固定大小:__FD_SETSIZE = 1024
    • 在64位系统上:1024 / 64 = 16 个 unsigned long、
    • 总共可以表示 1024 个文件描述符

3. 位图数据结构详解

位值含义
位图含义
fd_set 位图结构
1: 监控
0: 不监控
位1: 文件描述符1
位0: 文件描述符0
位2: 文件描述符2
...
位1023: 文件描述符1023
bits_1 64位
bits_0 64位
bits_2 64位
...
bits_15 64位

4. 文件描述符处理详细流程

用户空间 内核空间 文件系统 Socket层 select() 系统调用 从用户空间拷贝fd_set到内核 遍历位图 fdget(i) 获取文件描述符 返回文件对象 调用sock_poll() 检查socket状态 返回事件掩码 根据事件掩码设置结果位 fdput(f) 释放文件描述符 loop [对每个设置的文件描述符] 更新结果位图 返回结果到用户空间 用户空间 内核空间 文件系统 Socket层

5. Poll方法调用链

TCP
UDP
其他
do_select调用poll
sock_poll
检查socket类型
tcp_poll
udp_poll
默认poll
检查TCP连接状态
检查接收缓冲区
检查发送缓冲区
检查错误状态
检查UDP数据
返回默认事件
返回事件掩码
设置结果位

6. 等待机制实现

进程运行中
没有事件就绪
poll_schedule_timeout
文件描述符状态改变
超时时间到达
收到信号
重新检查事件
重新检查事件
重新检查事件
返回结果
Running
Interruptible
Waiting
Woken
Timeout
Signal

7. 用户态处理就绪事件

用户程序收到返回值后,会处理相应事件:

int ready = select(n, &readfds, &writefds, &exceptfds, &timeout);
if (ready > 0) {
    // 检查哪些文件描述符就绪
    for (int i = 0; i < n; i++) {
        if (FD_ISSET(i, &readfds)) {
            // 文件描述符 i 有数据可读
            read_data(i);
        }
        if (FD_ISSET(i, &writefds)) {
            // 文件描述符 i 可以写入数据
            write_data(i);
        }
        if (FD_ISSET(i, &exceptfds)) {
            // 文件描述符 i 发生异常
            handle_exception(i);
        }
    }
}

8. 性能瓶颈分析

Select性能瓶颈
遍历开销
内存拷贝
文件描述符限制
重复检查
O_n 时间复杂度
每次都要遍历所有fd
即使大部分fd没有事件
每次调用都拷贝fd_set
结果也要拷贝回用户空间
用户态内核态切换开销
最多1024个文件描述符
无法动态扩展
不适合高并发场景
每次重新检查所有fd状态
没有事件驱动机制
轮询方式效率低

9. 与其他I/O多路复用对比

Epoll
Poll
Select
时间复杂度: O_1
fd限制: 无限制
内存拷贝: 只在添加时
事件通知: 事件驱动
数据结构: 红黑树+链表
时间复杂度: O_n
fd限制: 无限制
内存拷贝: 每次调用
事件通知: 轮询
数据结构: 数组
时间复杂度: O_n
fd限制: 1024
内存拷贝: 每次调用
事件通知: 轮询
数据结构: 位图
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

TracyCoder123

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

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

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

打赏作者

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

抵扣说明:

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

余额充值