epoll底层红黑树使用部分源码剖析:为什么使用红黑树以及如何使用红黑树

article/2025/9/21 2:09:17

我们知道epoll的底层使用了红黑树来管理文件描述符,为什么会选择红黑树这种结构呢?

以下是个人理解:

epoll和poll的一个很大的区别在于,poll每次调用时都会存在一个将pollfd结构体数组中的每个结构体元素从用户态向内核态中的一个链表节点拷贝的过程,而内核中的这个链表并不会一直保存,当poll运行一次就会重新执行一次上述的拷贝过程,这说明一个问题:poll并不会在内核中为要监听的文件描述符长久的维护一个数据结构来存放他们,而epoll内核中维护了一个内核事件表,它是将所有的文件描述符全部都存放在内核中,系统去检测有事件发生的时候触发回调,当你要添加新的文件描述符的时候也是调用epoll_ctl函数使用EPOLL_CTL_ADD宏来插入,epoll_wait也不是每次调用时都会重新拷贝一遍所有的文件描述符到内核态。当我现在要在内核中长久的维护一个数据结构来存放文件描述符,并且时常会有插入,查找和删除的操作发生,这对内核的效率会产生不小的影响,因此需要一种插入,查找和删除效率都不错的数据结构来存放这些文件描述符,那么红黑树当然是不二的人选。

poll的底层源码和执行步骤可以看一下这篇博客:https://blog.csdn.net/Eunice_fan1207/article/details/99641348

epoll的整个底层运行逻辑参考这篇博客,本文只关注红黑树部分:https://blog.csdn.net/Eunice_fan1207/article/details/99674021

接下来我们来看看epoll底层是如何使用红黑树的

我们知道epoll在添加一个文件描述符进行监听或者删除一个文件描述符时使用的是epoll_ctl函数,该函数底层调用的是sys_epoll_ctl函数,下面给出该函数的部分源码

/** The following function implements the controller interface for* the eventpoll file that enables the insertion/removal/change of* file descriptors inside the interest set.  It represents* the kernel part of the user space epoll_ctl(2).*/
asmlinkage long
sys_epoll_ctl(int epfd, int op, int fd, struct epoll_event __user *event)
{int error;struct file *file, *tfile;struct eventpoll *ep;struct epitem *epi;struct epoll_event epds;DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p)\n",current, epfd, op, fd, event));error = -EFAULT;if (EP_OP_HASH_EVENT(op) &&copy_from_user(&epds, event, sizeof(struct epoll_event)))goto eexit_1;/* Get the "struct file *" for the eventpoll file */error = -EBADF;file = fget(epfd);if (!file)goto eexit_1;/* Get the "struct file *" for the target file */tfile = fget(fd);if (!tfile)goto eexit_2;/* The target file descriptor must support poll */error = -EPERM;if (!tfile->f_op || !tfile->f_op->poll)goto eexit_3;/** We have to check that the file structure underneath the file descriptor* the user passed to us _is_ an eventpoll file. And also we do not permit* adding an epoll file descriptor inside itself.*/error = -EINVAL;if (file == tfile || !IS_FILE_EPOLL(file))goto eexit_3;/** At this point it is safe to assume that the "private_data" contains* our own data structure.*/ep = file->private_data;down_write(&ep->sem);/* Try to lookup the file inside our hash table */epi = ep_find(ep, tfile, fd);

 

在sys_epoll_ctl的参数中,op代表要进行的操作,fd表示要被操作的文件描述符。操作类型定义在下面着三个宏中

/* Valid opcodes to issue to sys_epoll_ctl() */
#define EPOLL_CTL_ADD 1
#define EPOLL_CTL_DEL 2
#define EPOLL_CTL_MOD 3

 

首先呢,会调用ep_find函数在内核事件表也就是红黑树中查找该fd是否已经存在,这里的结果会先保存在epi中,ep_find函数做了什么操作呢?这里就是我们第一个用到红黑树的地方:查找

先来看一下ep_find的实现:

/** Search the file inside the eventpoll hash. It add usage count to* the returned item, so the caller must call ep_release_epitem()* after finished using the "struct epitem".*/
static struct epitem *ep_find(struct eventpoll *ep, struct file *file, int fd)
{int kcmp;unsigned long flags;struct rb_node *rbp;struct epitem *epi, *epir = NULL;struct epoll_filefd ffd;EP_SET_FFD(&ffd, file, fd);read_lock_irqsave(&ep->lock, flags);for (rbp = ep->rbr.rb_node; rbp; ) {epi = rb_entry(rbp, struct epitem, rbn);kcmp = EP_CMP_FFD(&ffd, &epi->ffd);if (kcmp > 0)rbp = rbp->rb_right;else if (kcmp < 0)rbp = rbp->rb_left;else {ep_use_epitem(epi);epir = epi;break;}}read_unlock_irqrestore(&ep->lock, flags);DNPRINTK(3, (KERN_INFO "[%p] eventpoll: ep_find(%p) -> %p\n",current, file, epir));return epir;
}

