我理解的epoll(一)——实现分析
epoll项目中用了几次,但是对于其原理只是一知半解。我希望通过几篇blog能加深对她的理解。
我认为epoll是同步IO,因为他在调用epoll_wait时,内核在有I/O就绪前是阻塞的,虽然可以将timeout设置为0,此时就是非阻塞的了。但这不是变成忙轮询了么?
select和epoll的比较
select的缺点:
1、支持一个进程打开大数目的socket描述符(FD):epoll支持的数目是进程打开文件的数目,具体数目可以cat /proc/sys/fs/file-max察看,差不多10玩非。而select只支持1024。虽然后来可以自己修改,但是引入了第二个缺点。
2、IO效率不随FD数目增加而线性下降:虽然监听的FD很多,但是很多时候只有少数处于活跃,但是每次调用select还是不得不轮询所有的预监听的FD集合,导致效率成线性下降。因为select只告知用户准备好的fd的数目,没有告知具体是哪些FD准备好了。epoll不存在这样的问题,epoll_wait得到的是准备好的fd的集合。每一次扫描该集合都是有效的。
3、每次调用select都需要重新设置fdset,并重新传到内核,而epoll只需要调用epoll_ctrl对已经传到内核的fdset进行add,del或者modify操作。
epoll的简单实现
知道这些缺点并没什么卵用,必须弄清楚其内部实现,才能对以上差异了解的更加透彻。这些差异中,最重要的是epoll性能不会随着FD的增加呈线程下降。这是怎么做到的呢。这要从epoll_create说起。
int epfd = epoll_create(size); //size代表一次监听的最大FD数目,因为早起版本是用hashtable实现的,现在是rbtree,所以size已经没有意义了。
疑问:epoll_create为何要返回一个fd?后面在回答。
epoll_create的时候创建了一个 struct eventpoll 结构体(内核自己创建的),每次创建epoll_create时,返回一个文件描述符epfd,内核就是通过这个数据结构来管理epoll的,或者说这个fd和这个struct evenpoll绑定了。
struct eventpoll { spin_lock_t lock; //对本数据结构的访问 struct mutex mtx; //防止使用时被删除 wait_queue_head_t wq; //sys_epoll_wait() 使用的等待队列 wait_queue_head_t poll_wait; //file->poll()使用的等待队列 struct list_head rdllist; //事件满足条件的链表 struct rb_root rbr; //用于管理所有fd的红黑树(树根) struct epitem *ovflist; //将事件到达的fd进行链接起来发送至用户空间 }
当向系统中添加一个fd时,就创建一个epitem结构体,这是内核管理epoll的基本数据结构:
struct epitem { struct rb_node rbn; //用于主结构管理的红黑树 struct list_head rdllink; //事件就绪队列 struct epitem *next; //用于主结构体中的链表 struct epoll_filefd ffd; //这个结构体对应的被监听的文件描述符信息 int nwait; //poll操作中事件的个数 struct list_head pwqlist; //双向链表,保存着被监视文件的等待队列,功能类似于select/poll中的poll_table struct eventpoll *ep; //该项属于哪个主结构体(多个epitm从属于一个eventpoll) struct list_head fllink; //双向链表,用来链接被监视的文件描述符对应的struct file。因为file里有f_ep_link,用来保存所有监视这个文件的epoll节点 struct epoll_event event; //注册的感兴趣的事件,也就是用户空间的epoll_event }
struct evenpoll结构中最重要的就是 struct rb_root rbr 和 struct list_head rdllist 。struct rb_node rbn是红黑树的根结点,epoll_ctrl向内核注册的要监听的fd都在这棵树上操作,比如add,del,modify。同时epoll_create还创建了一个事件满足条件的链表struct list_head rdllist,存放着就绪事件。
这样说来,内核中维护了一棵红黑树,大致的结构如下:
epol_wait得到的就绪事件都是从struct list_head rdllist中拷贝出来的。这是怎么做到的呢?epoll_ctl的时候还向内核中断处理程序注册一个回调函数ep_poll_callback,告诉内核,如果这个fd的中断到了,就把它放到准备就绪list链表里(rdllist)。所以,ep_poll_callback函数主要的功能是将被监视文件的等待事件就绪时,将文件对应的epitem实例添加到就绪队列中,当用户调用epoll_wait()时,内核会将就绪队列中的事件报告给用户。
介绍完epoll的大体流程,说一下epoll_ctl的函数原型:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd:epoll_create创建的文件句柄。
op:有三个值。EPOLL_CTL_ADD、EPOLL_CTL_DEL、EPOLL_CTL_MOD。 看字面意思就知道了怎么用了,其中由于struct eventpoll内部是红黑树,所以反复对同一个fd进行EPOLL_CTL_ADD没有效果。
fd:要监听的文件描述符。
event:是一个和fd关联的结构体,内部结构如下:
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t; struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
一般使用方式为:event.data.fd = fd,还要指定事件类型和触发方式:event.events = EPOLLIN | EPOLLET。
关于触发LT和ET触发方式,下文再说。epoll支持的事件类型,已经有很多资料了,就不细说了。
下面简要分析一下epoll的工作过程:
(1)epoll_wait调用ep_poll监听事件就绪队列rdlist,为空挂起,不为空被唤醒。
(2)fd状态改变,导致fd对应的ep_poll_callback被调用,将fd关联的epitem结构放到rdlist上。
(3)ep_events_transfer将rdllist上epitem的拷贝到txlist中,并将rdlist清空。
(4)ep_send_events(很关键)将扫描txlist上的每个epitem,并调用其关联fd对用的poll方法,取得fd关联的新的events,将fd和封装到struct epoll_event内,发送到用户态。通过epoll_wait返回。之后如果这个epitem对应的fd是LT模式监听且取得的events是用户所关心的,则将其重新加入回rdlist,否则(ET模式)不在加入rdlist。
ET事件发生仅通知一次的原因是只被添加到rdlist中一次,而LT可以有多次添加的机会。
有两种情况下ep_poll_callback会添加rdlist:
情况1:fd状态发生改变,从不可读变成可读,或者从不可写变成可写。
情况2:fd可读,或可写。
LT模式下,情况1、2都可以触发ep_poll_callback添加rddlist。
ET模式下,只有情况1能触发ep_poll_callback添加rddlist。
我理解的epoll(一)——实现分析的更多相关文章
- epoll源码分析
epoll源码分析 最近在使用libev过程中遇到一个场景:一个fd从一个ev_loop迁移到另一个ev_loop,会出现这个fd同时存在两个epoll的瞬间.不禁要问了,一个fd同时被两个epoll ...
- epoll源码分析(基于linux-5.1.4)
API epoll提供给用户进程的接口有如下四个,本文基于linux-5.1.4源码详细分析每个API具体做了啥工作,通过UML时序图理清内核内部的函数调用关系. int epoll_create1( ...
- (转)浅析epoll – epoll例子以及分析
原文地址:http://www.cppfans.org/1419.html 浅析epoll – epoll例子以及分析 上篇我们讲到epoll的函数和性能.这一篇用用这些个函数,给出一个最简单的epo ...
- 我理解的epoll(二)——ET、LT的实例分析
https://www.cnblogs.com/yuuyuu/p/5103744.html 这篇文章已经写的很清楚了,暂时不展开论述了. http://blog.csdn.net/weiyuefei/ ...
- select, poll, epoll的实现分析
select, poll, epoll都是Linux上的IO多路复用机制.知其然知其所以然,为了更好地理解其底层实现,这几天我阅读了这三个系统调用的源码. 以下源代码摘自Linux4.4.0内核. 预 ...
- epoll源码分析(转)
在create后会创建eventpoll对象保存在一个匿名fd的file struct的private指针中,然后进程睡在等待队列上面. 对于等待的fd,通过poll机制在准备好之后会调用相应的cal ...
- Linux内核分析--理解进程调度时机、跟踪分析进程调度和进程切换的过程
ID:fuchen1994 姓名:江军 作业要求: 理解Linux系统中进程调度的时机,可以在内核代码中搜索schedule()函数,看都是哪里调用了schedule(),判断我们课程内容中的总结是否 ...
- epoll实现机制分析
本文只介绍epoll的主要流程而不是分析源代码,如果需要了解更多的细节可以自己翻阅相关的内核源代码. 相关内核代码: fs/eventpoll.c 判断一个tcp套接字上是否有激活事件:net/ipv ...
- 深入理解JVM一java堆分析
上一节介绍了针对JVM的监控工具,包括JPS可以查看当前所有的java进程,jstack查看线程栈可以帮助你分析是否有死锁等情况,jmap可以导出java堆文件在MAT工具上进行分析等等.这些工具都非 ...
随机推荐
- C# Winform中WebBrowser给网页中的input控件赋值/设置值
订阅WebBrowser的DocumentCompleted事件,在里面写入 private void browser_DocumentCompleted(object sender, WebBrow ...
- windows 下OPENSSL 生成秘钥和公钥的方法
1. 生成原始 RSA私钥文件 private_key.pem openssl genrsa -out private_key.pem 1024 2. 将原始 RSA私钥转换为 pkcs8格式 ope ...
- Day04:循环结构(while、do-while、for)
Java 循环结构 - while ,do...while,for 反复执行一段相同或相似代码的格式. 顺序结构的程序语句只能被执行一次.如果您想要同样的操作执行多次,,就需要使用循环结构. Java ...
- postman 发送json 格式数据
hearder 设置 body 设置 { ", "primary_content": "{\"SN\":129,\"MPBH\&q ...
- C语言tips_2 关于scanf 读取规则小结以及与getchar 的区别
第一点:scanf默认回车和空格是输入不同组之间的间隔和结束符号. 也就是说他不会读取 空格 和 换行符.而是把他们当作一个 数据被读取完成的标志!他的停止标志则为,当%d之类的数据输入结束之后,自动 ...
- Qt——鼠标拖动缩放窗口源码
#ifndef MOVEWIDGET_H #define MOVEWIDGET_H #include <QWidget> #include <QEvent> class Mov ...
- centOS 安装 pm2
安装 npm install -g pm2 自启 pm2 startup centos 启动 pm2 start server/add.js (以实际路径为准) 其他命令: pm2 list #查看进 ...
- 最新 汇量科技java校招面经 (含整理过的面试题大全)
从6月到10月,经过4个月努力和坚持,自己有幸拿到了网易雷火.京东.去哪儿. 汇量科技等10家互联网公司的校招Offer,因为某些自身原因最终选择了 汇量科技.6.7月主要是做系统复习.项目复盘.Le ...
- idea开发shell脚本并运行
参考:https://blog.csdn.net/u012443641/article/details/81295999 IEDA中的bashsupport插件支持在IDEA中编写shell脚本文件, ...
- vue移动端弹框组件
最近做一个移动端项目,弹框写的比较麻烦,查找资料,找到了这个组件,但是说明文档比较少,自己研究了下,把我碰到的错,和详细用法分享给大家!有疑问可以打开组件看一看,这个组件是仿layer-mobile的 ...