Linux epoll内核实现详解 (linux epoll内核实现)

在Linux系统中,I/O操作是一个非常重要的部分,这也是让系统支持更高级的网络应用程序的重要因素之一。原始的Linux操作系统中,主要是使用select和poll等方法来实现I/O多路复用的。但是,在高负载情况下,这些方法会出现效率较低的瓶颈。因此,Linux内核开发人员就在2.6版本中引入了epoll机制,来解决这个问题。

epoll机制是Linux内核提供的一种高效的I/O多路复用机制。相较于select和poll等方法,在高负载情况下,epoll可以更有效地处理多个客户端请求,提高系统响应速度和处理能力。那么,epoll是如何实现这样高效的多路复用呢?

一、epoll基本原理

epoll机制主要借助了Linux内核中的“事件通知机制”。这个机制是通过一个双向链表和一个红黑树来实现的。每当一个文件描述符上发生一个事件时,它就会被添加到双向链表中的某个位置上。而红黑树的作用,则是快速地查找当前发生事件的文件描述符。

在使用epoll时,应用程序会首先创建一个epoll实例,并用epoll_create函数获取一个描述符。接着,在epoll实例中注册一些感兴趣的文件描述符(读、写、异常等),这些事件被注册之后,内核就会将这些文件描述符添加到双向链表和红黑树中。如果这些文件描述符上出现了感兴趣的事件,那么内核就会将它们添加到一个就绪事件链表中,并通过epoll_wt或类似的函数通知应用程序。

二、epoll与select、poll的比较

1. 其他方法的缺陷

在传统的select和poll等方法中,应用程序需要将所有需要监听的文件描述符都加到轮询列表中。而在轮询列表中,每个文件描述符都要被遍历一遍,来检查它是否产生了需要监听的事件。这就会产生很多不必要的操作,浪费了系统资源和CPU周期。

另外,select和poll等方法对于可读、可写和出现异常等事件的处理方式,也是比较粗略的。在这些方法中,每次事件被触发时,整个列表中的所有文件描述符都必须被重新扫描一遍。这样,就会产生很多无谓的内存和CPU消耗。

2. epoll的优点

与select和poll等传统的I/O多路复用方法相比,epoll有以下几点优势:

(1)支持更多文件描述符

select和poll等方法都有一个缺陷,就是最多只能支持1024个文件描述符。如果要监听更多的文件描述符,就必须使用多个进程进行处理。而epoll则没有这个限制,它可以同时监视一百万个文件描述符。

(2)IO效率更高

在select和poll等方法中,每个文件描述符的监听状态都会被复制到内核中去。而在epoll中,则只需要复制一次,因此I/O效率更高。

另外,在epoll中,内核直接将监听状态的文件描述符添加到了事件数组中,而不是轮询列表中。当有事件触发时,只有真正产生事件的文件描述符会被回传,减少了不必要的遍历。

(3)更灵活的监听模式

在select和poll中,每个文件描述符只能同时被监听一种事件类型(可读、可写或异常)。而在epoll中,则可以同时监听不同类型的事件。

此外,在epoll中还可以使用ET模式进行事件监听。ET模式是指边缘触发模式,当事件产生时,内核只会通知一次。而在LT模式中,则会在文件描述符上保持一个状态,直到它的事件被处理完毕。因此,ET模式的效率更高。

三、epoll实现原理分析

epoll的实现是比较复杂的,主要要穿插文件描述符的状态处理、内存管理、多线程等多个方面。但总体而言,它的实现原理可以分为以下几个方面:

1. 状态管理

在epoll机制中,每个文件描述符都会有一个内部数据结构。这个数据结构中包含了文件描述符的当前状态、监听事件和回调函数等信息。所有的数据结构和管理都是由内核完成的。

每当一个文件描述符上的事件被触发时,相应的数据结构就会被更新。如果文件描述符上的事件被取消了,则相应的事件处理器也会被删除。这样,内核就可以根据当前的事件状态,来更智能地处理I/O调度。

2. 内存管理

在epoll中,所有内存的处理都是交给内核的。当应用程序使用epoll_create时,内核会为epoll实例分配内存,来存储epoll所需要的所有信息。

在内存分配过程中,尽量避免发生内存泄漏或内存损坏等问题,这样可以更好地保证应用程序的稳定性。

3. 多线程处理

为了提高epoll的效率,内核会使用多线程来加速事件处理。当有事件产生时,内核会先将它加入到一个就绪列表中,并通知正在等待事件的线程。当线程获取事件后,就可以快速地对事件进行处理。