这里的for循环就是一个红黑树的查找过程,我们可以看到这里查找的时候用到的一个变量是kcmp,这个kcmp的值就是我们的fd在红黑树中所用来排序的值。而且我们可以看到这个kcmp的值来源于宏函数EP_CMP_FFD我们来看一下这个宏函数的实现

/* Compare rb-tree keys */
#define EP_CMP_FFD(p1, p2) ((p1)->file > (p2)->file ? +1: \((p1)->file < (p2)->file ? -1: (p1)->fd - (p2)->fd))

根据该宏函数的实现我们看到在比较时其实使用的是一个epoll_filefd的结构体中的file成员来比较的,那么我们再进入epoll_filefd中查看一下

我们看到这里的file是一个struct file类型的指针,当我们比较两个file类型的指针时比较的是他们的指针的值,也就是file结构体的地址。

根据源码判断,在红黑树中排序的根据是file的地址大小。至于为什么,目前还并不是很清楚,也存在我理解错误的可能,这里不是很确定。

查找完毕后,就要开始进行具体的操作了,这里会根据宏的值判断应该进行的操作是插入,删除,还是修改。这里给出sys_epoll_ctl的剩余源码(和文章开头给出的前半部分刚好衔接)

error = -EINVAL;switch (op) {case EPOLL_CTL_ADD:if (!epi) {epds.events |= POLLERR | POLLHUP;error = ep_insert(ep, &epds, tfile, fd);} elseerror = -EEXIST;break;case EPOLL_CTL_DEL:if (epi)error = ep_remove(ep, epi);elseerror = -ENOENT;break;case EPOLL_CTL_MOD:if (epi) {epds.events |= POLLERR | POLLHUP;error = ep_modify(ep, epi, &epds);} elseerror = -ENOENT;break;}/** The function ep_find() increments the usage count of the structure* so, if this is not NULL, we need to release it.*/if (epi)ep_release_epitem(epi);up_write(&ep->sem);eexit_3:fput(tfile);
eexit_2:fput(file);
eexit_1:DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %p) = %d\n",current, epfd, op, fd, event, error));return error;
}

我们看到这部分代码里最主要的工作就是进行这个switch,case语句所做的判断工作了,这里sys_epoll_ctl函数根据参数op的不同而调用不同的函数进行处理,我们以EPOLL_CTL_ADD宏举例,该宏要进行的操作是插入一个新的文件描述符。

epoll底层的红黑树插入是调用ep_insert插入的,而ep_insert函数里面调用了ep_rbtree_insert来进行对红黑树中一个节点的插入。这两个函数的声明如下:

static void ep_rbtree_insert(struct eventpoll *ep, struct epitem *epi);
static int ep_insert(struct eventpoll *ep, struct epoll_event *event,struct file *tfile, int fd);

我们忽略ep_insert函数其他的实现要点,直接查看它所调用的函数ep_retree_insert的实现

static void ep_rbtree_insert(struct eventpoll *ep, struct epitem *epi)
{int kcmp;struct rb_node **p = &ep->rbr.rb_node, *parent = NULL;struct epitem *epic;while (*p) {parent = *p;epic = rb_entry(parent, struct epitem, rbn);kcmp = EP_CMP_FFD(&epi->ffd, &epic->ffd);if (kcmp > 0)p = &parent->rb_right;elsep = &parent->rb_left;}rb_link_node(&epi->rbn, parent, p);rb_insert_color(&epi->rbn, &ep->rbr);
}

可以看到这里在插入一个新节点时对于其在红黑树中的位置的选择过程是用一个while循环来实现的,当该while循环退出后,说明我们已经找到了该节点应在的位置,接下来调用rb_link_node函数将该节点插入到红黑树中,该函数的实现很简单,就是往一颗二叉树中插入一个新的节点,实现如下

static inline void rb_link_node(struct rb_node * node, struct rb_node * parent,struct rb_node ** rb_link)
{node->rb_parent = parent;node->rb_color = RB_RED;node->rb_left = node->rb_right = NULL;*rb_link = node;
}

 

然后再调用rb_insert_color函数,这个函数实现的是对插入一个新节点之后的整个红黑树进行调整的过程,这里牵扯到红黑树的旋转,不是我们本文的重点,只把代码贴上,有兴趣的同学可以下去自习。

