Linux 下 Epoll 机制概述

Android进阶——Handler底层fd监听之epoll机制 Handler 消息循环机制底层也是依赖于epoll 实现的。 阅读详情

在深入系统的学习Handler的时候,我们接触到了Looper之所以死循环不会导致CPU使用率过高,是因为使用了Linux下的epoll机制。

Android的应用层通过Message.java实现队列,利用管道和epoll机制实现线程状态的管理,配合起来实现了Android主线程的消息队列模型。

本文参考链接:

深入理解 Epoll:https://zhuanlan.zhihu.com/p/93609693

一、Epoll介绍

Epoll是linux2.6内核的一个新的系统调用,Epoll在设计之初,就是为了替代select,Epoll线性复杂度的模型,epoll的时间复杂度为O(1), 也就意味着,Epoll在高并发场景,随着文件描述符的增长,有良好的可扩展性。

select 和 poll 监听文件描述符list,进行一个线性的查找 O(n)
epoll: 使用了内核文件级别的回调机制O(1)
下图展示了文件描述符的量级和CPU耗时:

在这里插入图片描述

/proc/sys/fs/epoll/max_user_watches

表示用户能注册到epoll实例中的最大文件描述符的数量限制。

关键函数

epoll_create1: 创建一个epoll实例,文件描述符

epoll_ctl: 将监听的文件描述符添加到epoll实例中,实例代码为将标准输入文件描述符添加到epoll中

epoll_wait: 等待epoll事件从epoll实例中发生, 并返回事件以及对应文件描述符l

epoll 关键的核心数据结构如下:

typedef union epoll_data {
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;

struct epoll_event {
  uint32_t events;  /* Epoll events */
  epoll_data_t data;    /* User data variable */
};
边沿触发vs水平触发

epoll事件有两种模型,边沿触发:edge-triggered (ET), 水平触发:level-triggered (LT)。

水平触发(level-triggered):

socket接收缓冲区不为空 有数据可读 读事件一直触发
socket发送缓冲区不满 可以继续写入数据 写事件一直触发
边沿触发(edge-triggered):

socket的接收缓冲区状态变化时触发读事件,即空的接收缓冲区刚接收到数据时触发读事件
socket的发送缓冲区状态变化时触发写事件,即满的缓冲区刚空出空间时触发读事件
边沿触发仅触发一次,水平触发会一直触发。

事件宏

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

代码

            
       #define MAX_EVENTS 10
       struct epoll_event ev, events[MAX_EVENTS];
           int listen_sock, conn_sock, nfds, epollfd;

           /* Code to set up listening socket, 'listen_sock',
              (socket(), bind(), listen()) omitted */

           // 创建epoll实例
           epollfd = epoll_create1(0);

           if (epollfd == -1) {
               perror("epoll_create1");
               exit(EXIT_FAILURE);
           }

           // 将监听的端口的socket对应的文件描述符添加到epoll事件列表中
           ev.events = EPOLLIN;
           ev.data.fd = listen_sock;
           if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
               perror("epoll_ctl: listen_sock");
               exit(EXIT_FAILURE);
           }

           for (;;) {
               // epoll_wait 阻塞线程,等待事件发生
               nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
               if (nfds == -1) {
                   perror("epoll_wait");
                   exit(EXIT_FAILURE);
               }

               for (n = 0; n < nfds; ++n) {
                   if (events[n].data.fd == listen_sock) {
                       // 新建的连接
                       conn_sock = accept(listen_sock,
                                          (struct sockaddr *) &addr, &addrlen);
                       // accept 返回新建连接的文件描述符
                       if (conn_sock == -1) {
                           perror("accept");
                           exit(EXIT_FAILURE);
                       }
                       setnonblocking(conn_sock);
                       // setnotblocking 将该文件描述符置为非阻塞状态

                       ev.events = EPOLLIN | EPOLLET;
                       ev.data.fd = conn_sock;
                       // 将该文件描述符添加到epoll事件监听的列表中,使用ET模式
                       if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
                                   &ev) == -1)
                           perror("epoll_ctl: conn_sock");
                           exit(EXIT_FAILURE);
                       }
                   } else {
                       // 使用已监听的文件描述符中的数据
                       do_use_fd(events[n].data.fd);
                   }
               }
           }

