• 对于epoll实现原理的理解


    Epoll是Linux IO的多路复用的机制,是select/poll的增强版本,在Linux内核fs/eventpoll.c中可以查看epoll的具体的实现。

    一、epoll数据结构

      学习任何组件,首先得知道它有什么数据结构或者数据类型,epoll主要有两个结构体:eventpoll和epitem。epitem是每一个IO对应的事件,比如EPOLL_CTL_ADD操作时,就需要创建一个epitem;eventpoll是每一个epoll所对应的,比如epoll_create就是创建一个eventpoll。

    1. struct epitem {
    2. union {
    3. /* RB tree node links this structure to the eventpoll RB tree */
    4. struct rb_node rbn;
    5. /* Used to free the struct epitem */
    6. struct rcu_head rcu;
    7. };
    8. /* List header used to link this structure to the eventpoll ready list */
    9. struct list_head rdllink;
    10. /*
    11. * Works together "struct eventpoll"->ovflist in keeping the
    12. * single linked chain of items.
    13. */
    14. struct epitem *next;
    15. /* The file descriptor information this item refers to */
    16. struct epoll_filefd ffd;//sockfd
    17. /* List containing poll wait queues */
    18. struct eppoll_entry *pwqlist;
    19. /* The "container" of this item */
    20. struct eventpoll *ep;
    21. /* List header used to link this item to the "struct file" items list */
    22. struct hlist_node fllink;
    23. /* wakeup_source used when EPOLLWAKEUP is set */
    24. struct wakeup_source __rcu *ws;
    25. /* The structure that describe the interested events and the source fd */
    26. struct epoll_event event;
    27. };
    28. struct eventpoll {
    29. struct mutex mtx;
    30. /* Wait queue used by sys_epoll_wait() */
    31. wait_queue_head_t wq;
    32. /* Wait queue used by file->poll() */
    33. wait_queue_head_t poll_wait;
    34. /* List of ready file descriptors */
    35. struct list_head rdllist;
    36. /* Lock which protects rdllist and ovflist */
    37. rwlock_t lock;
    38. /* RB tree root used to store monitored fd structs */
    39. struct rb_root_cached rbr;
    40. /*
    41. * This is a single linked list that chains all the "struct epitem" that
    42. * happened while transferring ready events to userspace w/out
    43. * holding ->lock.
    44. */
    45. struct epitem *ovflist;
    46. /* wakeup_source used when ep_scan_ready_list is running */
    47. struct wakeup_source *ws;
    48. /* The user that created the eventpoll descriptor */
    49. struct user_struct *user;
    50. struct file *file;
    51. /* used to optimize loop detection check */
    52. u64 gen;
    53. struct hlist_head refs;
    54. #ifdef CONFIG_NET_RX_BUSY_POLL
    55. /* used to track busy poll napi_id */
    56. unsigned int napi_id;
    57. #endif
    58. #ifdef CONFIG_DEBUG_LOCK_ALLOC
    59. /* tracks wakeup nests for lockdep validation */
    60. u8 nests;
    61. #endif
    62. };

     数据结构如下图所示。

      list用来存储就绪的IO,rbtree用来存储所有IO,方便快速查找fd,这两种数据结构我们都从inster和remove来讨论。对于list,当内核IO准备就绪时,则执行epoll_event_callback的回调函数,将epitem添加到list中;当epoll_wait激活重新运行时,将list的epitem 逐一拷贝到events中,并删除list中被拷贝出来的epitem。

    对于rbtree又该何时添加何时删除呢?当app执行epoll_ctl(EPOLL_CTL_ADD)操作,将epitem添加到rbtree中;当app执行epoll_ctl(EPOLL_CTL_DEL)操作,将对应的epitem从rbtree中删除。那么list和rbtree又如何做到线程安全呢?

    二、epoll锁的机制

      list使用最小粒度的锁spinlock,便于在SMP下添加操作的时候,能够快速操作list。避免SMP体系下,多核竞争,此处采用自旋锁,不适合采用睡眠锁;添加操作如下。

        (1)获取spinlock

        (2)epitem的rdy置为1,代表epitem已在就绪队列中

        (3)添加到list

        (4)将eventpoll的rdnum加1

        (5)释放spinlock

      删除则与添加类似。

      对于rbtree的操作使用互斥锁,过程如下:

        (1)获取互斥锁

        (2)查找sockid的epitem是否存在,不存在可以添加

        (3)分配epitem

        (4)sockid赋值

        (5)设置event添加到epitem的event域

        (6)将epitem添加到rbtree

        (7)释放互斥锁

      这里的互斥锁,锁的是整颗树,而不是节点;删除则与之类似操作。

    三、epoll回调

      首先要知道回调函数何时执行,此部分需要与tcp的协议栈联系起来理解。

      (1)三次握手完成时,把fd加入到就绪队列,把event置为EPOLLIN可读,此时标识可以进入到accept读取socket数据;

      (2)recvbuffer有数据的时候(可读数据),找到对应的fd加入到就绪队列,把event置EPOLLIN为可读;

      (3)sendbuffer有空隙的时候(可发数据),找到对应的fd加入到就绪队列,把event置EPOLLOUT为可写;

      (4)接收到fin的时候(断开连接),找到对应的fd加入到就绪队列,把event置EPOLLIN为可读;

    四、LT与ET

      (1)LT是水平出发,有数据就一直触发,只要recvBuffer里面有数据就一直触发,直到数据读取完,适用于大块;

      (2)ET是边沿触发,从没有数据到有数据,才触发,只触发一次,即使没读完数据,也不会再触发去读取剩余的数据,剩余的数据等待下一次触发再读,适用于小块;

    思考:

      就绪集合为啥使用队列而不适用栈?

        首先就绪集合的数据本身就需要遍历所有,肯定使用链式的数据结构,如果使用栈,就会存在就绪节点一次那不完的情况,导致上一次没被取出的节点,在下一次epoll_wait再拿的时候也可能拿不到,导致出现一些就绪节点永远都不被处理。

      epoll的误区:

      (1)epoll性能高,里面有内存映射,mmap

      (2)epoll比select/poll要高,在fd很少时select/poll比epoll更好

    C/C++Linux服务器开发高级架构师/C++后台开发架构师​免费学习地址

    另外还整理一些C++后台开发架构师 相关学习资料,面试题,教学视频,以及学习路线图,详情看以下视频

    Linux C/C++后台开发学习资源整理,包括教学视频,文档,面试题,学习路线图

  • 相关阅读:
    【PAT甲级】1098 Insertion or Heap Sort
    {版本发布公告}HMS Core 6.6.0来啦
    服务注册与发现Eureka、Zookeeper、Consul 三个注册中心的异同点(CAP理论)
    关于python中的几个问题
    ImageKnife组件,让小白也能轻松搞定图片开发
    外包干了3个多月,技术退步明显。。。。
    Elasticsearch
    SFI立昌Common Mode Filter方案与应用
    阿里云99 / 腾讯云88,现在服务器这么便宜了吗?价格降这么多
    Matlab(数值微积分)
  • 原文地址:https://blog.csdn.net/Linuxhus/article/details/126991123