void rb_insert_color(struct rb_node *node, struct rb_root *root)
{struct rb_node *parent, *gparent;while ((parent = node->rb_parent) && parent->rb_color == RB_RED){gparent = parent->rb_parent;if (parent == gparent->rb_left){{register struct rb_node *uncle = gparent->rb_right;if (uncle && uncle->rb_color == RB_RED){uncle->rb_color = RB_BLACK;parent->rb_color = RB_BLACK;gparent->rb_color = RB_RED;node = gparent;continue;}}if (parent->rb_right == node){register struct rb_node *tmp;__rb_rotate_left(parent, root);tmp = parent;parent = node;node = tmp;}parent->rb_color = RB_BLACK;gparent->rb_color = RB_RED;__rb_rotate_right(gparent, root);} else {{register struct rb_node *uncle = gparent->rb_left;if (uncle && uncle->rb_color == RB_RED){uncle->rb_color = RB_BLACK;parent->rb_color = RB_BLACK;gparent->rb_color = RB_RED;node = gparent;continue;}}if (parent->rb_left == node){register struct rb_node *tmp;__rb_rotate_right(parent, root);tmp = parent;parent = node;node = tmp;}parent->rb_color = RB_BLACK;gparent->rb_color = RB_RED;__rb_rotate_left(gparent, root);}}root->rb_node->rb_color = RB_BLACK;
}

总结:这篇文章给出了epoll底层的红黑树的插入和查找步骤,只是一个大概的总结,源码的实现很精彩,第一次进行源码剖析,有讲解不清的地方可以在评论区提出。


http://chatgpt.dhexx.cn/article/HIWjnujc.shtml

相关文章

HashMap在jdk1.8为何引入了红黑树?

原创不易,麻烦点个关注,点个赞,谢谢各位。 二叉查找树 二叉查找树,也称有序二叉树(ordered binary tree),或已排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树: 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若任…

从电路的角度理解特征阻抗

传输显得特征阻抗不是真实的电阻&#xff0c;微波技术课程会从波的角度描述特征阻抗&#xff0c;这次试图从电路的角度来理解 无损传输线是分布的L C 网络&#xff0c;假设是无限长传输线 从a,b两点看入的阻抗是相等的&#xff0c;所以可以简化成下图&#xff1a; 化简可得 这…

同轴电缆阻抗总结(电阻、阻抗、特性阻抗)

文章目录 同轴电缆电阻、阻抗、特性阻抗电阻阻抗&#xff08;Impedance&#xff09;特性阻抗 总结 同轴电缆 同轴电缆是一种电线及信号传输线&#xff0c;一般是由四层物料造成&#xff1a;最内里是一条导电铜线&#xff0c;线的外面有一层塑胶&#xff08;作绝缘体、电介质之…

传输线阻抗方程的推导

在传输线理论中&#xff0c;当一段特征阻抗为 Z 0 Z_0 Z0​ 的传输线的终端连接了一个阻抗为 Z L Z_L ZL​ 的负载时&#xff0c;看向这段传输线的输入阻抗 Z i n Z_{in} Zin​ 将不再是 Z 0 Z_0 Z0​。 传输线阻抗方程 (Transmission Line Impedance Equation) 就是计算…

PCB阻抗计算

阻抗匹配是指在能量传输时&#xff0c;要求负载阻抗要和传输线的特征阻抗相等&#xff0c;此时的传输不会产生反射&#xff0c;这表明所有能量都被负载吸收了。反之则在传输中有能量损失。在高速PCB设计中&#xff0c;阻抗的匹配与否关系到信号的质量优劣&#xff0c;下面简单介…

特征阻抗和阻抗匹配_没有诸如对象关系阻抗不匹配之类的东西

特征阻抗和阻抗匹配 过去十年来&#xff0c;ORM的许多批评都错了这一点&#xff0c;因为它不准确。 到本文结尾&#xff0c;我们将得出以下结论&#xff1a; 关系&#xff08;数据&#xff09;模型和面向对象的模型之间没有显着差异 如何得出这个结论&#xff1f; 继续阅读&a…

传输线特征阻抗计算

