TCP源码—epoll源码及测试

1、epoll_create & epoll_create1node

SYSCALL_DEFINE1(epoll_create, int, size)tcp

sys_epoll_create->sys_epoll_create1函数

SYSCALL_DEFINE1(epoll_create1, int, flags)oop

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:spa

  1. 该函数建立的文件共享使用一个inode,节省内存避免代码重复,inode:anon_inode_inode、 sb:anon_inode_mnt->mnt_sb、 fs:anon_inode_fs_type,初始化位置[anon_inode_init]。pwa

  2. 使用alloc_file分配一个文件,file->f_op = eventpoll_fops;指针

  3. 设置file->f_flags = (O_RDWR | (flags & O_CLOEXEC)) & (O_ACCMODE | O_NONBLOCK); file->private_data =ep;code


    其中eventpoll_fops注册了ep_show_fdinfo函数,容许咱们在proc中查看对应epfd的epi信息orm

  1.    
       
       
       
    lybxin@Inspiron:~$more /proc/1469/task/1469/fdinfo/4pos: 0flags: 02000002mnt_id: 11tfd:        5 events:       19 data:     55b8247c8da0tfd:       13 events:       19 data:     55b8247ccc90tfd:       14 events:       19 data:     55b8248079a0tfd:        9 events:       19 data:     55b8247e9860tfd:       12 events:       1a data:     55b8247e9b40tfd:        8 events:       19 data:     55b8247caf90tfd:        7 events:       19 data:     55b824806490


2、epoll_ctl

SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,struct epoll_event __user *, event)


sys_epoll_ctl:

  1. 若是不是EPOLL_CTL_DEL操做,则从用户空间复制epoll_event结构

  2. 获取fd结构,epfd->f,须要操做的fd->tf

  3. 目标fd对应的fd结构必须支持tf.file->f_op->poll操做

  4. 处理EPOLLWAKEUP标志

  5. 检验epfd对应有效的epoll文件描述符,且须要操做的fd与epfd不是对应同一个文件

  6. 经过ep_loop_check检测epfd是否构成闭环或者连续epfd的深度超过5,对应宏EP_MAX_NESTS[4]

  7. 经过ep_find查找这个epfd是否已经添加了目标fd文件

  8. ADD/MOD操做自动添加POLLERR | POLLHUP这两个标志位


ep_loop_check:

  1. visited_list:表示已经处理过的节点,假设epfd1下挂epfd2和epfd3,而epfd2和epfd3又同时挂epfd4,那么保证epfd只处理一次

  2. tfile_check_list:保存非epoll文件的fd用于反向检查

  3. 从源码和下面的测试来看这个闭环和深度检测只能从添加的fd向下检测,而不能向上检测,所以并非全部场景都能有效的检测出来,以下测试,另外还有一种场景由于会跳过已经visit的节点,因此visit的节点的最大深度也可能会超过5。

  4.     
        
        
        
    ---------------test1 start---------------  //正向查找只检测target fdadd 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 fdadd 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:

  1. max_user_watches:/proc/sys/fs/epoll/max_user_watches 每一个用户同时watch的最大fd数目

  2. 若是watch的总数超过max_user_watches,则返回ENOSPC

  3. 若是从epi_cache分配epitem失败,则返回ENOMEM

  4. 根据EPOLLWAKEUP标志注册wake up

  5. 经过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/目录下面的文件。

  6. 把epi插入到f_ep_links链表的尾部,list_add_tail_rcu(&epi->fllink, &tfile->f_ep_links);

  7. 把epi插入到ep的红黑树中,ep_rbtree_insert(ep, epi);

  8. 经过reverse_path_check进行反向检查

  9. 若是获取到的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)

  10. 自增ep->user->epoll_watches


reverse_path_check:

  1. 对于第一层反向检查不限制数目。

  2. 对于第2-5层,限制引用数目分别为500、100、50、10,以下变量定义了上限,其中该变量第一个成员1000仅做占位使用,并不限制第一层引用总数,参考path_count_inc。static const int path_limits[PATH_ARR_SIZE] = { 1000, 500, 100, 50, 10 };

  3. 对于5层以上则直接返回错误

  4.     
        
        
        
    ---------------test4 反向查找---------------  //反向查找层数超过5add 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:

  1. 移除一个epi

  2. 从poll wait中移除

  3. 从file的f_ep_links链表移除

  4. 从红黑树中移除

  5. 从ready链表中移除

  6. 取消wakeup注册

  7. 自减ep->user->epoll_watches