性能测试

使用了wrk测试工具, 测试了epoll事件驱动的简单的http server。

在这里插入图片描述

二、Epoll高效原理

Epoll在linux内核中源码主要为 eventpoll.c 和 eventpoll.h 主要位于fs/eventpoll.c 和 include/linux/eventpool.h, 具体可以参考linux3.16,下述为部分关键数据结构摘要, 主要介绍epitem 红黑树节点 和eventpoll 关键入口数据结构,维护着链表头节点ready list header和红黑树根节点RB-Tree root。

/*
 * Each file descriptor added to the eventpoll interface will
 * have an entry of this type linked to the "rbr" RB tree.
 * Avoid increasing the size of this struct, there can be many thousands
 * of these on a server and we do not want this to take another cache line.
 */
struct epitem {
    union {
        /* RB tree node links this structure to the eventpoll RB tree */
        struct rb_node rbn;
        /* Used to free the struct epitem */
        struct rcu_head rcu;
    };

    /* List header used to link this structure to the eventpoll ready list */
    struct list_head rdllink;

    /*
     * Works together "struct eventpoll"->ovflist in keeping the
     * single linked chain of items.
     */
    struct epitem *next;

    /* The file descriptor information this item refers to */
    struct epoll_filefd ffd;

    /* Number of active wait queue attached to poll operations */
    int nwait;

    /* List containing poll wait queues */
    struct list_head pwqlist;

    /* The "container" of this item */
    struct eventpoll *ep;

    /* List header used to link this item to the "struct file" items list */
    struct list_head fllink;

    /* wakeup_source used when EPOLLWAKEUP is set */
    struct wakeup_source __rcu *ws;

    /* The structure that describe the interested events and the source fd */
    struct epoll_event event;
};

/*
 * This structure is stored inside the "private_data" member of the file
 * structure and represents the main data structure for the eventpoll
 * interface.
 */
struct eventpoll {
    /* Protect the access to this structure */
    spinlock_t lock;

    /*
     * This mutex is used to ensure that files are not removed
     * while epoll is using them. This is held during the event
     * collection loop, the file cleanup path, the epoll file exit
     * code and the ctl operations.
     */
    struct mutex mtx;

    /* Wait queue used by sys_epoll_wait() */
    wait_queue_head_t wq;

    /* Wait queue used by file->poll() */
    wait_queue_head_t poll_wait;

    /* List of ready file descriptors */
    struct list_head rdllist;

    /* RB tree root used to store monitored fd structs */
    struct rb_root rbr;

    /*
     * This is a single linked list that chains all the "struct epitem" that
     * happened while transferring ready events to userspace w/out
     * holding ->lock.
     */
    struct epitem *ovflist;

    /* wakeup_source used when ep_scan_ready_list is running */
    struct wakeup_source *ws;

    /* The user that created the eventpoll descriptor */
    struct user_struct *user;

    struct file *file;

    /* used to optimize loop detection check */
    int visited;
    struct list_head visited_list_link;
};

epoll使用RB-Tree红黑树去监听并维护所有文件描述符,RB-Tree的根节点。调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个 红黑树 用于存储以后epoll_ctl传来的socket外,还会再建立一个list链表,用于存储准备就绪的事件.当epoll_wait调用时,仅仅观察这个list链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。而且,通常情况下即使我们要监控百万计的句柄,大多一次也只返回很少量的准备就绪句柄而已,所以,epoll_wait仅需要从内核态copy少量的句柄到用户态而已.

那么,这个准备就绪list链表是怎么维护的呢?

当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。

epoll相比于select并不是在所有情况下都要高效,例如在如果有少于1024个文件描述符监听,且大多数socket都是出于活跃繁忙的状态,这种情况下,select要比epoll更为高效,因为epoll会有更多次的系统调用,用户态和内核态会有更加频繁的切换。

