epoll nginx 核心详解_epoll详解——从功能到内核

首先我们了解一下什么是I/O复用。I/O就是指网络中的I/O(即输入输出),多路是指多个TCP连接,复用是指一个或少量线程被重复使用。连起来理解就是,用少量的线程来处理网络上大量的TCP连接中的I/O。常见的I/O复用有以下三种:select

poll

epoll

为什么使用epoll?

这个问题也可以理解为epoll相比于select和poll有什么缺点。首先我们来分析一下select。

select函数声明:

#include

#include

int select(int maxfdpl,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);

函数第一个参数是被监听的描述符的最大值+1,select底层的数据结构是位数组,因此必须知道被监听的最大描述符才可以确定描述符的范围,否则就需要将整个数组遍历一遍。

函数第二、三、四个参数是被监听的事件,分别是读、写、异常事件。

函数的最后一个参数是监听的时间(NULL、0、正值)

selct缺点:可以从函数参数列表上来看,select只能监听读、写、异常这三个事件

selct监听的描述符是有最大值限制的,在Linux内核中是1024

select的实现是每次将待检测的描述符放在位数组中,全部传给内核进行监听,内核监听之后会返回一个就绪描述符个数,并且修改了监听的事件值,以表示该事件就绪。内核再将修改后的数组传给用户空间。用户空间只能通过遍历所有描述符来处理就绪的描述符,之后再将描述符传给内核继续监听......很明显,这样在监听的描述符少的情况下并不影响效率,但是监听的描述符数量特别大的情况下,每次又只有少数描述符上有事件就绪,大量的换入换出会使得效率十分低下

第二步,我们接着分析一下poll

poll函数声明:

int poll(struct pollfd fdarray[],unsigned long nfds,int timeout);

struct pollfd{

int fd;

short events;

short revents;

};

第一个参数是个结构体数组,结构体中声明了被监听描述符和相应的事件,每个被监听的描述符对应一个结构体,数组表示可以监听多个描述符。

第二个参数是被监听描述符的个数。

第三个参数同select,只监听时间

poll缺点:

从函数参数来看,poll解决了select前两个问题,监听的描述符数量没有严格限制,监听的事件不止读、写、异常,但是第三个缺点依然存在,存在大量的换入换出。

在Linux2.4及以前的版本中使用的是select和poll,在2.6的时候使用的是epoll,正是因为epoll解决了select和poll共同存在的问题。接下来我们看一下epoll是怎么解决的。

epoll的相关函数

#include

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内核剖析epoll_create

epoll_create的做法是创建出一个内核事件表,实际上就是创建文件,这其中包括文件描述符的分配、文件实体的分配等等,在这里我们记住文件描述符中有一个域很重要,就是:private_data域,该域才是epoll的核心,其中有内核时间表、就绪描述符队列等信息。如下图:

epoll_ctl

该函数主要是对内核事件表的操作,涉及插入(添加监听描述符)、删除(删除被监听的描述符)、修改(修改被监听的描述符)。主要有以下步骤:遍历内核事件表,看该描述符是否在内核事件表中。

判断所要做的操作:插入、删除或是修改

根据操作做相应处理

注意:此处注意的一点是在插入的时候,对相应的描述符注册了回调函数,即当该描述符上有数据就绪时,自动调用回调函数将该描述符加入就绪队列epoll_wait

在看epoll_wait之前,我们来以下内核事件表和组织就绪描述符的数据结构。内核事件表的底层数据结构是红黑树,就绪描述符的底层数据结构是链表。

epoll_wait的功能就是不断查看就绪队列中有没有描述符,如果没有就一直检查、直到超时。如果有就绪描述符,就将就绪描述符通知给用户。此处有关ET和LT模式就是在给用户空间返回就绪描述符的时候体现的。

回顾以下ET模式和LT模式:ET模式是高效模式,就绪描述符只通知用户一次,如果用户没做处理内核将不再进行通知;LT模式比较稳定,如果用户没有处理就绪的描述符,内核会不断通知。

接下来我们看一下具体是怎么实现的。当为ET模式时,上边我们提到就绪描述符是用链表组织的,因此只需将就绪部分断链发给用户,而在LT模式下,用户没有处理就绪描述符时,内核会再次将未处理的就绪描述符加入到就绪队列中重复提醒用户空间。

文末给大家分享c/c++ Linux服务器高阶知识视频资料的朋友可以加群720209036获取

知识点有C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
epoll_ctl函数是用来向epoll实例中添加、修改或删除文件描述符的,其函数原型如下: ```c int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); ``` 其中,epfd是epoll实例的文件描述符,op指定操作类型,可以是EPOLL_CTL_ADD、EPOLL_CTL_MOD或EPOLL_CTL_DEL,fd是需要添加、修改或删除的文件描述符,event是一个epoll_event结构体,用来描述需要监听的事件类型和其他信息。 EPOLL_CTL_ADD操作用于向epoll实例中添加新的文件描述符,如果fd已经存在于epoll实例中,则会返回错误。event结构体中需要填写的字段包括: - events:需要监听的事件类型,可以是EPOLLIN、EPOLLOUT、EPOLLRDHUP、EPOLLPRI或EPOLLERR等。 - data:用户自定义数据,可以是任意类型的指针。 EPOLL_CTL_MOD操作用于修改已经存在于epoll实例中的文件描述符的监听事件类型和用户自定义数据。event结构体中需要填写的字段同EPOLL_CTL_ADD操作。 EPOLL_CTL_DEL操作用于从epoll实例中删除文件描述符,event可以为NULL。 需要注意的是,在使用epoll_ctl函数添加或修改文件描述符时,需要将文件描述符设置为非阻塞模式,否则可能会出现阻塞的情况。 下面是一个使用epoll_ctl函数添加文件描述符的例子: ```c int epoll_fd = epoll_create(10); // 创建epoll实例 struct epoll_event ev; ev.events = EPOLLIN | EPOLLET; // 监听读事件,使用ET模式 ev.data.fd = fd; // 需要监听的文件描述符 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { perror("epoll_ctl"); exit(EXIT_FAILURE); } ``` 这段代码将fd添加到epoll实例中,并监听其读事件,使用ET模式。如果添加失败,则输出错误信息并退出程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值