linux epoll解析

linux epoll

引言

高性能网络编程避免不了与epoll打交道,epoll是一种IO多路复用技术,可以非常高效的处理数以百万计的socket句柄,比起以前的select和poll效率高很多,那么,它到底为什么可以高速处理这么多并发连接呢?

三个关键函数

int epoll_create(int 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);
  • epoll_create建立一个epoll对象,参数size是内核保证能够处理的最大句柄数,多于这个最大数时内核可不保证效果。在内核里,一切皆文件,所以,epoll向内核注册了一个文件系统描述符,用于存储上述的被监控socket。当你调用epoll_create时,就会在这个虚拟的epoll文件系统里创建一个file结点。当然这个file不是普通文件,它只服务于epoll。==主要的三件事==:
  1. 在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点。
  2. 在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket。
  3. 再建立一个list链表,用于存储准备就绪的事件。
  • epoll_ctl可以操作上面建立的epoll,即对某个fd执行add or del or modify(op指定)的read/write事件(event指定) 等操作例如,将刚建立的socket加入到epoll中让其监控,或者把epoll正在监控的某个socket句柄移出epoll,不再监控它等等。==准备就绪list链表和红黑树是怎么维护的呢?==:
  1. 当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上之外,
  2. 还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。
  3. 所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后,就把socket插入到准备就绪链表里了。
  • epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回用户态的进程。//当epoll_wait调用时,仅仅观察上述list链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后,即使链表没数据也返回。所以,epoll_wait非常高效。

  • 对比select/poll:每次调用时都要传递你所要监控的所有socket给select/poll系统调用,这意味着需要将用户态的socket列表copy到内核态,如果以万计的句柄会导致每次都要copy几十几百KB的内存到内核态,非常低效。而在你调用epoll_create后,内核就已经在内核态开始准备帮你存储要监控的句柄了,每次调用epoll_ctl只是在往内核的数据结构里塞入新的socket句柄。

  • epoll初始化

static int __init eventpoll_init(void) { 
    ... ... 
    /* Allocates slab cache used to allocate "struct epitem" items */ 
    epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct  epitem), 0, SLAB_HWCACHE_ALIGN| EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL); 
    /* Allocates slab cache used to allocate "struct eppoll_entry" */ 
    pwq_cache = kmem_cache_create("eventpoll_pwq", sizeof(struct eppoll_entry), 0, EPI_SLAB_DEBUG|SLAB_PANIC, NULL, NULL); 
    ... ...
  • epoll在被内核初始化时(操作系统启动),同时会开辟出epoll自己的内核高速cache区,用于安置每一个我们想监控的socket,==这些socket会以红黑树的形式保存在内核cache里,以支持快速的查找、插入、删除==。这个内核高速cache区,就是建立连续的物理内存页,然后在之上建立slab层,简单的说,就是物理上分配好你想要的size的内存对象,每次使用时都是使用空闲的已分配好的对象。
  • 总结:==一颗红黑树,一张准备就绪句柄链表,少量的内核cache==,就帮我们解决了大并发下的socket处理问题。执行epoll_create时,创建了红黑树和就绪链表,执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。

ET/LT模式:

LT模式:

  1. 读模式
  • 缓存区不空时,返回读就绪。
  1. 写模式
  • 缓冲区不满,就返回写就绪。

ET模式:

  1. 读模式:
  • 当缓冲区由不可读变为可读的时候,即缓冲区由空变为不空的时候
  • 当有新数据到达时,即缓冲区中的待读数据变多的时候
  • 当缓冲区有数据可读,且应用进程对相应的描述符进行EPOLL_CTL_MOD修改EPOLLIN事件时
  1. 写模式
  • 当缓冲区由不可写变为可写时。
  • 当有旧数据被发送走,即缓冲区中的内容变少的时候。
  • 当缓冲区有空间可写,且应用进程对相应的描述符进行EPOLL_CTL_MOD修改EPOLLOUT事件时

总结:LT模式是epoll默认的工作方式,ET模式为边沿触发,意思是说当只有在数据到来时,触发中断事件;LT模式为水平触发,只要缓存区有数据,触发中断事件。

参考:

  • Linux下的非阻塞IO库epoll
  • epoll 水平触发与边缘触发

你可能感兴趣的:(linux epoll解析)