同时,在epoll中,需要尽可能地避免线程间的互相干扰。当有多个线程同时访问文件描述符上的事件时,需要使用互斥量、读写锁等机制,来避免数据竞争和死锁等问题。

四、

epoll机制是Linux内核提供的一种高效的I/O多路复用机制。与select和poll等传统方法相比,epoll具有更高的处理能力和更灵活的监听模式。在使用epoll时,应用程序只需要将需要监听的文件描述符注册到epoll实例中,在事件触发时,内核就可以根据当前的事件状态,更智能地进行I/O调度。

epoll机制为高负载的网络应用程序提供了更好的支持。然而,在使用epoll时,也需要注意一些问题,例如内存管理、多线程处理和进程调度等问题。只有通过对这些问题的深入了解和处理,才能更好地发挥epoll的优势,提高应用程序的性能和稳定性。

相关问题拓展阅读:

Node.js的心脏-epoll

我们都知道Node.js是异步的,那么Node.js为什么会是异步的呢?这是因为Node.js使用了LIBUV做为它的跨平台抽象层。具体请看 nodejs运行机制

    select、poll、epoll是Linux平台下的IO多路复用机制,用来管理大量的文件描述符。但是select/poll相对于epoll来说效率是低下的。

、linux内核在select的每次返回前都要对所有的描述符 循环遍历 ,将有事件发生的文件描述符放在一个里返回。在描述符不多的时候对性能影响不大,但是当描述符达到数十万甚至更多的时候,这种处理方式造成大量的浪费和资源开销,select的效率会急剧下降。这是因为每次select的时候,会将所有的文件描述符从用户态拷贝的内核态,在内核态进行循环,查看是否有事件发生。2、select默认的管理的更大文件描述符是1024个,当然可以对linux内核从新编译来改变这个限制。

    原理和select相似也是使用循环遍历的方式管理文件描述符,不同的是管理的文件更大文件描述符的数量没有限制(根据系统限制来定)。

下文讲解epoll实现原理

    epoll改进了select的两个缺点,从而能够在管理大量的描述符的情况下,对系统资源的使用并没有急剧的增加,而只是对内存的使用有所增加(毕竟存储大量的描述符的数据结构会占用大量内存)。epoll在实现上的三个核心点是:1、mmap,2、红黑树,3、rdlist(就绪描述符链表)接下来一一解释这三个并且解释为什么会高效。

    mmap是共享内存,用户进程和内核有一段地址(虚拟存储器地址)映射到了同一块物理地址上,这样当内核要对描述符上的事件进行检查的时候就不用来回的拷贝了。   

    红黑树是用来存储这些描述符的。当内核初始化epoll的时候(当调用epoll_create的时候内核也是个epoll描述符创建了一个文件,毕竟在Linux中一切都是文件,而epoll面对的是一个特殊的文件,和普通文件不同),会开辟出一块内核缓冲区,这块区域用来存储我们要监管的所有的socket描述符,当然在这里面存储有一个数据结构,这就是红黑树,由于红黑树的接近平衡的查找,插入,删除能力,在这里显著的提高了对描述符的管理。

rdlist就绪描述符链表这是一个双链表,epoll_wait()函数返回的也是这个就绪链表。当内核创建了红黑树之后,同时也会建立一个双向链表rdlist,用于存储准备就绪的描述符,当调用epoll_wait的时候在timeout时间内,只是简单的去管理这个rdlist中是否有数据,如果没有则睡眠至超时,如果有数据则立即返回并将链表中的数据赋值到events数组中。这样就能够高效的管理就绪的描述符,而不用去轮询所有的描述符。

当执行epoll_ctl时除了把socket描述符放入到红黑树中之外,还会给内核中断处理程序注册一个回调函数,告诉内核,当这个描述符上有事件到达(或者说中断了)的时候就调用这个回调函数。这个回调函数的作用就是将描述符放入到rdlist中,所以当一个socket上的数据到达的时候内核就会把网卡上的数据复制到内核,然后把socket描述符插入就绪链表rdlist中。

Epoll的两种模式:

. 水平触发(LT):使用此种模式,当数据可读的时候,epoll_wait()将会一直返回就绪事件。如果你没有处理完全部数据,并且再次在该epoll实例上调用epoll_wait()才监听描述符的时候,它将会再次返回就绪事件,因为有数据可读。

