定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力

作用:
应用程序通常需要处理来自多条事件流中的事件

可以使用并发(多线程和多进程),但成本较大,开销较大

IO多路复用解决的本质问题是在用更少的资源完成更多的事。

IO模型

1、阻塞IO   闲等待 
2、非阻塞IO  EAGAIN  忙等待 errno
3、信号驱动IO  SIGIO 用的相对少(了解)
4、并行模型 进程,线程
5, IO多路复用  select、poll、epoll

阻塞IO

 ===》最常用 默认设置

非阻塞IO 

===》在阻塞IO的基础上调整其为不再阻塞等待。
 在程序执行阶段调整文件的执行方式为非阻塞:
===》fcntl() ===>动态调整文件的阻塞属性

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能:修改指定文件的属性信息。
参数:fd 要调整的文件描述符
  cmd 要调整的文件属性宏名称
  ... 可变长的属性值参数。
返回值:成功  不一定,看cmd
失败  -1;

eg:修改文件的非阻塞属性:
int flag ;
flag  = fcntl(fd,F_GETFL,0);  ///获取fd文件的默认属性到flag变量中。
flag  = flag | O_NONBLOCK;    ///将变量的值调整并添加非阻塞属性
fcntl(fd,F_SETFL,flag);       ///将新属性flag设置到fd对应的文件生效。

以上代码执行后的阻塞IO将变成非阻塞方式。

信号驱动io 


文件描述符需要追加 O_ASYNC 标志。
设备有io事件可以执行时,内核发送SIGIO信号。

1.追加标志
int flag ;
flag  = fcntl(fd,F_GETFL,0);
fcntl(fd,F_SETFL,flag | O_ASYNC);    
2.设置信号接收者
fcntl(fd,F_SETOWN,getpid());//常用设置
3.对信号进行捕获
signal(SIGIO,myhandle);//

并发 


1.进程
2.线程

 IO 多路复用 

===》并发服务器 ===》TCP协议

select循环服务器 ===> 用select函数来动态检测有数据流动的文件描述符
 

select函数

1.创建集合
2. 加入fd 
3. select 轮询
4. 找到对应的fd r/w
 

int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
功能:完成指定描述符集合中有效描述符的动态检测。
  该函数具有阻塞等待功能,在函数执行完毕后
  目标测试集合中将只保留最后有数据的描述符。

参数:nfds 描述符的上限值,一般是链接后描述符的最大值+1;
  readfds 只读描述符集
  writefds 只写描述符集
  exceptfds 异常描述符集
  以上三个参数都是 fd_set * 的描述符集合类型
  timeout  检测超时 如果是NULL表示一直检测不超时 。

返回值:超时 0
失败  -1
成功 >0


为了配合select函数执行,有如下宏函数:
void FD_CLR(int fd, fd_set *set);
功能:将指定的set集合中编号为fd的描述符号删除。

int  FD_ISSET(int fd, fd_set *set);
功能:判断值为fd的描述符是否在set集合中,
  如果在则返回真,否则返回假。

void FD_SET(int fd, fd_set *set);
功能:将指定的fd描述符,添加到set集合中。

void FD_ZERO(fd_set *set);
功能:将指定的set集合中所有描述符删除。
 

epoll函数

epoll_create
epoll_ctl
epoll_wait

1.创建集合(底层是一个二叉树)

2.添加fd,放一个结构体

3.等待事件触发

4.找到对应的fd

select和epoll的区别

小结

1.  select  集合大小 (0~1023)  epoll 2k,3k  10K .
2. 检测方式 select  轮询 epoll 主动上报(有设备的中断触发) 
3. 找fd 有差异 。 select ,需要在原始集合查找(就绪的fd+未就绪fd)  epoll  专门有一个 就绪集合,范围小很多。
    4. select 的集合会在内核层和用户层多次复制。 epoll 集合在共享内存中。

详解

1. select
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

select的调用一般要注意几点:
① readfds等是指针结果参数,会被函数修改,所以一般会另外定义一个allread_fdset,保持全部要监听读的句柄,将它的拷贝传递给select函数,返回可读的句柄集合,类型fdset支持赋值运算符=;
② 要注意计算nfds,当新增监听句柄时比较容易修改,当减少监听句柄时较麻烦些,如果要精确修改需要遍历或者采用最大堆等数据结构维护这个句柄集,以方便的找到第二大的句柄,或者干脆在减少监听句柄时不管nfds;
③ timeout如果为NULL表示阻塞等,如果timeout指向的时间为0,表示非阻塞,否则表示select的超时时间;
④ select返回-1表示错误,返回0表示超时时间到没有监听到的事件发生,返回正数表示监听到的所有事件数(包括可读,可 写,异常),通常在处理事件时 会利用这个返回值来提高效率,避免不必要的事件触发检查。(比如总共只有一个事件,已经在可读集合中处理了一个事件,则可写和异常就没必要再去遍历句柄集 判断是否发生事件了);
⑤ Linux的实现中select返回时会将timeout修改为剩余时间,所以重复使用timeout需要注意。

select的缺点在于:
① 由于描述符集合set的限制,每个set最多只能监听FD_SETSIZE(在Linux上是1024)个句柄(不同机器可能不一样);
② 返回的可读集合是个fdset类型,需要对所有的监听读句柄一一进行FD_ISSET的测试来判断是否可读;
③ nfds的存在就是为了解决select的效率问题(select遍历nfds个文件描述符,判断每个描述符是否是自己关心的,对关心的描述符判断是否发生事件)。但是解决不彻底,比如如果只监听0和1000两个句柄,select需要遍历1001个句柄来检查事件。


3. epoll
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epoll 解决了select和poll的几个性能上的缺陷:①不限制监听的描述符个数(poll也是),只受进程打开描述符总数的限制;②监听性能不随着监听描述 符数的增加而增加,是O(1)的,不再是轮询描述符来探测事件,而是由描述符主动上报事件;③使用共享内存的方式,不在用户和内核之间反复传递监听的描述 符信息;④返回参数中就是触发事件的列表,不用再遍历输入事件表查询各个事件是否被触发。
epoll显著提高性能的前提是:监听大量描述符,并且每次触发事件的描述符文件非常少。
epoll的另外区别是:①epoll创建了描述符,记得close;②支持水平触发和边沿触发。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