epoll底层原理

简介: epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本。

epoll底层原理

epoll作为linux下高性能网络服务器的必备技术至关重要,java NIO、nginx、redis、skynet和大部分游戏服务器都使用了这一多路复用技术。
epoll是select和poll的增强版本。

epoll的三个方法:

  • epoll_create: 内核会创建一个eventpoll对象(专用的文件描述符,也就是程序中epfd所代表的对象)
    eventpoll对象也是文件系统中的一员,和socket一样,它也会有等待队列。
  • epoll_ctl: 添加待监控的socket

如果通过spoll_ctl添加sock1、sock2和sock3的监视,内核会将三个socket添加到eventpoll监听队列

  • epoll_wait: 阻塞等待

进程A运行到了epoll_wait语句之后,进程A会等到eventpoll的等待队列。

epoll相对于select和poll的优化措施:

优化措施一:

  • 功能分离: 进程到等待队列,进程阻塞
select低效的原因之一是将“维护等待队列”和“阻塞进程”两个步揍合二为一。
大多数的应用场景中,需要监视的socket数量相对固定,并不需要每次都修改。

epoll将这两个操作分开,首先调用epoll_ctl维护监听队列,在调用epoll_wait阻塞进程。
epoll的监听列表

优化措施二:

  • 引入了就绪队列rdlist
select低效的另一个原因在于程序不知道那些socket收到数据,只能一个一个遍历。如果内核维护一个“就绪队列”rdlist,引用收到数据的socket,就能避免遍历。

就绪列表

下面代码中,先用epoll_create创建一个epoll对象epfd,再通过epoll_ctl将需要监听的socket添加到epfd的专用监听队列中,最后调用epoll_wait等待数据,返回rdlist列表中的就绪socket。

int epfd=epoll_create(...);
epoll_ctl(epfd,...)//第一步:将所需要监听的socket添加到epfd中监听队列

while(1){
int n=epoll_wait(...); //第二步:阻塞进程等待事件
for(接收到数据的socket){
//处理
    }
}

假设计算机正在运行进程A和进程B,在某时刻进程A运行到了epoll_wait语句。内核会将进程A放入到eventpoll监听队列中,阻塞进程。

在这里插入图片描述
当socket接收到数据,中断程序会做两个工作

  • 一方面修改rdlist
  • 另一方面唤醒eventpoll监听队列中的进程,进程A再次进入运行状态。

也正因为rdlist的存在,进程A可以知道哪些socket发生了变化。

epoll的三大步骤:

epoll的三大步骤图
Epoll第一步epoll_create:
当某个进程调用epoll_create方法时,内核会创建一个eventpoll对象(epfd文件描述符)
在这里插入图片描述
Epoll的第二步epoll_ctl:
可以用epoll_ctl添加和删除所要监听的socket。
eg:如果通过epoll_ctl添加sock1、sock2和sock3的监视,内核会将eventpoll添加到这三个socket的监视队列中。
在这里插入图片描述
当socket收到数据后,中断程序会操作eventpoll的就绪队列rdlist,而不是直接操作读取数据的进程(如进程A)。当socket2、socket3收到数据后,中断程序让这两个socket进入rdlist。
在这里插入图片描述
Epoll的第三步:epoll_wait:
当程序执行到epoll_wait时,如果rdlist非空则返回,如果rdlist为空则阻塞进程。
在这里插入图片描述
当socket接收到数据,中断程序一方面将其插入rdlist,另一方面唤醒eventpoll中的进程,进程A再次进入内核的工作队列,进程A进入运行状态。
在这里插入图片描述

Linux epoll API:epoll_create

