TCP源码—epoll源码及测试
一、epoll_create & epoll_create1
SYSCALL_DEFINE1(epoll_create, int, size)
sys_epoll_create->sys_epoll_create1
SYSCALL_DEFINE1(epoll_create1, int, flags)
sys_epoll_create1(入参检测等)->ep_alloc(分配eventpoll,并初始化锁、等待队列等结构)->[sys_epoll_create1]get_unused_fd_flags(分配fd)->[sys_epoll_create1]anon_inode_getfile(分配file)->[sys_epoll_create1]fd_install(关联file和fd)
anon_inode_getfile:
该函数创建的文件共享使用一个inode,节省内存避免代码重复,inode:anon_inode_inode、 sb:anon_inode_mnt->mnt_sb、 fs:anon_inode_fs_type,初始化位置[anon_inode_init]。
使用alloc_file分配一个文件,file->f_op = eventpoll_fops;
设置file->f_flags = (O_RDWR | (flags & O_CLOEXEC)) & (O_ACCMODE | O_NONBLOCK); file->private_data =ep;
其中eventpoll_fops注册了ep_show_fdinfo函数,允许我们在proc中查看对应epfd的epi信息
lybxin@Inspiron:~$more /proc/1469/task/1469/fdinfo/4
pos: 0
flags: 02000002
mnt_id: 11
tfd: 5 events: 19 data: 55b8247c8da0
tfd: 13 events: 19 data: 55b8247ccc90
tfd: 14 events: 19 data: 55b8248079a0
tfd: 9 events: 19 data: 55b8247e9860
tfd: 12 events: 1a data: 55b8247e9b40
tfd: 8 events: 19 data: 55b8247caf90
tfd: 7 events: 19 data: 55b824806490
二、epoll_ctl
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,struct epoll_event __user *, event)
sys_epoll_ctl:
如果不是EPOLL_CTL_DEL操作,则从用户空间复制epoll_event结构
获取fd结构,epfd->f,需要操作的fd->tf
目标fd对应的fd结构必须支持tf.file->f_op->poll操作
处理EPOLLWAKEUP标志
检验epfd对应有效的epoll文件描述符,且需要操作的fd与epfd不是对应同一个文件
通过ep_loop_check检测epfd是否构成闭环或者连续epfd的深度超过5,对应宏EP_MAX_NESTS[4]
通过ep_find查找这个epfd是否已经添加了目标fd文件
ADD/MOD操作自动添加POLLERR | POLLHUP这两个标志位
ep_loop_check:
visited_list:表示已经处理过的节点,假设epfd1下挂epfd2和epfd3,而epfd2和epfd3又同时挂epfd4,那么保证epfd只处理一次
tfile_check_list:保存非epoll文件的fd用于反向检查
从源码和下面的测试来看这个闭环和深度检测只能从添加的fd向下检测,而不能向上检测,因此并不是所有场景都能有效的检测出来,如下测试,另外还有一种场景因为会跳过已经visit的节点,所以visit的节点的最大深度也可能会超过5。
---------------test1 start--------------- //正向查找只检测target fd
add epfd2 to epfd1:add 1 success
add epfd3 to epfd2:add 2 success
add epfd4 to epfd3:add 3 success
add epfd5 to epfd4:add 4 success
add epfd6 to epfd5:add 5 success
add epfd7 to epfd6:add 6 success
add epfd8 to epfd7:add 7 success
add epfd9 to epfd8:add 8 success
---------------test1 end---------------
---------------test2 start--------------- //正向查找只检测target fd
add epfd1 to epfd2:add 1 success
add epfd2 to epfd3:add 2 success
add epfd3 to epfd4:add 3 success
add epfd4 to epfd5:add 4 success
add epfd5 to epfd6:add 5 success
add epfd6 to epfd7:epoll_ctl error:Too many levels of symbolic links(errno:40)
add epfd7 to epfd8:add 6 success
add epfd8 to epfd9:add 7 success
---------------test2 end---------------
---------------test3 start--------------- //正向查找形成闭环 操作失败
add epfd2 to epfd1:add 1 success
add epfd3 to epfd2:add 2 success
add epfd1 to epfd3:epoll_ctl error:Too many levels of symbolic links(errno:40)
---------------test3 end---------------
ep_insert:
max_user_watches:/proc/sys/fs/epoll/max_user_watches 每个用户同时watch的最大fd数目
如果watch的总数超过max_user_watches,则返回ENOSPC
如果从epi_cache分配epitem失败,则返回ENOMEM
根据EPOLLWAKEUP标志注册wake up
通过ep_item_poll把item添加到poll钩子中,并获取当前revents。最终会通过ep_ptable_queue_proc函数把eppoll_entry添加到sk->sk_wq->wait的头部,并通过pwq->llink添加到epi->pwqlist的尾部。这里每个epi对应一个pwqlist链表的原因是poll一些文件的时候,需要添加两次等待队列,如/dev/bsg/目录下面的文件。
把epi插入到f_ep_links链表的尾部,list_add_tail_rcu(&epi->fllink, &tfile->f_ep_links);
把epi插入到ep的红黑树中,ep_rbtree_insert(ep, epi);
通过reverse_path_check进行反向检查
如果获取到的revents中有用户关注的事件,并且epi未在ready链表中,那么把epi插入ready链表尾部 list_add_tail(&epi->rdllink, &ep->rdllist);并尝试唤醒epoll_wait进程wake_up_locked(&ep->wq);以及file->poll()等待进程ep_poll_safewake(&ep->poll_wait)
自增ep->user->epoll_watches
reverse_path_check:
对于第一层反向检查不限制数目。
对于第2-5层,限制引用数目分别为500、100、50、10,如下变量定义了上限,其中该变量第一个成员1000仅作占位使用,并不限制第一层引用总数,参考path_count_inc。static const int path_limits[PATH_ARR_SIZE] = { 1000, 500, 100, 50, 10 };
对于5层以上则直接返回错误
---------------test4 反向查找--------------- //反向查找层数超过5
add epfd2 to epfd1:add 1 success
add epfd3 to epfd2:add 2 success
add epfd4 to epfd3:add 3 success
add listen_fd to epfd4:add 4 success
add epfd5 to epfd4:add 5 success
add listen_fd to epfd5:add 6 success
add epfd6 to epfd5:add 7 success
add listen_fd to epfd6:epoll_ctl error:Invalid argument(errno:22)
add epfd7 to epfd6:add 8 success
add listen_fd to epfd7:epoll_ctl error:Invalid argument(errno:22)
add epfd8 to epfd7:add 9 success
add epfd9 to epfd8:add 10 success
add listen_fd to epfd9:epoll_ctl error:Invalid argument(errno:22)
---------------test4 end---------------
---------------test5 反向查找 i:0 --------------- //第一层反向查找直到fd数目的上限才会失败添加第一层
添加第一层 add error num:1021 error:Bad file descriptor(errno:9)
---------------test5 end i:1020 ---------------
---------------test6 反向查找 i:0 --------------- //第二层反向查找的限制为path_limits[1]
第二层 add error i:501 error:Invalid argument(errno:22)
---------------test6 end i:500 ---------------
ep_remove:
移除一个epi
从poll wait中移除
从file的f_ep_links链表移除
从红黑树中移除
从ready链表中移除
取消wakeup注册
自减ep->user->epoll_watches
ep_modify:修改epi
更新epi->event.events和epi->event.data
根据EPOLLWAKEUP更新wake up
刷新内存屏障smp_mb
通过ep_item_poll获取revents,相比ep_insert差异在于并不会调用ep_ptable_queue_proc重新注册
如果获取到的revents中有用户关注的事件,并且epi未在ready链表中,那么把epi插入ready链表尾部 list_add_tail(&epi->rdllink, &ep->rdllist);并尝试唤醒epoll_wait进程wake_up_locked(&ep->wq);以及file->poll()等待进程ep_poll_safewake(&ep->poll_wait)
三、epoll_wait&epoll_pwait
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,int, maxevents, int, timeout)
sys_epoll_pwait->sys_epoll_wait
sys_epoll_wait:主要做参数检查和epfd 的校验,然后通过ep_poll进行操作
ep_poll:
根据入参估计超时时间to和slack,或者设置timed_out标志位
如果epoll_wait入参定时时间为0,那么直接通过ep_events_available判断当前是否有用户感兴趣的事件发生,如果有则通过ep_send_events进行处理
如果定时时间大于0,并且当前没有用户关注的事件发生,则进行休眠,并添加到ep->wq等待队列的头部。 对等待事件描述符设置WQ_FLAG_EXCLUSIVE标志
ep_poll被事件唤醒后会重新检查是否有关注事件,如果对应的事件已经被抢走,那么ep_poll会继续休眠等待。
lybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$./nesttest
-----------test8 测试epoll和accept同时等待的唤醒情况 epfd1:4,epfd:5,listen_fd:3-----------
lybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$ss -tlnap | grep 9877
LISTEN 0 128 *:9877 *:* users:(("nesttest",pid=6895,fd=3),("nesttest",pid=6894,fd=3),("nesttest",pid=6893,fd=3))
lybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$nc 127.0.0.1 9877
epfd2 epoll_wait return:
i:0,nfds:1,fd:3,sec:238
epfd1 epoll_wait return:
i:0,nfds:1,fd:3,sec:238
accept return connfd:6,sec:238
^C
lybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$nc 127.0.0.1 9877
epfd2 epoll_wait return:
accept return connfd:7,sec:240
i:0,nfds:1,fd:3,sec:240
^C
lybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$nc 127.0.0.1 9877
epfd1 epoll_wait return:
accept return connfd:8,sec:242
i:0,nfds:1,fd:3,sec:242
^C
lybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$nc 127.0.0.1 9877
epfd2 epoll_wait return:
accept return connfd:9,sec:244
i:0,nfds:1,fd:3,sec:244
^C
lybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$nc 127.0.0.1 9877
epfd1 epoll_wait return:
i:0,nfds:1,fd:3,sec:246
epfd2 epoll_wait return:
i:0,nfds:1,fd:3,sec:246
accept return connfd:10,sec:246
^C
select_estimate_accuracy:
估计slack,最大为MAX_SLACK(100ms),最小为current->timer_slack_ns(默认值为50000ns,即50 usec),timer_slack_ns可以通过prctl的PR_SET_TIMERSLACK选项设置
nice 进程取定时时间的0.5%,普通进程取0.1%
ep_events_available:
如果ready链ep->rdllist非空或者ep->ovflist有效,则表示当前有关注的event发生
ep_scan_ready_list[ep_send_events]:
epoll_wait的时候传递函数指针ep_send_events_proc给ep_scan_ready_list,epfd进行poll的时候则传递函数指针ep_read_events_proc
把ep->rdllist链接到txlist,并清空ep->rdllist,设置ep->ovflist = NULL,表示当前正在往用户空间发送数据,新事件触发的epi插入到ep->ovflist的头部,参考ep_send_events_proc函数的注释
调用传入的函数指针处理txlist
把ep->ovflist插入到ep->rdllist
设置ep->ovflist = EP_UNACTIVE_PTR; 表示当前需要往ready 链表插入事件epi
把txlist中剩余元素插入ep->rdllist
如果ready链表非空,尝试唤醒ep->wq和ep->poll_wait等待队列
ep_send_events_proc[ep_scan_ready_list]:
读取txlist中已经ready的事件,获取事件的events,复制到用户空间,复制失败则把epi重新插入到ready链表
如果设置了EPOLLONESHOT标志位,则设置epi->event.events &= EP_PRIVATE_BITS,其定义如下#define EP_PRIVATE_BITS (EPOLLWAKEUP | EPOLLONESHOT | EPOLLET),后续根据EP_PRIVATE_BITS判断不再加入ep->rdllist或者ep->ovflist。注意设置了EPOLLONESHOT触发一次后并没有删除epi,因而通过epoll_ctl进行ADD操作后会提示File exists错误。
如果设置了水平触发(没有EPOLLET标志位),那么即使已经成功把事件传递到了用户空间也会把epi重新添加到ready链表尾部,这样下次进行epoll_wait的时候可以重新检查这个epi。注意EPOLLONESHOT优先于水平触发的处理,即同时设置水平触发和EPOLLONESHOT并不会把epi添加到ready链表。
TCP源码—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( ...
- EventBus源码解析 源码阅读记录
EventBus源码阅读记录 repo地址: greenrobot/EventBus EventBus的构造 双重加锁的单例. static volatile EventBus defaultInst ...
- Flink源码分析 - 源码构建
原文地址:https://mp.weixin.qq.com/s?__biz=MzU2Njg5Nzk0NQ==&mid=2247483692&idx=1&sn=18cddc1ee ...
- Elasticsearch源码分析 - 源码构建
原文地址:https://mp.weixin.qq.com/s?__biz=MzU2Njg5Nzk0NQ==&mid=2247483694&idx=1&sn=bd03afe5a ...
- Vue源码探究-源码文件组织
Vue源码探究-源码文件组织 源码探究基于最新开发分支,当前发布版本为v2.5.17-beta.0 Vue 2.0版本的大整改不仅在于使用功能上的优化和调整,整个代码库也发生了天翻地覆的重组.可见随着 ...
- Flink 源码解析 —— 源码编译运行
更新一篇知识星球里面的源码分析文章,去年写的,周末自己录了个视频,大家看下效果好吗?如果好的话,后面补录发在知识星球里面的其他源码解析文章. 前言 之前自己本地 clone 了 Flink 的源码,编 ...
- ios源码-ios游戏源码-ios源码下载
游戏源码 一款休闲类的音乐小游戏源码 该源码实现了一款休闲类的音乐小游戏源码,该游戏的源码很简单,而且游戏的玩法也很容易学会,只要我们点击视图中的grid,就可以 人气:2943运行环境:/Xco ...
- C#UDP(接收和发送源码)源码完整
C#UDP(接收和发送源码)源码完整 最近做了一个UDP的服务接收和发送的东西.希望能对初学的朋友一点帮助. 源码如下: 一.逻辑--UdpServer.cs using System;using S ...
随机推荐
- 2017-2018-2 《网络对抗技术》20155322 Exp7 网络欺诈防范
[-= 博客目录 =-] 1-实践目标 1.1-实践介绍 1.2-实践内容 1.3-实践要求 2-实践过程 2.1-简单应用SET工具建立冒名网站 2.2-ettercap DNS spoof 2.3 ...
- c++ switch语句
一.认识switch格式 switch(表达式) { case 常量表达式: 语句1; break; case 常量表达式: 语句2; break; case 常量表达式: 语句3; break; . ...
- 【HNOI2015】菜肴制作
题面 题解 这道题目首先可以想到拓扑排序,但是肯定不是字典序最小的排列. 比如说,有\(4\)种菜,限制为\(2 \to 4, 3 \to 1\),那么如果求字典序最小的排列会算出\((2, 3, 1 ...
- Codeforces 912 D. Fishes (贪心、bfs)
题目链接:Fishes 题意: 有一个n×m的鱼塘,有一张r×r的渔网,现在往池塘里面放k条鱼(每个格子只能放一条鱼), 现在撒网的地方是随机的(必须在池塘内),问能捕的鱼的期望值最大是多少? 题解: ...
- Wannafly挑战赛25C 期望操作数
Wannafly挑战赛25C 期望操作数 简单题啦 \(f[i]=\frac{\sum_{j<=i}f[j]}{i}+1\) \(f[i]=\frac{f[i]}{i}+\frac{\sum_{ ...
- 关于Mybatis的Example(and ,or )应用
近期的一个项目中遇到Mybatis的Example的and or 的应用,感觉有必要记录一下(个人见解,有问题请指出.谢谢) 1.在Example中的每一个Criteria相当于一个括号,把里面的内容 ...
- 菜鸟vimer成长记——第3章、文件
上一章一直在讲的是vim的文本的操作,本章主要讲的是vim的文件操作. 本章的有些概念和传统的文本编辑器也不尽相同.所以需要注意概念或者切切说是思维习惯的区别. vim 允许在一个编辑会话中编辑多个文 ...
- CSS快速入门-代码目录
我们写python代码或者其他代码的时候,发现文件会越来越多,这时候你就觉得有必要把代码文件进行整理了. 对代码整理的整理主要思路: 1.按功能(比如:可执行程序文件.数据库文件.视图文件) 2.按类 ...
- Django—— restful 设计风格
RESTful Api设计风格 协议:API 与用户的通信协议,总是使用 HTTPS 协议 域名:应该尽量将 API 部署在专用域名之下,如果确定 API 很简单,不会有进一步的扩展,可以考虑放在主域 ...
- @Helper辅助方法和@functions自定义函数
1.首先说下@helper辅助方法,当我们在多个视图中共用相同的方法的时候,可以把此方法剥离出来放到一个位置,此时就可以用到@Helper辅助方法,首先我们在解决方案右键添加 App_Code文件夹, ...