epoll原理简要分析

写在前面:文章掺杂了个人理解,请谨慎参考

1. select、poll、epoll区别

三个系统调用均用于感知多个socket是否有数据到来

1.1 select、poll:

暴力做法:当一个进程A调用recv阻塞,等待数据时,其实是睡眠在了recv所等待的socket(这里假设有fd1、fd2)的等待队列上,当网卡接收到数据,并得知该数据发给fd1时,会触发中断,fd1会从等待队列上唤醒A,使得A重新进入运行状态,而A被唤醒后,得知至少有一个socket上有数据到来,便执行程序遍历一遍监听的socket列表,就可以得到就绪的socket。
性能开销:每次调用 Select 都需要将进程加入到所有监视 Socket 的等待队列,每次唤醒都需要从每个队列中移除。这里涉及了两次遍历,而且每次都要将整个 FDS 列表传递给内核,有一定的开销。
上述就是select的实现大致过程,出于效率的考量,通过bitmask规定了 Select 的最大监视数量,所以在设计之初,就默认认为select的最大范围是1024;
poll:更换数据结构,由bitmask更换为结构体数组,一个结构体对应一个socket,所以不限制上限数目,但是出于操作系统的内存限制,实际上poll监听的socket数目也是有限的。

1.2 epoll

在理解上述数据到来时,进程如何感知到是哪个socket有数据的过程后,来看一下epoll所做的改进。
epoll在内核态中有两个重要的数据结构,①采用红黑树实现的eventpoll对象:用于保存所监听的socket fd②采用双向链表实现的rdlist对象:用于保存可读写的socket,并给用户态进程返回读写就绪的fd集合。

  • 在select的实现过程中,有一个重要的环节是,由中断唤醒对应的进程来处理可读写的socket;在epoll中,所做的优化是:触发中断时,重写了中断处理程序,首先将可读写的socket添加到rdlist中,再唤醒进程;因此进程唤醒后,可以直接从rdlist中得到可读写的socket。
  • 采用了红黑树的结构,也使得对socket的增删改查时间维持在对数时间内。

2. epoll中的ET、LT触发方式

接下来,结合epoll高效的原理分析,继续简要分析一下epoll所提供的两种模式ET和LT的实现原理。

LT和ET的区别

LT模式:如果数据可读,epoll_wait 会通知用户空间,即使在之前的通知后数据尚未被完全读取。(举例来说,如果某次来了100 Bytes,read可能读了60 Bytes后,因为内核调度的原因,剩下的数据没来得及读完,后续再次调用read时,余下的40 Bytes也会被继续读取)
ET模式:与LT不同,如果当时这40 Bytes不读取的话,之后再次调用read时便不会再被读取。
因此选择不同的模式,编写代码时有区别:

/*******
***LT***
*******/
int nfds, epfd;
struct epoll_event events[MAX_EVENTS];

while (1) {
    nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
    
    for (int i = 0; i < nfds; ++i) {
        if (events[i].events & EPOLLIN) {
            char buffer[1024];
            ssize_t bytes_read = read(events[i].data.fd, buffer, sizeof(buffer));
            // 处理读取的数据...
        }
        // 其他事件处理...
    }
}

/*******
***ET***
*******/
int nfds, epfd;
struct epoll_event events[MAX_EVENTS];

while (1) {
    nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
    
    for (int i = 0; i < nfds; ++i) {
        if (events[i].events & EPOLLIN) {
            while (1) {
                char buffer[1024];
                ssize_t bytes_read = read(events[i].data.fd, buffer, sizeof(buffer));

                if (bytes_read == -1) {
                    if (errno != EAGAIN) {
                        // 读取出现错误...
                    }
                    break; // EAGAIN 表示所有数据都已读取完毕
                } else if (bytes_read == 0) {
                    // 连接已关闭...
                    break;
                }

                // 处理读取的数据...
            }
        }
        // 其他事件处理...
    }
}

实现原理

在内核态中,epoll_wait在给用户态进程返回rdlist数据后,不同的模式下有不同的后续处理:
LT模式:对于没有读完缓冲区的fd,继续放在rdlist中不做处理;
ET模式:一旦某事件被报告给用户态程序并从就绪列表中移除;

参考资料:

Epoll原理解析_confirmwz的博客-优快云博客_epoll原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值