Node.js的心脏-epoll

1、Node.js和epoll

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

2、select/poll/epoll

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

select

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

poll

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

epoll

下文讲解epoll实现原理

3、epoll实现原理

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

mmap

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

红黑树

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

rdlist

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

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

Epoll的两种模式:

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

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

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

4、epoll实现

epoll的linux实现

你可能感兴趣的:(Node.js的心脏-epoll)