. 边缘触发(ET):使用此种模式,只能获取一次就绪通知,如果没有处理完全部数据,并且再次调用epoll_wait()的时候,它将会阻塞,因为就绪事件已经释放出来了。

ET的效能更高,但是对程序员的要求也更高。在ET模式下,我们必须一次干净而彻底地处理完所有事件。

epoll的linux实现

linux手册翻译——epoll(7)

epoll — I/O 事件通知机制

epoll API与poll具有相同功能:监视多个文件描述符,以查看这些文件描述符中任何一个上可以进行特定的I/O操作,如是否可读/可写。epoll API可以使用edge-triggered和level-triggered两种接口,并且可以高性能的同时监视大量的fd,这是对epoll相对鱼poll的核心优势。

epoll的核心概念是epoll instance,这是一种内核数据结构,从用户空间角度看,可以视为一个包含两种列表的容器:

提供以下3个系统调用来创建和管理epoll instance:

两种触发模式:level_triggered (LT)和 edge_triggered(ET)

假设发生如下场景:

如果使用ET触发,那么步骤5就会阻塞挂起,这是因为对于ET模式而言,只有当缓冲区数据发生变化时才会触发事件(对于读,“变化”指新数据到达)。而对于LT而言,只要缓冲区中存在数据,就会一直触发。

使用ET时应使用非阻塞的fd (即无法读写时返回EAGIN,而非阻塞),以避免task阻塞导教其他fd无法监控。

合理使用ET模式步骤:

1)修改fd为非阻塞(non-blocking)

2)在read或write操作返回EAGIN后再执行wait等待事件。

为何ET需要非阻塞呢?因为ET模式下要循环多次read,并通过阻塞(即是否返回EAGIN)来确定数据是否全部读完。之一次执行read是不可能阻塞的。

若使用LT模式(默认情况下,使用ET模式),则可以将epoll看作是一个快速的poll,可以在任何地方使用epoll(LT)替换poll,因为他们的语义完全相同。

即使采用ET模式,在多线程的情况依然会导致产生多个事件(对于同一被监控的fd),这将导致多个线程操作同一fd,可以使用EPOLLNESHOT标志避免,即在一次wait返回后禁止fd再产生事件,并在处理完成后使用epoll_ctl的MOD操作重新开启。

在多进程或多线程中,epoll_fd是共享的,这将导致所有线程都会知道事情的发生,但是epoll仅会唤醒一个线程,以规避“群惊”现象。

If the system is in autosleep mode via /sys/power/autosleep and an event happens which wakes the device from sleep, the device driver will keep the device awake only until that event is queued. To keep the device awake until the event has been processed, it is necessary to use the epoll_ctl(2) EPOLLWAKEUP flag.

When the EPOLLWAKEUP flag is set in the events field for a struct epoll_event, the system will be kept awake from the moment the event is queued, through the epoll_wait(2) call which returns the event until the subsequent epoll_wait(2) call. If the event should keep the system awake beyond that time, then a separate wake_lock should be taken before the second epoll_wait(2) call.

以下接口可用于限制 epoll 消耗的内核内存用量:

虽然 epoll 在用作级别触发接口时具有与 poll(2) 相同的语义,但边缘触发的用法需要更多说明以避免应用程序事件循环中的阻塞。

在下面例子中,listener 是一个非阻塞套接字,在它上面调用了 listen(2)。 函数 do_use_fd() 使用新的就绪文件描述符,直到 read(2) 或 write(2) 返回 EAGAIN。 事件驱动的状态机应用程序应该在收到 EAGAIN 后记录其当前状态,以便在下一次调用 do_use_fd() 时,它将继续从之前停止的位置read (2) 或write (2)。

当使用ET模式时,出于性能原因,可以通过EPOLL_CTL_ADD调用 epoll_ctl(2)指定 (EPOLLIN|EPOLLOUT)添加一次文件描述符。 避免使用 EPOLL_CTL_MOD 调用 epoll_ctl(2)在 EPOLLIN 和 EPOLLOUT 之间连续切换。

The epoll API is Linux-specific. Some other systems provide similar mechanis, for example, FreeBSD has kqueue, and Solaris has /dev/poll.

通过 epoll 文件描述符监视的文件描述符集可以通过进程的 /proc//fdinfo 目录中的 epoll 文件描述符条目查看。 有关更多详细信息,请参阅 proc(5)。

关于linux epoll内核实现的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。


数据运维技术 » Linux epoll内核实现详解 (linux epoll内核实现)