天天看点

【IO事件】 【io多路复用】epoll的事件驱动;从多线程模型到单线程模型,再到IO多路复用模型的过程;

文章目录

        • 概念
        • 【并发】IO多路复用select/poll/epoll
            • 使用多线程、进程来实现;// BIO 时代
            • 使用单线程来实现;// NIO 时代**
            • select // 同步IO 时代
            • poll
            • epoll(Linux特有)// 同步 非阻塞IO
        • 同步与异步
    • 参考

概念

  • GDT

    :(全局描述符表/Global Descriptor Table)内核启动的时候,划分了用户、内核空间;
  • BIO

    : (blocking I/O、阻塞IO)
  • NIO

    :(non-blocking I/O、非阻塞IO) : 同步非阻塞就时,我发起一个IO操作,我去做其他的,隔一段时间再去问IO操作要结果
  • AIO

    : (Asynchronous I/O、 异步IO) : 异步非阻塞就是,我发起一个IO操作,然后我去做其他的,IO操作完之后主动告诉我结果
  • OS不是运行着的代码,而是一堆躺在内存里等着被调用的代码。内核就是一个由interrupt驱动的程序。这个interrupt可以是一个系统调用(x86下,很多OS的系统调用是靠software interrupt实现的),可以是一个用户程序产生的异常,也可以是一个硬件产生的事件中断。
  • OS kernel 的大部分代码确实是「躺着」这是从 kernel 代码的静态覆盖来说。但是 CPU 不会处于一个僵死的状态。CPU 是永远处于运行代码的状态的,从这个方面来说,CPU 99% 的时间都在运行同样的几十行 kernel 代码。

【并发】IO多路复用select/poll/epoll

  1. 多线程处理:上下文切换在大量客户端的时候,成为瓶颈;(BIO时期)
  2. 单线程来实现,用户态轮询效率低下;(NIO时期)
  3. 多路复用select,内核轮询,效率有所上升;//198几年就有提出一直到现在还有系统在用;内核需要O(n)的主动遍历,性能有些浪费;
  4. epoll(event poll,靠事件驱动)
  • 前面的都是同步IO;只是IO多路复用只是在状态上用了多路复用,但是IO上的数据读写还是要程序自己实现,所以还是同步的IO(只要是自己完成读写,都是同步IO)(linux 下的异步IO 还没完全统一);

使用多线程、进程来实现;// BIO 时代

  • 都是靠的阻塞IO 来实现;// BIO 时代
流程:
  • socket accepte 线程用于死循环的 accepted ;接受到新连接后,开新线程去接收并处理;
缺陷:
  • 多线程消耗资源(线程栈独享);浪费内存;
  • 线程切换浪费;
  • 根本问题是: 阻塞
    • 于是引出使用NIO 的单线程模型

使用单线程来实现;// NIO 时代**

流程:
  • 使用NIO;(accept 有 none blocking 选项)
  • 不断轮询遍历fd;判断是否存在数据需要处理;(需要不停的recvfrom)
缺陷:
  • 这样低效;每次查询fd 都需要陷入内核查询,再返回;无法应对C10K 问题;因为每轮循环需要O(n)的轮询所有fd;
    • 于是引出内核增加的系统调用 select;可以每轮O(m)的循环ready了的fd;

select // 同步IO 时代

概述:
  • select 允许一个程序监控很多个fd; 等待直到一个或者多个fd 变为 “可读”
  • 可以每轮O(m)的循环ready了的fd;相较单纯NIO 来说,降低了时间复杂度;
  • select 多路复用实现了返回状态;但是还需要程序自己实现读写;所以是同步IO
  • (只有windows 的IOCP模型支持异步IO)
  • 相较于NIO: 多线程NIO 是10W次系统调用;select 是内核中和用户态中10W次遍历;速度差异很大的;
特点:
  • linux 提供了

    select

    实现;传入最大描述符+1、bitmap(读写各一个)等
  • bitsmap 位图有1024位;最大能表示1024个fd;每位为1 表示对应位是否被监听;
  • select

    将对应bitmap拷贝到内核中,由内核判断每个fd 是否有数据需要处理;(内核来轮询比 用户态来轮询更快,因为用户态轮询,每次还是需要陷入内核判断是否有数据,存在用户态和内核态的频繁切换)
  • select

    是阻塞函数;(所以是同步阻塞IO)
  • 当有数据需要处理的时候,内核会将对应bitmap 中的fd 位置1,并返回select函数;
流程:
    1. 将bitmap、最大fd 交给

      select

    2. select

      让内核将对应bitmap 拷贝到内核空间中;
    3. 内核轮询bitmap 的时候会阻塞

      select

      函数;
    4. 当有数据要处理的时候,内核将bitmap 对应fd 处置1;然后拷贝回用户态空间;
    5. 用户空间遍历bitmap,将有数据的fd中buffer读出来;
  • 优势在于,交给内核来轮询fd,效率更高些;
