I/O多路复用器之隐秘的角落
上一篇文章讲到了Unix的I/O模型,以及在java中的具体实现,其中在java中我们最为关注的就是 I/O 复用了,这篇主要总结下I/O多路复用器。
概念:文件描述符fd
Linux的内核将所有外部设备都可以看做一个文件来操作。那么我们对与外部设备的操作都可以看做对文件进行操作。我们对一个文件的读写,都通过调用内核提供的系统调用;内核给我们返回一个filede scriptor(fd,文件描述符)。而对一个socket的读写也会有相应的描述符,称为socketfd(socket描述符)。描述符就是一个数字,指向内核中一个结构体(文件路径,数据区,等一些属性)。那么我们的应用程序对文件的读写就通过对描述符的读写完成。
复用器
1. select
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
参数解析
- readset、writeset、exceptset 指定我们要让内核测试读、写和异常条件的描述符。它们的类型fd_set是一个BitMap的设计,32位操作系统内核设置它1024位,64位操作系统则设置为2048位,拿readset为例,如果描述符为3的读时间被关注,那么readset的第三位会被置为1。
- maxfdp1 指定待测试对的描述符个数,它的值是待测试的最大描述符加1(如其名),它就是在readset、writeset、exceptset三个描述符集中找出最大描述符编号值,然后加1。表示0~(maxfgp1 - 1)位均将被内核关注。
- timeout 就是超时时间,它的结构精确到了微秒,相对更为精准;
- 返回 若有就绪描述符则返回其数目,若超时则返回0,若出错则返回-1;
操作过程
- select执行,CPU由用户态转为内核态,同时这些参数也拷贝到内核态,由内核进行判断所关注的0~(maxfgp1 - 1)描述符是否有事件,如果没有,则阻塞,如果有,则对有事件发生的FD,进行置位(其实是对没有发生事件的置位为0),select返回;
- 程序接下来还需要重新遍历0~(maxfgp1 - 1)的FD,对已经被置位的FD进行处理;
- readset、writeset、exceptset 3个数据对关注的FD置位,因为已经被内核修改了,所以需要每次都要重新设置一下;
缺点
- FD的数目有上限,意味着最大连接数:x86机器为1024,x64为2048;
- 每次调用,都会发生上下文切换,而且都需要将3个fd_set数据结构传入内核;
- fd_set不可重用,每次都需重新置位;
- 2次对关注FD进行遍历,时间复杂度为O(n);
2. poll
int poll(struct pollfd *fdarray, unsigned nfds,int timeout);
参数解析
- fdarray
fdarray是一个链表的结构,数据结构如下:
struct pollfd{
int fd; // 需要关注的文件描述符
short events;// 关注的事件类型
short revents;// 发生的事件
}
poll相对于select的改进主要是在这个结构体上,由数组到链表,解决了描述符有上限的问题,并且将结果和参数分离,只需要重置revents就可以了,而不需要重新申请整个结构;
- nfds 代表元素的个数
- timeout 超时时间
- 返回 若有就绪描述符则返回其数目,若超时则返回0,若出错则返回-1;
缺点
经过select->poll的改进,还剩下select的2个缺点、
- 每次调用,都会发生上下文切换,而且都需要将3个fd_set数据结构传入内核;
- 2次对关注FD进行遍历,时间复杂度为O(n);
3. epoll
epoll在poll和select的缺点之上做了重大改进,但是逻辑也更为复杂。它有三个函数:
int epoll_create(int size)
直接在内核创建保存文件描述符的空间。epoll的结构采用红黑树保存,epoll_create可视为初始化root节点。调用epoll_create所创建的文件描述符保存空间称为“epoll例程”。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):
与select函数类似,等待文件描述符发生变化;
参数解析
- epfd:表示事件发生监视范围的epoll例程的文件描述符
- events:用于保存发生事件的文件描述符集合的链表
- maxevents:告诉内核第二个参数events的大小
- timeout:超时时间
操作过程
存储FD的数据结构直接改由在内核的维护,我们便不再需要重复多次从用户态copy到内核态,只需要实时维护发生变化的FD到内核就可以了;用events来存储发生事件的fd,则无需再遍历整个被关注的描述符集合。
操作模式
epoll对文件描述符的操作有2种模式,默认模式是水平触发。
- 水平触发(level trigger) 当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。水平触发同时支持block和none-blocking
- 边缘触发(edge trigger) 当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。边缘触发是高速工作方式,只支持none-blocking
4. 总结
select |
poll |
epoll |
|
---|---|---|---|
操作方式 |
遍历 |
遍历 |
回调 |
底层实现 |
数组 |
链表 |
红黑树 |
IO效率 |
每次调用都进行线性遍历,时间复杂度为O(n) |
每次调用都进行线性遍历,时间复杂度为O(n) |
事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面,时间复杂度O(1) |
最大连接数 |
1024(x86)或2048(x64) |
无上限 |
无上限 |
fd拷贝 |
每次调用select,都需要把fd集合从用户态拷贝到内核态 |
每次调用poll,都需要把fd集合从用户态拷贝到内核态 |
调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝 |
- 洛谷 P1972 [SDOI2009]HH的项链【莫队算法学习】
- BZOJ 2257: [Jsoi2009]瓶子和燃料【数论:裴蜀定理】
- 在 EF 5 中跟踪SQL和缓存数据
- hihoCoder #1015 : KMP算法【KMP裸题,板子】
- 对X86汇编的理解与入门
- BZOJ 2748: [HAOI2012]音量调节【二维dp,枚举】
- POJ 3264 Balanced Lineup【线段树区间查询求最大值和最小值】
- HDU 2289 Cup【高精度,二分】
- BZOJ 1083: [SCOI2005]繁忙的都市【Kruscal最小生成树裸题】
- [快学Python3]二分查找[策略优化版本]
- 微服务与SOA架构(4)
- 移动测试Appium之API手册
- BZOJ 1088: [SCOI2005]扫雷Mine【思维题,神奇的模拟+枚举】
- 浅谈关于特征选择算法与Relief的实现
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 一天一大 lee(组合总和 II)难度:中等-Day20200910
- 一天一大 lee(组合总和 III)难度:中等-Day20200911
- 一天一大 lee(翻转二叉树)难度:简单-Day20200916
- 一天一大 lee(表示数值的字符串)难度:中等-Day20200902
- 一天一大 lee(单词搜索)难度:简单-Day20200913
- 一天一大 lee(克隆图)难度:中等-Day20200813
- 一天一大 lee(相同的树)难度:简单-Day20200807
- 一天一大 lee(解数独)难度:困难-Day20200915
- 一天一大 lee(复原IP地址)难度:中等-Day20200809
- 一天一大 lee(计数二进制子串)难度:简单-Day20200810
- 一天一大 lee(打家劫舍 III)难度:中等-Day20200805
- 一天一大 lee(课程表)难度:中等-Day20200804
- 一天一大 leet(二叉树展开为链表)难度:中等-Day20200802
- 一天一大 leet(字符串相加)难度:简单-Day20200803
- 一天一大 lee(恢复二叉搜索树)难度:困难-Day20200808