IO模型
linux中有几种IO模型,如select、poll、epoll,这几个分别是什么呢?
select 模型
- 工作流程:
- 首先创建一个文件描述符(fd)集合,添加需要监测的 socket 句柄。
- 调用
select()函数,将 fd 集合从用户态拷贝到内核态。 - 内核遍历所有 fd,检查是否有 IO 事件(如可读、可写)就绪。
- 若有就绪事件,内核修改 fd 集合标记就绪的 fd,然后将集合拷贝回用户态。
- 用户态遍历 fd 集合,找到就绪的 fd 并处理。
- 缺点:
- 最大 fd 限制:默认 1024(可通过修改内核参数调整,但不推荐)。
- 效率低:每次调用都需拷贝 fd 集合,且内核和用户态都要遍历所有 fd,高并发下开销剧增。
- 水平触发(LT):若就绪事件未被处理,下次调用仍会通知,可能导致重复处理。
poll 模型
- 改进点:
- 用链表存储 fd 集合,理论上无最大连接数限制,解决了 select 的 fd 数量瓶颈。
- 与 select 的共性问题:
- 仍需在用户态和内核态之间拷贝 fd 集合,高并发时拷贝开销大。
- 内核和用户态都需遍历所有 fd,效率随 fd 数量增加而下降。
- 同样采用水平触发(LT)机制。
epoll 模型
- 核心改进:
- 事件驱动:不再轮询,而是为每个 fd 注册回调函数,当 IO 事件就绪时,内核主动触发回调,将就绪 fd 加入就绪队列。
- 减少拷贝:fd 集合只需在初始化时从用户态拷贝到内核态(通过
epoll_ctl()),后续无需重复拷贝。 - 高效遍历:用户态通过
epoll_wait()直接获取就绪 fd 列表,无需遍历所有 fd。
- 触发模式:
- 水平触发(LT):默认模式,若就绪事件未处理,下次仍会通知(兼容 select/poll 的使用习惯)。
- 边缘触发(ET):仅在 fd 状态从 “未就绪” 变为 “就绪” 时通知一次,需一次性处理完所有数据,效率更高,但编程复杂度增加。
- 优势场景:高并发、长连接场景(如服务器处理大量客户端请求),性能远超 select 和 poll。
三者对比表格
| 特性 | select | poll | epoll |
|---|---|---|---|
| 最大 fd 限制 | 1024(可修改) | 无(链表存储) | 无(理论上取决于系统内存) |
| fd 集合拷贝 | 每次调用拷贝 | 每次调用拷贝 | 仅初始化时拷贝 |
| 遍历方式 | 轮询所有 fd | 轮询所有 fd | 直接获取就绪 fd 列表 |
| 触发模式 | 水平触发(LT) | 水平触发(LT) | LT/ET(支持两种) |
| 高并发效率 | 低(O (n)) | 低(O (n)) | 高(O (1)) |
| 适用场景 | 连接数少且固定 | 连接数中等 | 高并发、大连接数 |
总结来说,epoll 是 Linux 下高性能 IO 模型的首选,尤其在高并发场景下优势明显;而 select 和 poll 由于效率限制,更多用于简单场景或兼容性需求。