当前位置: 首页 > news >正文

玳瑁的嵌入式日记D33-0904(IO多路复用)

一、核心概念
  • 定义单线程 / 单进程同时监测多个文件描述符是否可执行 IO 操作的能力
  • 核心价值:用更少的资源(减少进程 / 线程创建开销、避免上下文切换、解决资源竞争)处理更多事件流
  • 并发本质:逻辑控制流在时间上的重叠,通过 CPU 时分复用实现
二、为何需要 IO 多路复用?

传统并发处理方案(多进程 / 多线程)存在明显成本:

  • 进程 / 线程创建与销毁的资源开销
  • 上下文切换(Context Switch)的性能损耗
  • 多线程间的资源竞争与同步问题

IO 多路复用提供了单线程处理多事件流的高效方案

三、五大 IO 模型对比
  1. 阻塞 IO(默认常用)

    • 特点:执行 IO 操作时会阻塞等待,直到操作完成
    • 适用场景:简单场景,无需同时处理多个 IO 事件
  2. 非阻塞 IO

    • 特点:不会阻塞等待,无数据时立即返回
    • 关键标识:EAGAIN(需重试)、errno(错误码)
    • 实现方式:通过 fcntl () 设置 O_NONBLOCK 标志
  3. 信号驱动 IO

    • 特点:通过信号通知 IO 事件,核心是 SIGIO 信号
    • 实现步骤:
      1. 追加 O_ASYNC 标志:fcntl(fd, F_SETFL, flag | O_ASYNC)
      2. 设置信号接收者:fcntl(fd, F_SETOWN, getpid())
      3. 注册信号处理函数:signal(SIGIO, 处理函数)
    • 现状:实际应用较少

select 循环服务器(TCP 协议)

一、并发的两种主要实现方式
  1. 进程:通过多进程处理多个连接请求
  2. 线程:通过多线程处理多个连接请求
二、select 循环服务器(IO 多路复用实现)

select 是 IO 多路复用的一种实现方式,可在单进程 / 线程中管理多个文件描述符,实现并发服务器。

1. select 函数原型
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
  • 功能:检测指定描述符集合中是否有可执行 IO 操作的描述符,具有阻塞等待特性
  • 返回值
    • 超时:0
    • 失败:-1
    • 成功:返回就绪的文件描述符数量(>0)
2. 参数详解
  • nfds:需检测的描述符上限值(通常为最大描述符值 + 1
  • readfds:需检测的可读描述符集合
  • writefds:需检测的可写描述符集合
  • exceptfds:需检测的异常描述符集合
  • timeout:超时设置
    • NULL:一直阻塞等待
    • 非 NULL:指定超时时间
3. 配套宏函数
宏函数功能
FD_CLR(int fd, fd_set *set)从集合中移除指定描述符
FD_ISSET(int fd, fd_set *set)判断描述符是否在集合中(就绪)
FD_SET(int fd, fd_set *set)向集合中添加指定描述符
FD_ZERO(fd_set *set)清空集合中所有描述符
4. select 使用注意事项
  1. 描述符集合处理

    • readfds 等参数是 "值结果参数",会被函数修改
    • 通常需维护一个原始集合(如 allread_fdset),每次调用前拷贝到临时集合
    • 支持赋值运算符=进行集合拷贝
  2. nfds 管理

    • 新增描述符时需更新最大值
    • 减少描述符时处理较麻烦,可采用最大堆维护或暂时不精确修改
  3. timeout 处理

    • NULL:阻塞等待
    • 时间为 0:非阻塞模式
    • Linux 中返回时会修改 timeout 为剩余时间,重复使用需重新初始化
  4. 返回值利用

    • 正数表示就绪事件总数,可优化处理流程(如已知只有 1 个事件,可减少遍历)
5. select 的缺点
  1. 描述符数量限制:受 FD_SETSIZE 限制(Linux 默认 1024)
  2. 效率问题
    • 需要遍历所有监听的描述符判断是否就绪(FD_ISSET)
    • nfds 设计不彻底,即使只监听 0 和 1000,也需遍历 1001 个描述符
  3. 使用复杂度:需手动维护描述符集合和最大值


一、epoll 核心函数

epoll 是 Linux 特有的 IO 多路复用机制,通过三个核心函数实现高效的事件监听,解决了 select/poll 的性能缺陷。

函数原型功能
int epoll_create(int size);创建 epoll 实例,返回 epoll 文件描述符(epfd)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);管理 epoll 实例中的事件(添加 / 修改 / 删除文件描述符及监听事件)
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);等待 epoll 实例中的就绪事件,返回就绪事件数量
1. 关键结构体:struct epoll_event