ep_modify:修改epi

  1. 更新epi->event.events和epi->event.data

  2. 根据EPOLLWAKEUP更新wake up

  3. 刷新内存屏障smp_mb

  4. 经过ep_item_poll获取revents,相比ep_insert差别在于并不会调用ep_ptable_queue_proc从新注册

  5. 若是获取到的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)



3、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:

  1. 根据入参估计超时时间to和slack,或者设置timed_out标志位

  2. 若是epoll_wait入参定时时间为0,那么直接经过ep_events_available判断当前是否有用户感兴趣的事件发生,若是有则经过ep_send_events进行处理

  3. 若是定时时间大于0,而且当前没有用户关注的事件发生,则进行休眠,并添加到ep->wq等待队列的头部。 对等待事件描述符设置WQ_FLAG_EXCLUSIVE标志

  4. ep_poll被事件唤醒后会从新检查是否有关注事件,若是对应的事件已经被抢走,那么ep_poll会继续休眠等待。

  
  
  
  
lybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$./nesttest-----------test8 测试epollaccept同时等待的唤醒状况 epfd1:4,epfd:5,listen_fd:3----------- lybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$ss -tlnap | grep 9877LISTEN     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 9877epfd2 epoll_wait return: i:0,nfds:1,fd:3,sec:238epfd1 epoll_wait return: i:0,nfds:1,fd:3,sec:238accept return connfd:6,sec:238^Clybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$nc  127.0.0.1 9877epfd2 epoll_wait return: accept return connfd:7,sec:240i:0,nfds:1,fd:3,sec:240^Clybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$nc  127.0.0.1 9877epfd1 epoll_wait return: accept return connfd:8,sec:242i:0,nfds:1,fd:3,sec:242^Clybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$nc  127.0.0.1 9877epfd2 epoll_wait return: accept return connfd:9,sec:244i:0,nfds:1,fd:3,sec:244^Clybxin@Inspiron:~/MyRes/LNP/tcp/epolltest$nc  127.0.0.1 9877epfd1 epoll_wait return: i:0,nfds:1,fd:3,sec:246epfd2 epoll_wait return: i:0,nfds:1,fd:3,sec:246accept return connfd:10,sec:246^C


select_estimate_accuracy:

  1. 估计slack,最大为MAX_SLACK(100ms),最小为current->timer_slack_ns(默认值为50000ns,即50 usec),timer_slack_ns能够经过prctl的PR_SET_TIMERSLACK选项设置

  2. nice 进程取定时时间的0.5%,普通进程取0.1%


ep_events_available:

  1. 若是ready链ep->rdllist非空或者ep->ovflist有效,则表示当前有关注的event发生



ep_scan_ready_list[ep_send_events]:

  1. epoll_wait的时候传递函数指针ep_send_events_proc给ep_scan_ready_list,epfd进行poll的时候则传递函数指针ep_read_events_proc

  2. 把ep->rdllist连接到txlist,并清空ep->rdllist,设置ep->ovflist = NULL,表示当前正在往用户空间发送数据,新事件触发的epi插入到ep->ovflist的头部,参考ep_send_events_proc函数的注释

  3. 调用传入的函数指针处理txlist

  4. 把ep->ovflist插入到ep->rdllist

  5. 设置ep->ovflist = EP_UNACTIVE_PTR; 表示当前须要往ready 链表插入事件epi

  6. 把txlist中剩余元素插入ep->rdllist

  7. 若是ready链表非空,尝试唤醒ep->wq和ep->poll_wait等待队列


ep_send_events_proc[ep_scan_ready_list]:

  1. 读取txlist中已经ready的事件,获取事件的events,复制到用户空间,复制失败则把epi从新插入到ready链表

  2. 若是设置了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错误。

  3. 若是设置了水平触发(没有EPOLLET标志位),那么即便已经成功把事件传递到了用户空间也会把epi从新添加到ready链表尾部,这样下次进行epoll_wait的时候能够从新检查这个epi。注意EPOLLONESHOT优先于水平触发的处理,即同时设置水平触发和EPOLLONESHOT并不会把epi添加到ready链表。






相关文章
相关标签/搜索