【Linux】【进程】epoll内核实现

news2025/2/18 18:27:21

【Linux】【进程】epoll内核实现

1 epoll提供的三个函数

1.1 epoll_create(int size);

  • epoll_create()成功返回内核事件表的文件描述符,失败返回-1
  • size 参数现在并不起作用

1.2 epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

  • epoll_ctl()成功返回 0,失败返回-1
  • epfd 参数指定要操作的内核事件表的文件描述符
  • op 参数指定操作类型:
    • EPOLL_CTL_ADD 往内核事件表中注册 fd 上的事
    • EPOLL_CTL_MOD 修改 fd 上的注册事件
    • EPOLL_CTL_DEL 删除 fd 上的注册事件
  • fd 参数指定要操作的文件描述符
  • event 参数指定事件,它是 epoll_event 结构指针类型
struct epoll_event
{
	_uint32_t events; // epoll 事件
	epoll_data_t data; // 用户数据
 };
  • events 成员描述事件类型
  • data 成员用于存储用户数据
typedef union epoll_data
{
 void *ptr
 int fd;
 uint32_t u32;
 uint64_t u64;
 }epoll_data_t;

1.3 epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

  • epoll_wait()成功返回就绪的文件描述符的个数,失败返回-1,超时返回 0
  • epfd 参数指定要操作的内核事件表的文件描述符
  • events 参数是一个用户数组指针,这个数组仅仅在 epoll_wait 返回时保存内核检测到的所有就绪事件
  • maxevents 参数指定用户数组的大小,即指定最多监听多少个事件,它必须大于0
  • timeout 参数指定超时时间,单位为毫秒,如果 timeout 为 0,则 epoll_wait 会立即返回,如果 timeout 为-1,则 epoll_wait 会一直阻塞,直到有事件就绪。

events[i].*data*.fd 能够拿到就绪的文件描述符
events[i].event 能拿到关注的事件

2.内核实现

2.1 epoll_create(int size);

系统调用epoll_create()会创建一个epoll实例并返回该实例对应的文件描述符fd。在内核中,每个epoll实例会和一个struct eventpoll类型的对象一一对应,该对象是epoll的核心,其在fs/eventpoll.c文件中.

1.首先创建一个eventpoll对象(–>kmalloc分配空间)

struct eventpoll *ep = NULL;
...
error = ep_alloc(&ep);
if (error < 0)
    return error;

2.然后分配一个未使用的文件描述符

fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
if (fd < 0) {
    error = fd;
    goto out_free_ep;
}

3.然后创建一个struct file对象

  • 将file中的struct file_operations *f_op设置为全局变量eventpoll_fops
  • 将void *private指向刚创建的eventpoll对象
struct file *file;
...
file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep, O_RDWR | (flags & O_CLOEXEC));
if (IS_ERR(file)) {
    error = PTR_ERR(file);
    goto out_free_fd;
}

4.然后设置eventpoll中的file指针:

ep->file = file;

5.最后将文件描述符添加到当前进程的文件描述符表中,并返回给用户:

fd_install(fd, file);<br>returnfd;

在这里插入图片描述

2.2 epoll_ctl()

1.首先判断op是不是删除操作,如果不是则将event参数从用户空间拷贝到内核中:

struct epoll_event epds;
...
if (ep_op_has_event(op) &&
     copy_from_user(&epds, event, sizeof(struct epoll_event)))
         goto error_return;
ep_op_has_event()实际就是判断op是不是删除操作:

static inline int ep_op_has_event(int op)
{
    return op != EPOLL_CTL_DEL;
}

2.接下来判断用户是否设置了EPOLLEXCLUSIVE标志

  • 主要是为了解决同一个文件描述符同时被添加到多个epoll实例中造成的“惊群”问题,
  • 这个标志的设置有一些限制条件,比如只能是在EPOLL_CTL_ADD操作中设置,而且对应的文件描述符本身不能是一个epoll实例,下面代码就是对这些限制的检查:
/*
 *epoll adds to the wakeup queue at EPOLL_CTL_ADD time only,
 * so EPOLLEXCLUSIVE is not allowed for a EPOLL_CTL_MOD operation.
 * Also, we do not currently supported nested exclusive wakeups.
 */
 if (epds.events & EPOLLEXCLUSIVE) {
     if (op == EPOLL_CTL_MOD)
         goto error_tgt_fput;
     if (op == EPOLL_CTL_ADD && (is_file_epoll(tf.file) ||
            (epds.events & ~EPOLLEXCLUSIVE_OK_BITS)))
         goto error_tgt_fput;
}