用于描述监听的事件类型及关联数据,定义如下:

struct epoll_event {uint32_t events;  // 监听的事件类型(如EPOLLIN、EPOLLOUT)epoll_data_t data; // 关联的数据(可存储文件描述符、自定义指针等)
};// data的联合体定义,支持多种数据类型
typedef union epoll_data {void *ptr;    // 自定义指针(可关联业务数据)int fd;       // 待监听的文件描述符uint32_t u32;uint64_t u64;
} epoll_data_t;

常用事件类型:

  • EPOLLIN:文件描述符可读(如客户端发送数据、连接关闭)
  • EPOLLOUT:文件描述符可写(如发送缓冲区空闲)
  • EPOLLERR:文件描述符发生错误(无需主动设置,内核自动触发)
  • EPOLLET:开启边沿触发模式(默认是水平触发)
2. 各函数参数与返回值详解
函数参数说明返回值
epoll_createsize:早期版本用于指定监听描述符上限,现在已忽略(需传≥0 的值)成功:epfd(非负整数);失败:-1
epoll_ctlepfd:epoll 实例的文件描述符
op:操作类型(EPOLL_CTL_ADD添加、EPOLL_CTL_MOD修改、EPOLL_CTL_DEL删除)
fd:待管理的文件描述符
event:监听的事件及关联数据(删除时可传 NULL)
成功:0;失败:-1
epoll_waitepfd:epoll 实例的文件描述符
events:输出参数,存储就绪的事件列表
maxeventsevents数组的最大长度(需≤epoll 实例中监听的描述符总数)
timeout:超时时间(毫秒,-1 表示阻塞等待,0 表示非阻塞)
成功:就绪事件数量(>0);超时:0;失败:-1
二、epoll 的核心优势(对比 select/poll)

epoll 通过底层设计优化,解决了 select/poll 的性能瓶颈,具体优势如下:

  1. 无监听描述符数量限制

    • 突破 select 的FD_SETSIZE(默认 1024)限制,仅受进程最大打开文件描述符数(可通过ulimit调整)约束
    • poll 虽也无数量限制,但性能随描述符增多下降,epoll 无此问题
  2. O (1) 级别的监听性能

    • select/poll 采用「轮询」机制,需遍历所有监听描述符判断是否就绪(O (n) 复杂度)
    • epoll 采用「事件驱动」机制:内核维护就绪事件列表,描述符就绪时主动上报,无需轮询(O (1) 复杂度),监听大量描述符时性能优势显著
  3. 减少用户态与内核态数据拷贝

    • select/poll 每次调用需将监听的描述符集合从用户态拷贝到内核态,频繁调用开销大
    • epoll 通过「共享内存」存储监听的事件信息,仅在初始化(添加描述符)和就绪时(返回事件)少量拷贝,大幅降低开销
  4. 直接返回就绪事件列表

    • select/poll 返回后,需遍历所有监听描述符(通过FD_ISSET)判断是否就绪,存在无效遍历
    • epoll 的epoll_wait直接返回就绪的事件列表(events数组),无需额外判断,直接处理即可