缺陷在于:
    1. bitmap 默认1024,上限限制了fd数量;
    2. fd set(bitmap/集合)不可重用,每次重新搞一个给select;
    3. 虽然比用户态交互更快,但是还是存在将整个bitmap拷贝内核态的过程;
    4. select 返回后,还是需要再去遍历bitmap,而不是直接告诉我哪个可以处理了;
【IO事件】 【io多路复用】epoll的事件驱动;从多线程模型到单线程模型,再到IO多路复用模型的过程;

poll

  • linux 提供了

    poll

    实现;传入

    pollfd

    数据结构数组、数量、超时时间;
  • 实现同样存在向内核态拷贝的过程;
  • 相较与fd ,不使用bitmap,而使用了pollfd 数据结构来实现;
  • pollfd 中有fd、events、revents;
流程:
    1. pollfd

      数据结构数组、数量、超时时间;交给

      poll

    2. poll

      让内核将对应

      pollfd

      数组拷贝给内核;
    3. 当有数据要处理的时候,内核将

      pollfd

      对应

      revents

      处置1;然后拷贝回用户态空间;
    4. 用户空间遍历

      pollfd

      数组,对应

      revents

      处为 1,就对其清零,并对对应fd read;
优势在于
    1. pollfd

      数组可以重用,每次

      revents

      用完后会被清零;
    2. pollfd

      数组大小可以任意,不再受限于bitmap 大小的限制;
缺陷在于:
    1. 虽然比用户态交互更快,但是还是存在将整个

      pollfd

      数组 拷贝内核态的过程;
    2. poll

      返回后,还是需要再去遍历

      pollfd

      数组,而不是直接告诉我哪个可以处理了;
【IO事件】 【io多路复用】epoll的事件驱动;从多线程模型到单线程模型,再到IO多路复用模型的过程;

epoll(Linux特有)// 同步 非阻塞IO

概述:
  • linux 提供了 epoll 的实现

    epoll_wait()

    ;传入

    epfd

    、events、数量、超时时间;
  • 实现同样存在向内核态拷贝的过程;
  • epoll_wait()

    如果检测到事件,返回的是就绪fd 文件描述符 的个数;
特点:
  • 重点在于中断事件驱动:在内核中响应fd 的中断,将对应fd放在链表中;
  • (尽量的利用了CPU,不需要CPU 主动遍历整个fd了;而是让中断处理,将对应fd加入链表)
流程:
    1. epoll_create()

      在内核开辟空间,返回一个fd; 开辟空间包括红黑树+链表;
    2. epoll_add()

      在每次出发连接的时候,将新的 fd 放进红黑树中;
    3. 网卡数据到达,内核中断后,遍历红黑树,将数据拷贝到对应fd 的buffer中去;
    4. 并将对应 fd 添加到链表中,以供用户态访问有数据的fd;
    5. epoll_wait()

      返回链表中有数据的fd 个数,用户程序根据fd 链表read 里面的buffer数据;
  • 存在水平、垂直触发;只在有事件时

    epoll_wait()

    出发的方式上、用户处理中有区别;
  • nginx

    epoll_wait()

    是设置阻塞住的; 采用的是一个线程就负责这个IO;
  • redis

    epoll_wait()

    是设置非阻塞住的; 因为是单线程;(不仅做IO ,还做别的)(因为是单线程,所以是原子的,串行化的)不用加锁?
优势
  • 如果该服务器拥有两个CPU,则这两个CPU实现了异步处理就绪文件描述符。极大的提高了应用程序索引就绪文件描述符的效率!
红黑树存在的意义
  • 每次网卡到达数据的时候内核需要把数据拷贝到对应socket的buf中,按照socket的地址端口号搞一个红黑树排序,可以在 O(logN)的时间复杂度内找到对应的socket, 然后把数据拷贝到buf中
  • 我觉得是,网卡交上来的数据,带着对应的四元组;内核需要找到我红黑树中的fd;将数据拷贝到对应fd 的buffer中去;

同步与异步

  • 多路复用仍然需要应用程序自己读取数据,内核只负责告诉程序哪个文件描述符可读可写而已;
  • 只要程序自己读写,那么这个IO模型就是同步IO模型
  • Windows 的异步IO : Windows的应用程序读取磁盘数据的时候,访问内存发现没有对应磁盘文件的数据,则当前进程挂起,由内核负责把这个数据拷贝到用户进程中,然后唤醒挂起的进程,进程没有自己读取数据却发现数据已经在自己的进程空间里了。(IOCP模型)

参考

清华大牛权威讲解nio,epoll,多路复用

【并发】IO多路复用select/poll/epoll介绍(这个epoll讲的有问题)

select、poll、epoll - IO模型超详解

继续阅读