3.接下来从传入的文件描述符开始,一步步获得struct file对象,再从struct file中的private_data字段获得struct eventpoll对象:

struct fd f, tf;
struct eventpoll *ep;
...
f = fdget(epfd);
...
tf = fdget(fd);
...
ep = f.file->private_data;

4.接下来会从epoll实例的红黑树里寻找和被监控文件对应的**epollitem对象,如果不存在,也就是之前没有添加过该文件,返回的会是NULL。**

struct epitem *epi;
...
epi = ep_find(ep, tf.file, fd);
ep_find()函数本质是一个红黑树查找过程,红黑树查找和插入使用的比较函数是ep_cmp_ffd(),先比较struct file对象的地址大小,相同的话再比较文件描述符大小。struct file对象地址相同的一种情况是通过dup()系统调用将不同的文件描述符指向同一个struct file对象。

static inline int ep_cmp_ffd(struct epoll_filefd *p1, <br>                             struct epoll_filefd *p2)
{
        return (p1->file > p2->file ? +1:
                (p1->file < p2->file ? -1 : p1->fd - p2->fd));
}

5.接下来会根据操作符op的不同做不同的处理,这里我们只看op等于EPOLL_CTL_ADD时的添加操作。

  • 首先会判断上一步操作中返回的epollitem对象地址是否为NULL,不是NULL说明该文件已经添加过了,返回错误,
  • 否则调用ep_insert()函数进行真正的添加操作。在添加文件之前内核会自动为该文件增加POLLERR和POLLHUP事件。
if (!epi) {
    epds.events |= POLLERR | POLLHUP;
    error = ep_insert(ep, &epds, tf.file, fd, full_check);
} else
    error = -EEXIST;
if (full_check)
    clear_tfile_check_list();

6.ep_insert()函数中,首先判断epoll实例中监视的文件数量是否已超过限制,没问题则为待添加的文件创建一个epollitem对象并对其初始化。

int error, revents, pwake = 0;
unsigned long flags;
long user_watches;
struct epitem *epi;
struct ep_pqueue epq;
  
user_watches = atomic_long_read(&ep->user->epoll_watches);
if (unlikely(user_watches >= max_user_watches))
        return -ENOSPC;
if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
        return -ENOMEM;

接下来是对epollitem的初始化:

INIT_LIST_HEAD(&epi->rdllink);
INIT_LIST_HEAD(&epi->fllink);
INIT_LIST_HEAD(&epi->pwqlist);
epi->ep = ep;
ep_set_ffd(&epi->ffd, tfile, fd);
epi->event = *event;
epi->nwait = 0;
epi->next = EP_UNACTIVE_PTR;
if (epi->event.events & EPOLLWAKEUP) {
        error = ep_create_wakeup_source(epi);
        if (error)
                goto error_create_wakeup_source;
} else {
        RCU_INIT_POINTER(epi->ws, NULL);
}

7.接下来将epollitem对象添加到被监视文件的内部注册回调函数链表
因为不同文件系统的实现不同,无法直接通过struct file对象获取等待队列,因此这里通过struct file的poll操作,以回调的方式返回对象的等待队列,这里设置的回调函数是ep_ptable_queue_proc:

struct ep_pqueue epq;
...
/* Initialize the poll table using the queue callback */
epq.epi = epi;
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
 
/*
 * Attach the item to the poll hooks and get current event bits.
 * We can safely use the file* here because its usage count has
 * been increased by the caller of this function. Note that after
 * this operation completes, the poll callback can start hitting
 * the new item.
 */
revents = ep_item_poll(epi, &epq.pt);

上面代码中结构体ep_queue的作用是能够在poll的回调函数中取得对应的epollitem对象

static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
                                 poll_table *pt)
{
        struct epitem *epi = ep_item_from_epqueue(pt);
        struct eppoll_entry *pwq;
 
        if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
                init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
                pwq->whead = whead;
                pwq->base = epi;
                if (epi->event.events & EPOLLEXCLUSIVE)
                        add_wait_queue_exclusive(whead, &pwq->wait);
                else
                        add_wait_queue(whead, &pwq->wait);
                list_add_tail(&pwq->llink, &epi->pwqlist);
                epi->nwait++;
        } else {
                /* We have to signal that an error occurred */
                epi->nwait = -1;
        }
}

eppoll_entry和epitem等结构关系如下图:
在这里插入图片描述
8.在**回到ep_insert()**函数中。ep_item_poll()调用完成之后,会将epitem中的fllink字段添加到struct file中的f_ep_links链表中,这样就可以通过struct file找到所有对应的struct epollitem对象,进而通过struct epollitem找到所有的epoll实例对应的struct eventpoll。

