epool如何高效

开发高性能网络程序时,windows开发者们言必称iocp,linux开发者们则言必称epoll。大家都明白epoll是一种IO多路复用技术,可以非常高效的处理数以百万计的socket句柄,比起以前的select和poll效率高大发了。我们用起epoll来都感觉挺爽,确实快,那么,它到底为什么可以高速处理这么多并发连接呢?

 

先简单回顾下如何使用C库封装的3个epoll系统调用吧。

 

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_ctl可以操作上面建立的epoll,例如,将刚建立的socket加入到epoll中让其监控,或者把 epoll正在监控的某个socket句柄移出epoll,不再监控它等等。

epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回用户态的进程。

 

从上面的调用方式就可以看到epoll比select/poll的优越之处:因为后者每次调用时都要传递你所要监控的所有socket给select/poll系统调用,这意味着需要将用户态的socket列表copy到内核态,如果以万计的句柄会导致每次都要copy几十几百KB的内存到内核态,非常低效。而我们调用epoll_wait时就相当于以往调用select/poll,但是这时却不用传递socket句柄给内核,因为内核已经在epoll_ctl中拿到了要监控的句柄列表。

 

所以,实际上在你调用epoll_create后,内核就已经在内核态开始准备帮你存储要监控的句柄了,每次调用epoll_ctl只是在往内核的数据结构里塞入新的socket句柄。

 

在内核里,一切皆文件。所以,epoll向内核注册了一个文件系统,用于存储上述的被监控socket。当你调用epoll_create时,就会在这个虚拟的epoll文件系统里创建一个file结点。当然这个file不是普通文件,它只服务于epoll。

 

epoll在被内核初始化时(操作系统启动),同时会开辟出epoll自己的内核高速cache区,用于安置每一个我们想监控的socket,这些socket会以红黑树的形式保存在内核cache里,以支持快速的查找、插入、删除。这个内核高速cache区,就是建立连续的物理内存页,然后在之上建立slab层,简单的说,就是物理上分配好你想要的size的内存对象,每次使用时都是使用空闲的已分配好的对象。

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_ctl往里塞入百万个句柄时,epoll_wait仍然可以飞快的返回,并有效的将发生事件的句柄给我们用户。这是由于我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外,还会再建立一个list链表,用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个list链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。

 

而且,通常情况下即使我们要监控百万计的句柄,大多一次也只返回很少量的准备就绪句柄而已,所以,epoll_wait仅需要从内核态copy少量的句柄到用户态而已,如何能不高效?!

 

那么,这个准备就绪list链表是怎么维护的呢?当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。

 

如此,一颗红黑树,一张准备就绪句柄链表,少量的内核cache,就帮我们解决了大并发下的socket处理问题。执行epoll_create时,创建了红黑树和就绪链表,执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。

 

最后看看epoll独有的两种模式LT和ET。无论是LT和ET模式,都适用于以上所说的流程。区别是,LT模式下,只要一个句柄上的事件一次没有处理完,会在以后调用epoll_wait时次次返回这个句柄,而ET模式仅在第一次返回。

 

这件事怎么做到的呢?当一个socket句柄上有事件时,内核会把该句柄插入上面所说的准备就绪list链表,这时我们调用epoll_wait,会把准备就绪的socket拷贝到用户态内存,然后清空准备就绪list链表,最后,epoll_wait干了件事,就是检查这些socket,如果不是ET模式(就是LT模式的句柄了),并且这些socket上确实有未处理的事件时,又把该句柄放回到刚刚清空的准备就绪链表了。所以,非ET的句柄,只要它上面还有事件,epoll_wait每次都会返回。而ET模式的句柄,除非有新中断到,即使socket上的事件没有处理完,也是不会次次从epoll_wait返回的。

 

