IO复习总结


别问 问就是为了面试豁出了老命

Linux的五种IO模型

  1. 阻塞IO

    进程或线程等待某个条件,如果不满足则一直等待,进入阻塞

  2. 非阻塞IO

    如果不满足条件则直接返回,然后轮询全部的请求,如果满足条件则读取

  3. 信号驱动IO模型

    由于轮询的效率还是不够高,那么则采用信号机制,也就是说采用报警器,如果满足条件则内核让进程去读取

  4. IO复用模型

    添加select函数去监听所有的请求,如果哪个成功则直接进行IO读取

  5. 异步IO模型

    IO读取由内核自己完成,如果读取完则直接告诉进程读取完成

Java中的IO

  • BIO
  • NIO
    • 原理图
      nio基础模型.jpg
      • channel
        数据通过channel到达buffer,之后从buffer再读取到线程
      • buffer
        作为nio的重要实现,数据不需要像javaIO阻塞读取,而是可以一直被读取,而不影响线程的其它操作
      • 总结分析
        NIO是同步非阻塞IO,由于buffer的实现,那么假如有数据需要读取到线程中,那么可以先读取到channel,再读取到buffer,但是如果数据没有完全读取完,线程在buffer还在不停的被读入数据的同时做其它的操作
    • selector
      nio_selector.jpg
      进一步的非阻塞,线程不会只是一个channel写入数据,单个线程可以利用selector控制多个channel写入数据
  • AIO

    NIO的进化版本,同步的io需要线程本身的资源去读取,而非阻塞异步io,数据的读取过程由内核自己完成,当成功读取到线程中后,会有操作系统来提示线程数据读取完成。

IO多路复用

select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

  • select
    • 维护了三个状态数组,分别是
      • fd_set* writefds
      • fd_set* readfds
      • fd_set* exceptfds
    • 通过遍历这三个数组则可以获取到状态
    • 问题: 单个进程可以监控的文件描述符有限,linux上是1024个
  • poll

    • select的进化,三个数组变成一个pollfd链表

      1
      2
      3
      4
      5
      struct pollfd {
      int fd; /* file descriptor */
      short events; /* requested events to watch */
      short revents; /* returned events witnessed */
      };
    • 问题: 虽然说取消了1024的限制,但是如果变长,效率还是很差

  • epoll

    • 将改进转移到内核中,将用户关系的文件描述符的事件存放到内核的一个事件表
    • epoll的操作过程

      • int epoll_create(int size);、
        初始化内核中事件表的大小,size参数则是指定的大小,但是并不做限制,只是建议
      • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

        • epfd 是 epoll_create()的返回值
        • op 是 add,mod,del (增删改查的操作)
        • fd 监听的文件描述符
        • epoll_event 表示监听的具体什么事件的数组
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          struct epoll_event {
          __uint32_t events; /* Epoll events */
          epoll_data_t data; /* User data variable */
          };

          //events可以是以下几个宏的集合:
          EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
          EPOLLOUT:表示对应的文件描述符可以写;
          EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
          EPOLLERR:表示对应的文件描述符发生错误;
          EPOLLHUP:表示对应的文件描述符被挂断;
          EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
          EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
      • int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
        epoll_create()返回的内核处理的事件结果

        • epfd 是 epoll_wait的结果
        • events 是内核上处理的事件的集合
        • maxevent就是告诉内核这些事件有多大,小于创建时的size
        • 事件的超时事件
    • epoll的工作方式

      • LT : 可以暂时不处理,等下次再来处理
      • ET :需要立刻处理
    • epoll的全流程总结

      epoll首先需要利用epoll_create()去创建一个句柄,方便直接联系到内核
      之后利用epoll_ctl()去为epoll_create()在内核中空间创建的时间表中,添加文件描述符,或为文件描述符添加,删除或者是修改事件
      注意一个文件描述符可以添加很多的描述的事件
      之后,由于内核中存在事件表,如果事件表发生某种变化,可以利用一种callback的方式回调到epoll中
      这个时候如果调用epoll_wait则可以获取这些状态 (减少了轮询,而是直接通知到epoll)