spin_lock(&tfile->f_lock);
list_add_tail_rcu(&epi->fllink, &tfile->f_ep_links);
spin_unlock(&tfile->f_lock);

9.然后就是将epollitem插入到红黑树中:
最后再更新下状态就返回了,插入操作也就完成了。

在返回之前还会判断一次刚才添加的文件是不是当前已经有事件就绪了,如果是就将其加入到epoll的就绪链表中

在这里插入图片描述

2.3 epoll_wait()

检查就绪事件

  • 遍历 ep->rdlist,若有事件,直接返回。
  • 遍历 rdlist,填充用户的 events[] 数组。
  • 返回 就绪的事件数,用户程序可以立即处理。

参考:
https://man7.org/linux/man-pages/man2/epoll_ctl.2.html
https://zhuanlan.zhihu.com/p/438528396

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2297769.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ICRA-2025 | 具身导航如何跨越地形障碍?SARO:通过视觉语言模型实现地形穿越

作者&#xff1a;Shaoting Zhu, Derun Li, Linzhan Mou, Yong Liu, Ningyi Xu, Hang Zhao 单位&#xff1a;清华大学交叉信息研究院&#xff0c;上海交通大学电子信息与电气工程学院&#xff0c;浙江大学计算机科学与技术学院&#xff0c;宾夕法尼亚大学GRASP实验室&#xff0…

当 LSTM 遇上 ARIMA!!

大家好&#xff0c;我是小青 ARIMA 和 LSTM 是两种常用于时间序列预测的模型&#xff0c;各有优劣。 ARIMA 擅长捕捉线性关系&#xff0c;而 LSTM 擅长处理非线性和长时间依赖的关系。将ARIMA 和 LSTM 融合&#xff0c;可以充分发挥它们各自的优势&#xff0c;构建更强大的时…

终结磁盘空间紧张局面,针对性处理重复、无用文件

软件介绍 在如今这个数字化浪潮汹涌的时代&#xff0c;咱们的电脑存储空间就像一个杂乱无章的储物间&#xff0c;被各种各样的重复文件塞得满满当当。这些重复文件&#xff0c;犹如隐藏在暗处的 “空间小偷”&#xff0c;悄无声息地吞噬着宝贵的硬盘空间&#xff0c;使得原本井…

高校LabVIEW开发调试中的常见问题

在高校进行LabVIEW开发调试时&#xff0c;常常面临硬件选型不当、方案设计不合理、布线不专业以及人员流动性强等问题。这些问题可能影响项目的进展和质量。本文将总结这些问题&#xff0c;并给出具体的解决方案&#xff0c;帮助学生和团队更高效地开展开发工作。 ​ 1. 硬件选…

【故障处理】- RMAN-06593: platform name ‘Linux x86 64-bitElapsed: 00:00:00.00‘

【故障处理】- RMAN-06593: platform name Linux x86 64-bitElapsed: 00:00:00.00 一、概述二、报错原因三、解决方法 一、概述 使用xtts迁移&#xff0c;在目标端进行恢复时&#xff0c;遇到RMAN-06593: platform name Linux x86 64-bitElapsed: 00:00:00.00’报错。 二、报错…

K8S下载离线安装包所需文件

下载相关文件 官网下载地址集合https://kubernetes.io/zh-cn/releases/download/ 下载相关镜像 官网镜像描述 所有 Kubernetes 容器镜像都被部署到 registry.k8s.io 容器镜像仓库。 容器镜像支持架构registry.k8s.io/kube-apiserver:v1.32.0amd64, arm, arm64, ppc64le, …

如何使用Java语言在Idea和Android中分别建立服务端和客户端实现局域网聊天

手把手教你用Java语言在Idea和Android中分别建立服务端和客户端实现局域网聊天 目录 文章目录 手把手教你用**Java**语言在**Idea**和**Android**中分别建立**服务端**和**客户端**实现局域网聊天**目录**[toc]**基本实现****问题分析****服务端**Idea:结构预览Server类代码解…

ArcGIS注册开发账号及API KEY

注册与激活 Sign up | ArcGIS Location Platform 填写信息&#xff0c;然后邮箱收到激活邮件&#xff0c;激活&#xff0c;再补充信息。 参考 Tutorial: Create an API key | Documentation | Esri Developer 产生API KEY Tutorial: Create an API key | Documentation |…

java八股---java面向对象

面向对象 面向对象概述 面向对象和面向过程的区别 面向过程&#xff1a; 优点&#xff1a;性能比面向对象高&#xff0c;因为类调用时需要实例化&#xff0c;开销比较大&#xff0c;比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发&#xff0c;性能…

