面试部分难点梳理 - IO多路复用

IO多路复用

  • 如果用JUC的方式来类比理解一下,BIO是相当于整个APP都加了synchronied,NIO相当于是使用自旋的方式。
    面试部分难点梳理 - IO多路复用_第1张图片

Linux操作系统中断

中断的分类

  • 内中断:陷入(系统调用),故障(缺页中断),终止(程序BUG)
  • 外中断: I/O中断请求(设备准备就绪),人工干预(Ctrl + C);
    面试部分难点梳理 - IO多路复用_第2张图片

系统中断,内核做什么事情

内核态 - 执行系统调用

Socket

Socket基础

面试部分难点梳理 - IO多路复用_第3张图片

面试部分难点梳理 - IO多路复用_第4张图片

Socket读写缓冲区机制

用户态和内核态

  • Socket在底层采用TCP/IP协议,因此在数据传输的过程中是可以保证可靠性的。
  • APP处于用户态,是无法处理TCP/IP协议的,必须要将其所需要传输的数据通过系统调用,传输到操作系统的内核空间中。
  • 当操作系统处于内核态时,可以通过底层的方法,建立Socket连接,来传输数据。

缓冲区

  • APP在用户空间中也有缓冲区,操作系统在内核空间中也有缓冲区。
  • 内核空间的缓冲区是用来接收/发送TCP/IP传输过程中的数据缓冲。
  • 用户空间的缓冲区是用来读取/写入内核缓冲区中的数据。
  • 内核缓冲区中分成了输入缓冲区,和输出缓冲区。

BIO

  • 如果一个APP写入输入缓冲区时,会对输出缓冲区加锁,同一时间仅仅能有该APP执行输入输出操作,如果输出缓冲区不足,也会加锁,该APP等待数据传输完成,再写入。其他APP必须等待该APP完成才能将自己的数据写入缓冲区。
  • app
    面试部分难点梳理 - IO多路复用_第5张图片

NIO

  • 如果缓冲区有剩余,则APP中的数据写入缓冲区,如果没写完,则返回写到的数据位置。
    面试部分难点梳理 - IO多路复用_第6张图片
  • 如果缓冲区没有剩余,或者正在进行TCP/IP的数据传输,则返回-1,表示该次写入失败。
    面试部分难点梳理 - IO多路复用_第7张图片

BIO

  • 将内核的数据缓冲区写到APP的用户数据缓冲区,如果没写完就会阻塞,直到所有需要的数据都写入了缓冲区才结束。
    面试部分难点梳理 - IO多路复用_第8张图片

NIO

  • NIO不会堵塞,采用轮询的方式,持续查询缓冲区中是否有数据,有数据就直接写入用户缓冲区中,没有就直接返回。

内核缓冲区满的情况

BIO

  • TCP会不断尝试去写,上层APP也会被挂起,直到等到缓冲区有位置可以写,APP会写入数据到内核缓冲区。
    面试部分难点梳理 - IO多路复用_第9张图片

NIO

  • NIO会不断尝试去写,如果缓冲区数据满了就会返回-1,表示此次写失败。

系统调用,用户态<—>内核态

为什么要有这两种状态

  • 有些功能是仅仅操作系统才能使用,例如系统调用。
  • 同时用户态有用户堆栈,内核态下有内核堆栈。

什么时候会切换到内核态

  • 当发生中断的时候会切换到内核态
  • 内中断:陷入(系统调用:例如申请外部I/O资源),故障(缺页中断),终止(系统bug)
  • 外中断:外部I/O设备准备就绪,强行干预(Ctrl + C)

状态切换时,要做什么事情

  • 保护用户堆栈的寄存器的现场。
  • 同时执行内核堆栈的程序。
  • 在中断执行之后,会重现现场,继续执行用户堆栈的程序。
    面试部分难点梳理 - IO多路复用_第10张图片

具体流程

  1. 用户态
    1. 查找自己需要调用哪一个系统资源。
    2. 将用户参数变量保存到操作系统的寄存器内。
    3. 保存进程的上下文信息
    4. 切换到进程内核栈
    5. 根据映射表调用System_call函数
  2. 内核态
    1. 取到指定的寄存器的号码。
    2. 从寄存器中取出参数变量。
    3. 将需要的数据从用户态拷贝到内核态
    4. 执行函数
    5. 将结果从内核空间拷贝到寄存器中
  3. 用户态
    1. 回复现场
    2. 读取寄存器中的数值到用户空间中
    3. 继续程序
      面试部分难点梳理 - IO多路复用_第11张图片