epoll高效的本质在于:

  • 减少了用户态和内核态的文件句柄拷贝
  • 减少了对可读可写文件句柄的遍历
  • mmap 加速了内核与用户空间的信息传递,epoll是通过内核与用户mmap同一块内存,避免了无谓的内存拷贝
  • IO性能不会随着监听的文件描述的数量增长而下降
  • 使用红黑树存储fd,以及对应的回调函数,其插入,查找,删除的性能不错,相比于hash,不必预先分配很多的空间
linux IO多路复用机制(select、poll、epoll)及5种IO模型 I/O多路复用:监视多个文件描述符(优势),一旦某个描述符就绪,通知程序 进行相应处理。Select、poll、epoll本质上都是同步IO(Linux)。 Select:1.文件描述符个数有限制1024       --位图        2.每次调用都需要重新将3个fd集合从用户态拷贝到内核态        3.内核需要遍历所有fd,效率低 如果select返回可读,结果 阅读详情

相关推荐

【Linux】【INotify与Epoll机制】

INotify是一个Linux内核所提供的一种文件系统变化通知机制。它可以为应用程序监控文件系统的变化,如文件的创建、删除、读写等。当监听的文件发生相应的操作时,可以通过read函数从INotify中读取事件。INotify主要有两个基本对象。分别是inotify对象和watch对象。第一步:通过inotify_init()创建一个inotify对象。第二步:通过inotify_add_watch将一个或多个监听添加到inotify对象中。

handsomethefirst的博客 1883

linux事件监听机制epoll

说明:linux 下的事件监听机制主要有 poll ,select,epoll ,这里主要介绍epoll 机制,以及可以完美配合使用的eventfd epoll : kernel source/linux/fd/eventpoll.c 系统调用: int epoll_create(int size);       //底层实现调用的是epoll_create1(0) ,size内核实现是没有使用的但是有判断不能小于0 int epoll_create1(int flags);      // f.

lehuihui的博客 4075

linux epoll详解及使用方法概述

一、什么是epoll 于Linux 2.5.44首度登场的epoll是Linux内核的可扩展I/O事件通知机制。它设计目的只在取代既有POSIX select(2)与poll(2)系统函数,让需要大量操作文件描述符的程序得以发挥更优异的性能(举例来说:旧有的系统函数所花费的时间复杂度为O(n),epoll则耗时O(1))。---维基百科 简要概括其主要特性: 1、  和select poll

小麒麟的成长之路 4441

epoll使用详解(精髓)

epoll - I/O event notification facility 在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。 相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明: #define __FD_SETSIZE 1024

XMJYever的博客 1231

Android-深入底层:Linux事件管理机制-epoll

当调用 epoll_wait检查是否有发生事件的连接时,只是检查 eventpoll对象中的 rdllist双向链表是否有 epitem元素而已,如果 rdllist链表不为空,则把这里的事件复制到用户态内存中,同时将事件数量返回给用户。因此,epoll_wait的效率非常高。epoll_ctl在向 epoll对象中添加、修改、删除事件时,从 rbr红黑树中查找事件也非常快,也就是说,epoll是非常高效的,它可以轻易地处理百万级别的并发连接。

2401_86399813的博客 875

linux关于epoll概述(各项组成,以及作用)

在具体的实现中,epoll 利用红黑树来快速查找和管理注册的套接字,以及就绪列表来存储已触发事件的套接字,从而实现高效的事件通知机制。小于 0 表示出错。例如,进程 A 和进程 B 都可以创建自己的 epoll 对象,分别添加各自需要监听的文件描述符,并通过各自的 epoll_wait 等待事件发生,然后进行相应的处理。在 Linux 中,多个进程可以各自创建自己的 epoll 对象,每个进程都有独立的 epoll 实例,即拥有独立的 eventpoll 结构体和相关的数据结构(如红黑树、就绪列表等)。

vbgesab的博客 796

epoll原理_Epoll的简介及原理

epoll 全称 eventpoll,是 linux 内核实现IO多路复用(IO multiplexing)的一个实现。IO多路复用的意思是在一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其的进行读写操作。在 linux 中,和 epoll 类似的有 select 和 poll。网上很多文章着重关注 epoll 与 select 的区别,或者关注 epoll 中...