《Deepseek入门到精通》2.0版本《Deepseek赋能职场应用》清华大学

&#x1f680; 《Deepseek入门到精通》2.0版本重磅发布&#xff01; &#x1f4da; 全新升级&#xff0c;赋能职场应用&#xff01; 经过多次改版与优化&#xff0c;《Deepseek入门到精通》2.0版本已经正式上线&#xff01;这不仅是一份技术指南&#xff0c;更是你提升职场竞争…

使用c++实现红黑树的构建和插入

1.红黑树简介&#xff1a; 红黑树实际上和AVL都属于一棵用于存储数据的平衡二叉搜索树&#xff0c;但是这棵树并不是使用平衡因子去维持平衡的&#xff0c;而是结合限制条件对结点标红标黑去让树达到类似平衡的效果。 2.红黑树的限制条件和效率分析&#xff1a; 2.1限制条件…

在大型语言模型(LLM)框架内Transformer架构与混合专家(MoE)策略的概念整合

文章目录 传统的神经网络框架存在的问题一. Transformer架构综述1.1 transformer的输入1.1.1 词向量1.1.2 位置编码&#xff08;Positional Encoding&#xff09;1.1.3 编码器与解码器结构1.1.4 多头自注意力机制 二.Transformer分步详解2.1 传统词向量存在的问题2.2 详解编解码…

Jenkins项目CICD流程

Jenkins项目流程:1.配置git环境 git config --...2.把前后端的目录初始化位本地工作目录 #git init3.提交到本地git #git add ./ git commit -m "" git tag v14.然后提交到远程git(通过,用户,群组,项目,管理项目)git remote add origin http://...git push -…

【IDEA】2017版本的使用

目录 一、常识 二、安装 1. 下载IDEA2017.exe 2. 安装教程 三、基本配置 1. 自动更新关掉 2. 整合JDK环境 3. 隐藏.idea文件夹和.iml等文件 四、创建Java工程 1. 新建项目 2. 创建包结构&#xff0c;创建类&#xff0c;编写main主函数&#xff0c;在控制台输出内容。…

Git指南-从入门到精通

代码提交和同步命令 流程图如下&#xff1a; 第零步: 工作区与仓库保持一致第一步: 文件增删改&#xff0c;变为已修改状态第二步: git add &#xff0c;变为已暂存状态 bash $ git status $ git add --all # 当前项目下的所有更改 $ git add . # 当前目录下的所有更改 $ g…

Spring boot(maven) - Mybatis 超级入门版

前言&#xff1a; 通过实践而发现真理&#xff0c;又通过实践而证实真理和发展真理。从感性认识而能动地发展到理性认识&#xff0c;又从理性认识而能动地指导革命实践&#xff0c;改造主观世界和客观世界。实践、认识、再实践、再认识&#xff0c;这种形式&#xff0c;循环往…

Spark 性能优化 (三):RBO 与 CBO

1. RBO 的核心概念 在 Apache Spark 的查询优化过程中&#xff0c;规则优化&#xff08;Rule-Based Optimization, RBO&#xff09; 是 Catalyst 优化器的一个关键组成部分。它主要依赖于一组固定的规则进行优化&#xff0c;而不是基于统计信息&#xff08;如 CBO - Cost-Base…

读 DeepSeek-R1 论文笔记

DeepSeek-R1&#xff1a;通过强化学习激发大语言模型的推理能力 DeepSeek-AI 摘要 我们推出第一代推理模型DeepSeek-R1-Zero和DeepSeek-R1。DeepSeek-R1-Zero作为无需监督微调(SFT)预训练阶段、直接通过大规模强化学习(RL)训练的基础模型&#xff0c;展现出卓越的推理能力。…

【Android开发AI实战】选择目标跟踪基于opencv实现——运动跟踪

文章目录 【Android 开发 AI 实战】选择目标跟踪基于 opencv 实现 —— 运动跟踪一、引言二、Android 开发与 AI 的融合趋势三、OpenCV 简介四、运动跟踪原理&#xff08;一&#xff09;光流法&#xff08;二&#xff09;卡尔曼滤波&#xff08;三&#xff09;粒子滤波 五、基于…

如何保持长久无痛苦的学英语?

“无痛苦”学英语&#xff1f; 听起来像天方夜谭&#xff0c;但并非不可能&#xff01; 关键在于&#xff0c;把英语学习变成你生活的一部分&#xff0c;融入你的兴趣和目标&#xff0c; 这样才能摆脱痛苦&#xff0c;享受学习的过程。 1. 兴趣是最好的老师&#xff1a; 找到自…