一直有很多人问我阻抗怎么计算的. 人家问多了,我想给大家整理个材料,于己于人都是个方便.如果大家还有什么问题或者文档有什么错误,欢迎讨论与指教! 在计算阻抗之前,我想很有必yi要理解这儿阻抗的意义 传输线阻抗的由来以及意义 传输线阻抗是从电报方程推导出来(具体可以查询微…

PCB特征阻抗计算

见教程&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1V4UbEoKfMD1bilwu-Qwdyg 密码&#xff1a;ml6t

射频特征阻抗

Characteris Impendance(特性阻抗&#xff0c;也称为‘特征阻抗’)是我们经常看到并使用自己的术语之一&#xff0c;但非常模糊且难以解释。以下是来自几个不同来源的Characteris Impendance(特性阻抗)的一些定义。 &#xff08;如果您检查10个不同的来源&#xff0c;您会看到1…

高速PCB的特征阻抗设计

我们在高速PCB设计当中,经常对高速信号线做特征阻抗控制来优化信号质量。那特征阻抗是什么东西呢? 1.传输线原理 介绍特征阻抗之前,我们复习下《信号完整性视频》介绍的传输线基本原理。如下图左边是低频电路采用集总参数的RLGC模型,右边高频电路采用分布参数的RLGC模型。…

传输线的波阻抗与特征阻抗

以上是时域方程&#xff0c;而我们的“波阻抗”是定义在频域下的&#xff08;正弦激励&#xff09;。 1&#xff09;“相电压/电流”的第一、二项分别代表了前向传输、反向传输分量&#xff1b; 2&#xff09;前向传输和反向传输分量两者无必然联系。 补充修改&#xff1a; 1&…

PCB寄生参数和特征阻抗

1、微带线Microstrip 相同情况下&#xff0c;PCB板厚H越厚&#xff08;影响很大&#xff09;&#xff1a; 特征阻抗越大&#xff08;H↑ > ln()↑ > Z0↑&#xff09;传输延时几乎不变&#xff08;与H无关&#xff09;寄生电感越大&#xff08;H↑ > ln()↑ > L…

传输线的特征阻抗

要理解特征阻抗首先要建立一个模型。传输线零阶模型 在这个模型中&#xff0c;每一个步长是△X&#xff0c;单位长度的电容为CL&#xff0c;所以每个步长的电容 CCL*△X 然后我们根据电荷量 QU*CI*t &#xff0c;电流I Q / t C * U / t&#xff0c;其中t △X / v得到 电流 …

阻抗,特征阻抗与等效阻抗

目录 一、阻抗 二、 特征阻抗 三、等效阻抗 射频的黄金三角之一就是阻抗&#xff0c;我们在射频设计中&#xff0c;会经常与阻抗打交道&#xff0c;比如特征阻抗&#xff0c;负载阻抗&#xff0c;阻抗匹配等等。更多的时候&#xff0c;我们所设计的射频电路就是一个阻抗匹配…

特性阻抗介绍

特性阻抗:又称“特征阻抗”,它不是直流电阻,属于长线传输中的概念。在高频范围内,信号传输过程中,信号沿到达的地方,信号线和参考平面(电源或地平面)间由于电场的建立,会产生一个瞬间电流,如果传输线是各向同性的,那么只要信号在传输,就始终存在一个电流I,而如果信…

单播、广播和多播地址以及组播ip与组播mac间的换算

转自&#xff1a;https://www.cnblogs.com/songdada/articles/4039468.html 除地址类外&#xff0c;还可根据传输的消息特征将IP地址分为单播、广播或多播。主机使用IP地址进行一对一&#xff08;单播&#xff09;、一对多&#xff08;多播&#xff09;或一对所有&#xff08;…

IP组播----组播基础 组播服务模型、组播地址

一、简介 IPv4传输方式有三种&#xff1a;单播、组播、广播 单播&#xff1a;信息源为每个需要信息的主机都发送一份独立的报文组播&#xff1a;信息源将保温发送到一个特定的组播IP地址&#xff0c;只有加入了这个组的主机才能接收广播&#xff1a;信息源将信息发送给网段中…

组播的地址范围

2019独角兽企业重金招聘Python工程师标准>>> 组播的地址是保留的D类地址从224.0.0.0—239.255.255.255&#xff0c;而且一些地址有特定的用处如&#xff0c;224.0.0.0—244.0.0.255只能用于局域网中路由器是不会转发的&#xff0c;并且224.0.0.1是所有主机的地址&am…

组播地址分类 Cyrus

一、组播地址分类 Multicast地址&#xff1a;224.0.0.0-239.255.255.255第一组八位元组为1110 Multicast地址也分为&#xff1a;预留的局部链路地址、全球范围地址、限制范围地址和GLOP地址。 >预留的局部链路地址(reserved link local address)&#xff1a; 保留给本地网段…

IPv6的组播地址

理解IPV6的组播地址 IPv6的组播地址通常是为IPv6的组播服务&#xff0c;而IPv6通信的核心大量的使用了组播&#xff0c;IPv6不再使用广播&#xff0c;这与IPv4的通信不同&#xff0c;然而要理解IPv6的组播&#xff0c;首先需要明白三个关键点&#xff1a; 第一、任何节点都能…