weixin_39603117的博客 517

嵌入式Linux系统epoll机制

1 什么是epoll 上一章节中,我们创建了一个tcpserver和client,进行数据的收发,而数据收发的过程是阻塞的,如调用到的recv函数,没有收到数据前一直卡住不会往下去执行其它代码。操作tcp也好,读写文件也罢,或者其它文件描述符,本质都是对I/O资源的操作;但对于什么时候来数据可以进行读取,其它方法比如阻塞,线程池并发,select多路复用这些我们一概忽略,基本现在产品开发可以用epoll代替。 简单来说,epoll使用红黑树的数据结构去监听并维护文件描述符。通过事件的方式告诉应用层去处.

HomeByte的博客 634

epoll监听文件_怎么使用epoll机制监听文件变化并读取文件

展开全部epoll的接口62616964757a686964616fe58685e5aeb931333363386165非常简单,一共就三个函数:1. int epoll_create(int size);创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会...

weixin_32306771的博客 1828

深入剖析 epoll 内核实现(Linux Kernel 2.6.11)

epoll 概述 、epoll 内核实现机制综述 、epoll 内核实现的具体分析

ZY-JIMMY 2084

Linux:基于epoll机制的socket通信

epoll定义及配置 epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。 目前epell是linux大规模...

不秃头的程序猿Gabriel的博客 1745

Linux epoll 机制——原理图解与源码实现分析

epoll的实现机制是通过内核与用户空间共享一个事件表,这个事件表中存放着所有需要监控的文件描述符以及它们的状态,当文件描述符的状态发生变化时,内核会将这个事件通知给用户空间,用户空间再根据事件类型进行相应的处理。

一枚菜鸡 3004

飞牛NAS+Docker打造私人IPTV直播源:手把手教你部署iptv-api

本文详细介绍了如何在飞牛NAS上通过Docker部署iptv-api,打造私人IPTV直播源。从SSH配置到Docker容器部署,手把手教你实现自动更新直播源、多终端支持的解决方案,适用于Kodi、PotPlayer等播放器,提升家庭娱乐体验。

earth的博客 5569

Linux内核机制之epoll详解

Linux的epoll机制详解

hinewcc的博客 1377

蓝桥杯算法:打印图形——六芒星

【输入格式】 输入一行包含一个整数 n。 【输出格式】 输出一个六芒星,表示满足题意的图形。 【样例输入】 3 4 【样例输出】 【评测用例规模与约定】 对于所有评测用例,1 ≤ n ≤ 20。 总结: 第一次做图形题,做了蛮久,图形题有很多的方式去完成,它考验的是你能不能找到更简单的那一种方式. 我初看到这个图形,第一种想法是把这个六芒星当做六个小三角形来做,然后以某种规则排列一下,就能做出来,但是尝试了一下感觉太麻烦了。 然后我的第二种想法是,这个图形是对称图形,怎么能写出一半吗? 这样另一半想办

KeepCodeing 3835

如何用AP525 测试输入信号的相位,频响,延时,Pop和卡顿

假设信号不是从AP输出,是功放或者其他的播放设备输出的信号,接入到AP525的输入端。

帮助100万人颠覆负面潜意识,实现人生蜕变,培养1万名潜意识开发导师 2398

epoll机制详解:

大家都明白epoll是一种IO多路复用技术,可以非常高效的处理数以百万计的socket句柄,比起以前的select和poll效率高大发了。在Linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询...

青山绿水 7997

QtQuick QML教程(2):qml语法介绍

​ qml一个普通的文本文件,后缀是.qml, 一个qml文件主要由两部分组成:(1) import语句 :这个就像C++中的头文件, 使用 import 导入qml需要的相关模块,可以查询Qt文档进行导入,注意每个版本的Qt模块声明可能都不同,相比于QWidget项目,QtQuick的变动是很大的;(2) QML对象树 :根对象是Window, 然后可以在Window中添加各种控件,例如按钮,文本,菜单等。

琅嬛福地 551

Linux epoll 机制深度解析