BIO通信底层原理

  • 刘老师的原理图
  • 我认为BIO就是一个线程仅仅关注自己的那个端口号,如果自己想要接收信息的客户端没有发送过来信息,就阻塞挂起,直到有信息发过来,被唤醒,获取数据。
  • 而NIO就是一个线程一次性关注多个端口号,通过一定的手段识别那个端口号的数据对应哪个服务器的端口,再将对应端口号的数据发布出去。

select多路复用

  • 刘老师的流程图
  • 在Linux中,我们可以使用select函数实现I/O端口的复用,传递给select函数的参数会告诉内核:
    在这里插入图片描述
    1. 我们所关心的文件描述符((读)readset、(写)writeset、(异常)exceptset )
    2. 对每个描述符,我们所关心的状态。
      (对于内核态的接收到位图来说,0表示没有对应端口需要操作,1表示有对应端口需要操作)。
      (对于用户态的接收位图来说,0表示对应缓冲区没有数据,1表示对应缓冲区有数据)。
    3. 我们要等待多长时间。(timeout)
  • 从select函数返回后,内核告诉我们一下信息:
    1. 对我们的要求已经做好准备的描述符的个数
    2. 对于三种条件哪些描述符已经做好准备.(读,写,异常)
      有了这些返回信息,我们可以调用合适的I/O函数(通常是read或write),并且这些函数不会再阻塞.
      面试部分难点梳理 - IO多路复用_第12张图片
  • maxfdp:bitmap表示的最大有效位,指定从0到n的位图标识位是有效的。
    面试部分难点梳理 - IO多路复用_第13张图片
  • select()问题:
    1. 内核态和用户态之间需要频繁的拷贝fd_set
    2. 重新置位(因为用户态的rset 和 内核态的rset 是不一样的,每次进行select 都会导致rset 不同 因此不能复用)
    3. fd_set位图的数目是有限制的,最大是1024
    4. 都要从索引0开始遍历 有很多位置都是0 会浪费时间。

epoll多路复用

  • 函数比较复杂,就把图贴着这里,这里挑重要的写。
  • 刘老师的流程图
  • epoll维护了两个空间,一个是epoll的监听事件空间,另一个是epoll_wait的等待队列。
  • epoll监听空间是通过红黑树结构来维护的,方便查找对应的epfd。

epoll_create()

在这里插入图片描述

  • 这个函数表示开启size个epoll结构空间,返回epoll文件描述符编号。
  • 直接创建到内核空间的epoll中。

epoll_ctl()对epoll监听列表中的epoll进行修改

  • 在这里插入图片描述
  • 在这里插入图片描述
  • epfd: epoll结构的进程fd编号,函数将依靠该编号找到对应的epol结构。
  • op:表示当前请求类型,增加、减少、修改epoll结构中的结构,由三个宏定义(EPOLL_CTL_ ADD:注册新的fd到epfd中)、(EPOLL_CTL_MOD:修改已经注册的fd的监听事件)、(EPOLL_CTL_DEL:从epfd中删除一个fd)
  • fd,需要监听的文件描述符,需要监听的端口号,一般指socket _fd,
  • event,告诉内核对该fd资源感兴趣的事件(输入,输出等)
  • 在这里插入图片描述

epoll_wait()对epoll就绪队列进行修改

  • 在这里插入图片描述
  • 在这里插入图片描述
  • epfd: epoll结构的进程fd编号,目前线程感兴趣的epoll编号。
  • *events, 是一个指针,必须指向一个epoll_event结构数组, 当函数返回时,内核会把就绪状态的数据拷贝到该数组中!
  • maxevent,标明参数二epoll_event数组最多能接收的数据量,即本次操作最多能获取多少就绪数据。
  • timeout,为0则表示立刻返回不阻塞,为+n则表示阻塞n秒,为-1则表示一直阻塞,直到接收到数据。

epoll相对select的优点

  • epoll的大小可以很大,不被1024限制。
  • 用户直接在内核空间中注册epoll空间
  • epoll_wait中的都是已经操作过的epoll,无用的epoll不会进入epoll_wait中,也不会被返回。select中用0来表示没有被操作,这样节省了遍历的时间。
  • epoll_wait返回的是接收数据的epoll数目;nfds

你可能感兴趣的:(NIO)