转载自 https://www.cnblogs.com/tqyysm/p/9782631.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: "epoll read errno" 其实是指 Epoll 系统调用在进行读取操作时出现的错误编号。Epoll 是 Linux 系统中的一个高性能的 I/O 事件通知机制,用于监听文件描述符上的事件并进行相应的处理。当 Epoll 进行读取操作时,可能会发生各种错误,造成读取失败。 其中,errno 是一个全局变量,用于保存最后一次发生错误时的错误编号。 通常情况下,epoll read errno 可能的取值和对应的含义如下: 1. EAGAIN 或 EWOULDBLOCK:表示当前没有可读取的数据,即阻塞状态。 2. EBADF 或 EINVAL:表示被监听的文件描述符无效或者不是一个合法的 socket。 3. EINTR:表示 Epoll 被信号中断,需要重新调用。 4. EFAULT:表示需要读取数据的缓冲区指针无效。 5. ENOMEM:表示内存不足,无法为 Epoll 内部数据结构分配内存空间。 6. 其他错误:比如权限问题、文件已关闭等。 当程序中出现 "epoll read errno" 错误时,我们可以根据具体的错误编号来判断错误的原因,并采取相应的处理措施。可能的处理方法包括:重新尝试读取、更新监听的文件描述符、增加缓冲区大小、检查文件描述符的权限、检查内存使用等。具体的解决方法需要根据实际情况来定,以确保程序的正常运行。 ### 回答2: "epool read errno" 是一个错误信息,通常在使用 Linux 系统中的 epoll I/O 多路复用机制时出现。 在 epoll I/O 多路复用中,使用一个 epoll 对象来监视多个文件描述符(包括套接字和文件)的状态变化。当一个文件描述符准备好进行读操作时,epoll_wait 函数会返回,并且可以通过相应的文件描述符进行读取操作。然而,有时候在调用 epoll_wait 函数时,可能会收到 "epool read errno" 错误。 这个错误通常是由于以下几种情况引起的: 1. epoll 对象没有正确初始化或被关闭了。在使用 epoll_create 函数创建 epoll 对象时,如果返回值为 -1,就说明出错了。 2. epoll_wait 函数的返回值小于 0,表示出错。可以通过读取 errno 变量来获取具体的错误代码。 3. epoll_wait 函数的超时时间参数设置错误,导致超时立即返回,而不是等待任何事件的到来。 解决这个问题的方法包括: 1. 确保正确初始化并正确使用 epoll 对象。如果 epoll_create 函数返回 -1,可以检查 errno 变量来获取具体的错误信息,并相应地处理。 2. 针对 epoll_wait 函数的返回值小于 0 的情况,可以读取 errno 变量来获取具体的错误代码,并进行相应的处理措施。 3. 检查 epoll_wait 函数的超时时间参数是否正确设置。 总之,"epool read errno" 错误是在使用 epoll I/O 多路复用机制时可能会遇到的错误信息,我们需要根据具体的场景和错误代码来进行分析和处理。 ### 回答3: "epoll read errno" 是指使用 epoll 函数进行读操作时出现的错误。epoll 是 Linux 系统中一种高效的 I/O 多路复用机制,允许程序同时监视多个文件描述符,等待其中任何一个文件描述符变为可读或可写状态。而 "epoll read errno" 表示在使用 epoll 进行读操作时出现了错误。 通常,"epoll read errno" 错误的产生是由于以下几个常见原因: 1. 文件描述符错误:epoll 函数对于读操作要求输入参数是一个有效的文件描述符,如果文件描述符无效或已关闭,就会导致 "epoll read errno" 错误。 2. 读取缓冲区不足:当 epoll 函数读取数据时,需要提供一个足够大的缓冲区来存储读取的数据。如果缓冲区不够大,读取的数据可能会被截断,也可能导致 "epoll read errno" 错误。 3. 连接中断或错误状态:在使用 epoll 进行读操作时,如果连接中断或发生错误,epoll 函数会返回相应的错误码,比如 EPIPE、ECONNRESET 等,表示连接发生了错误,从而引发 "epoll read errno" 错误。 为了解决 "epoll read errno" 错误,可以采取以下措施: 1. 检查文件描述符的有效性,确保它是一个有效的、打开的文件描述符。 2. 确保读取缓冲区足够大,可以容纳要读取的数据。 3. 分析错误码,如 EPIPE、ECONNRESET 等,找到具体的错误原因并进行相应的处理。 总之,"epoll read errno" 错误是指在使用 epoll 函数进行读取操作时发生的错误。可以通过检查文件描述符的有效性、确保读取缓冲区足够大以及分析错误码等方式来解决该错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值