三、epoll 的特殊特性
  1. 两种触发模式

    • 水平触发(LT,Level-Triggered):默认模式
      只要文件描述符处于就绪状态(如可读),每次调用epoll_wait都会返回该事件,直到数据被完全处理。
      优势:编程简单,兼容性好(类似 select/poll 的行为)。
    • 边沿触发(ET,Edge-Triggered):需手动设置EPOLLET标志
      仅在文件描述符「状态由未就绪变为就绪」时触发一次事件(如从无数据到有数据),后续即使有新数据,若未处理完也不会再次触发。
      优势:减少事件触发次数,性能更高;需注意:必须使用非阻塞 IO,且需一次性处理完所有数据(避免遗漏)。
  2. 资源管理注意事项

    • epoll_create创建的 epfd 是文件描述符,使用后需调用close(epfd)释放,避免资源泄漏
    • 当不再监听某个文件描述符时,需通过epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL)删除,避免无效事件触发
四、epoll 的适用场景

epoll 的高性能依赖「大量监听描述符 + 少量就绪事件」的场景,典型应用:

  • 高并发服务器(如 Nginx、Redis):需同时监听成千上万的客户端连接,且每次就绪的连接数较少
  • 网络 IO 密集型场景:避免轮询带来的性能损耗,提升系统吞吐量

文章转载自:

http://CiUFBN5C.tqjks.cn
http://LuhORFcP.tqjks.cn
http://VftEmADV.tqjks.cn
http://csfH5Gc7.tqjks.cn
http://80X4tDpL.tqjks.cn
http://mg5OXh3I.tqjks.cn
http://HpdzlyVz.tqjks.cn
http://HGTvPrLy.tqjks.cn
http://nX3sgaax.tqjks.cn
http://WgKLz3ua.tqjks.cn
http://xORYv8Oq.tqjks.cn
http://PTwEKqAu.tqjks.cn
http://0M5Nq8mY.tqjks.cn
http://TfsxZKqG.tqjks.cn
http://ykTchHdR.tqjks.cn
http://qMhOt3jq.tqjks.cn
http://gSfsRUeM.tqjks.cn
http://Fulhyse4.tqjks.cn
http://u0K2LsuW.tqjks.cn
http://2cFbsFGx.tqjks.cn
http://VtQhyDUW.tqjks.cn
http://VZzoNnoo.tqjks.cn
http://Qd6DRusz.tqjks.cn
http://eNS86Zer.tqjks.cn
http://R3MCQCMk.tqjks.cn
http://FdHxy991.tqjks.cn
http://YjKWaOVb.tqjks.cn
http://NsUemgES.tqjks.cn
http://IIGmCCp1.tqjks.cn
http://sGbN6Xv1.tqjks.cn
http://www.dtcms.com/a/368509.html

相关文章:

  • 硬件 - 关于MOS的使用
  • 什么是selenium自动化测试
  • 【智启未来园区】从“管理”到“治理”,重新定义智慧园区新范式!
  • 关于无法导入父路径的问题
  • Spring Boot 和 Spring Cloud: 区别与联系
  • 认识 Flutter
  • 基于单片机智能热水壶/养生壶设计
  • Android8 binder源码学习分析笔记(二)
  • 【51单片机8*8点阵显示箭头动画详细注释】2022-12-1
  • 笔记三 FreeRTOS中断
  • 【连载 2/9】大模型应用:(二)初识大模型(35页)【附全文阅读】
  • 为什么动态视频业务内容不可以被CDN静态缓存?
  • 【视频系统】技术汇编
  • 如何提升技术架构设计能力?
  • 【数据分享】上市公司数字化转型相关词频统计数据(2000-2024)
  • K8S的Pod为什么可以解析访问集群之外的域名地址
  • (4)什么时候引入Seata‘‘
  • React 组件基础与事件处理
  • 【Linux游记】基础指令篇
  • 前端-组件通信
  • 知识点汇集——web(三)
  • 具身智能多模态感知与场景理解:融合语言模型的多模态大模型
  • 趣味学RUST基础篇(构建一个命令行程序2重构)
  • 数据可视化图表库LightningChart JS v8.0上线:全新图例系统 + 数据集重构
  • spring事物失效场景
  • Win官方原版镜像站点推荐
  • Linux文件描述符详解
  • 一个月学习刷题规划详解
  • 云计算学习笔记——日志、SELinux、FTP、systemd篇
  • Spring DI详解--依赖注入的三种方式及优缺点分析