#index
int epoll_create(int size) //创建一个epoll的句柄,自从linux2.6.8之后,size参数是被忽略的。
需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,如果查看/proc/进程id/fd/ ,能够看到这个fd的,所以在使用完epoll后,必须调用close ()关闭,否则可能导致fd被耗尽。
cd /proc/进程id/fd/
ls -l
lrwx--- 1 root root 64 Nov 21 09:44 133 -> /dev/sda1
lrwx--- 1 root root 64 Nov 21 09:44 134 -> /dev/sdb1
lrwx--- 1 root root 64 Nov 21 09:44 136 -> /dev/sdb1
lrwx--- 1 root root 64 Nov 21 09:44 137 -> socket:[232460]
lrwx--- 1 root root 64 Nov 21 09:44 138 -> socket:[7326842]
lrwx--- 1 root root 64 Nov 21 09:44 139 -> socket:[7341066]

soket后面的一串数字是socket的inode号

Linnux epoll API:epoll_ctl:

epoll的事件注册函数,先注册要监听的事件类型

#include
int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);
  • 第一个参数是epoll_create()的返回值 epollfd的句柄epfd。
  • 第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
  • 第三个参数是需要监听的fd,比如说socket A、socket B、socket C。
  • 第四个参数是告诉内核需要监听什么事,使用epoll_event结构

struct epoll_event结构如下:

struct epoll_event{
__uint32_t events;/*Epoll events*/
epoll_data_t data;/*User data variable*/
}

events的事件类型(events可以是以下几个宏的集合)

  • EPOLLIN ∶表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
  • EPOLLOUT:表示对应的文件描述符可以写;
  • EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
  • EPOLLERR:表示对应的文件描述符发生错误;
  • EPOLLHUP:表示对应的文件描述符被挂断;
  • EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
  • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

epoll_event结构data成员变量:
是一个union类型的成员变量,类型定义如下

typedef union epoll_data{
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
}epoll_data_t;
union如果该结构对象属于动态存储类型,其成员具有不确定、灵活的初始值

Linux epoll API:epoll_wait:

等到epoll事件中已经发送的事件,类似于select()调用。

#include
int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);
  • 第二个参数events用来从内核得到事件的集合。epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。
  • 第三个参数maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
  • 第四个参数参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

eventpoll的数据结构

eventpoll的rbr是一颗红黑树,存放所有的socket;rdlist是一个双向链表,存放已经发生IO事件的socket;等待队列为poll_wait,进程A放在poll_wait里;
在这里插入图片描述

拓展知识:

红黑树是一种自平衡二叉查找树,搜索、插入和删除时间复杂度都是O(log(N)),效率较好。eventpoll的rbr等待队列是一颗红黑树,监听所有socket的IO事件。
在这里插入图片描述
(10亿数据红黑树可以进行不到30次比较就可以查到目标)
目录
相关文章
|
7天前
|
监控 Linux
epoll 的用法
【4月更文挑战第16天】epoll 通过改进的接口设计,避免了用户态 - 内核态频繁的数据拷贝,大大提高了系统性能。在使用 epoll 的时候,我们一定要理解条件触发和边缘触发两种模式。
|
3月前
|
存储 安全 网络协议
epoll的实现原理
epoll的实现原理
34 0
|
3月前
|
监控 Java 应用服务中间件
epoll封装reactor原理剖析与代码实现(2)
epoll封装reactor原理剖析与代码实现(2)
33 0
|
3月前
|
Linux API C++
epoll封装reactor原理剖析与代码实现(1)
epoll封装reactor原理剖析与代码实现(1)
60 0
|
3月前
|
监控 Linux API
epoll-reactor模型原理及代码解析
epoll-reactor模型原理及代码解析
47 0
|
3月前
|
安全
实现用户态epoll的原理
实现用户态epoll的原理
13 0
|
5月前
|
存储 监控 安全
2.9 epoll的实现原理
2.9 epoll的实现原理
38 0
|
存储 Linux
I/O 多路复用:select/poll/epoll 实现原理及区别
I/O 多路复用:select/poll/epoll 实现原理及区别
184 0
|
Linux
一文搞懂select、poll和epoll区别
一文搞懂select、poll和epoll区别
622 1
一文搞懂select、poll和epoll区别
|
存储 监控 Linux
一文搞懂select、poll和epoll区别(下)
一文搞懂select、poll和epoll区别
252 0
一文搞懂select、poll和epoll区别(下)