高性能:基于事件驱动的就绪通知机制,避免无效的轮询可扩展性:使用红黑树管理海量连接,时间复杂度稳定灵活性:支持水平触发和边缘触发两种模式内存效率:内核与用户空间共享事件数据,减少内存拷贝。

X 1016

cesium加载geotiff的 四种方法

本文介绍了在Cesium中加载GeoTIFF数据的两种主要方法。首先解析了核心前置知识,包括坐标系匹配、数据类型区分和性能优化。方法1详细演示了如何将RGB影像GeoTIFF转换为Cesium影像图层,包含从数据解析到Canvas转换的完整代码流程。方法2则针对高程数据(DEM)处理,展示了前端直接解析构建地形网格的方案。文章提供了完整的代码示例和关键实现要点,适用于卫星影像、专题图和数字高程模型等多种GeoTIFF数据类型在三维场景中的可视化需求。

大剑师兰特的GIS世界 392

再相逢【知识图谱】中文医学知识图谱CMeKG,中文产科医学知识图谱COKG | 附:图数据库Neo4j下载安装教学(遇到问题并解决) + Neo4j基本操作

本篇文章介绍知识图谱的相关知识。从便于理解的角度切入,给出知识图谱的形象化定义,并介绍了国内的中文医学知识图谱CMeKG,还以用户视角体验了中文产科医学知识图谱。目录二介绍了如何部署安装Neo4j数据库,并解决了部署过程中遇到的一些基础错误,随后介绍了Neo4j图数据库的基础操作。

追光者♂:记录、分享、总结、提升,现象级专栏《Python从入门到人工智能》作者,无惧黑暗,坚信曙光 2615

epoll概述和epoll内核源码分析(epoll最全讲解)

epoll概述 epoll是linux中IO多路复用的一种机制,I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。当然linux中IO多路复用不仅仅是epoll,其他多路复用机制还有select、poll,但是接下来介绍epoll的内核实现。 网上关于epoll接口的介绍非常多,这个不是我关注的重点,但是还...

Tasdily的博客 2079

华为OD机考双机位C卷:精准核酸检测 (C/C++/Py/Java/Js/Go)

华为OD机试双机位C卷 精准核酸检测,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解。华为OD机试 - 精准核酸检测。

qq_45776114的博客 1728

图文并茂的epoll机制全面分析

文章目录epollepoll机制概述相关接口epoll技术细节内核态的主流程相关数据结构总览事件的注册与处理谈一谈file_operations中的poll回调与poll_waitepoll_wait监听与被唤醒的流程多核环境下,多个事件同时到达的情况多核环境下,不及时调用epoll_wait,导致事件积累的情况epoll LT/ET 模式一个关于LT模式的问题epoll 惊群问题关于惊群对于nginx,多个进程分别创建自己的epfd的情形对于多个进程共用同一个epfd的情形为什么IO多路复用机制需要搭配非

x727847140的博客 2589

31.XAPP1052乱序分析及处理

目录 1.XAPP1052乱序分析 2.XAPP1052乱序处理 2.1乱序处理方法选择 2.2 XAPP1052乱序处理代码详解 3.XAPP1052乱序处理后的波形分析 3.1发送引擎 3.2接收引擎 4.XAPP1052乱序处理后的带宽测试 5.下期内容 1.XAPP1052乱序分析 首先先给大家讲解下什么是PCIe的乱序。在PCIe系统中,乱序就是主机发送的TLP序列ID为1、2、3、4到从机,从机接收到的可能不是1、2、3、4。主机和从机如果都不解决乱序的问题,就会导致从机接

一个早起的程序员 1706

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Linux-epoll机制

主要接口 epoll_create epoll_ctl epoll_wait epoll_create 头文件 #include <sys/epoll.h> 函数原型 int epoll_create(int size); int epoll_create1(int flags); 成功返回整型fd,失败返回-1 作用 创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好ep

chenwr2018的博客 3864

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

上一篇: Android Handler 机制(二):Handler 机制深入探究问题梳理
下一篇: Android Handler 机制(三):Handler 机制与管道 Pipe 机制
阿杰、
博客等级 码龄11年 13粉丝 47原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值