IO多路复用总结

211 阅读4分钟

Linux中断

所谓中断是指CPU对系统发生的某个事件做出的一种反应,CPU暂停正在执行的程序,保留现场后自动地转去执行相应的处理程序,处理完该事件后再返回断点继续执行被“打断”的程序

Socket 读/写 缓冲区

BIO 缓冲区读取 和写入 都会阻塞

NIO 缓冲区 写不下 或者没数据可读 会直接返回 不会阻塞

用户态 <--> 内核态

内核态:可以访问所有内存、硬件设备,还能让cpu切换进程

用户态:限制在空间内

用户态转内核态 会先保存上下文。切换内核态 然后在IRQ中断程序入口映射表加载对应中断程序 然后在内核态执行中断程序,执行完了恢复用户态现场 执行用户态 后续逻辑

BIO读取数据流程

  1. 正常流程cpu 执行进程
  2. 进程无数据进入等待队列
  3. 对端socket 发送数据过来,通过网卡发送 到客户端服务器内存。
  4. 网卡发起中断,说明进程数据有了
  5. cpu响应中断,准备执行网卡中断处理程序
  6. 将网卡缓冲区内信息转移到对应socket读缓冲区, 对应socket进入运行队列,cpu有机会执行
  7. 对应socket 读缓冲区已经有数据

特点:每个socket独有进程。非常消耗资源。所以我们改进为一个进程监听多个socket

多路复用技术

通过改变文件二进制位,文件描述符来实现多个监听socket

select

select硬伤就是 死循环内,rset每次调用 都被改变了,在循环内,每次都要置位一次

select 使用实现流程,监听5个socket

  1. 为客户端创建文件描述符,使客户端添加到accept事件队列
  2. 把文件描述符的置位,复制到内核态堆栈
  3. 客户端发送数据,发起网卡硬件中断,保存正在进行的进程的上下文状态信息
  4. 响应中断,读取网卡报文,找到对应端口号对应的进程,让进程A添加到运行队列
  5. 循环关注的socket,有数据的socket就会处理。

select 弊端,每次调用的时候都需要从用户空间copy文件描述符到内核空间,处理对应文件描述符socket数据。处理完后又需要重新置位。

poll

和select类似,只是描述fd集合的方式不同,poll使用pollfd结构而非select的fd_set结构。 管理多个描述符也是进行轮询,根据描述符的状态进行处理,但poll没有最大文件描述符数量的限制

poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

epoll

  1. epoll 主要还是等待事件产生的时候只会返回就绪的数据返回过来,不需要一位一位的去检查rest的值,
  2. (select只能关注最大1024,文件描述符最长1024)epoll可以自己设定关注socket的数量。
  3. 并且epoll 会在内核空间有个位置(epoll),会存储所关注的socket文件描述符。只需要copy一次。

上述流程

  1. 拿到所有socket客户端 fd,把他们添加到epoll空间内 的监听列表。里面是通过红黑树存储各个fd。
  2. 当进程A 被挂起,所有socket 指向的是epoll。 就绪队列无数据
  3. 当有socket 有数据发送过来了,到内存了。触发网卡硬件中断。
  4. cpu响应中断。找到报文对应端口对应socket,等待队列,找到等待队列指向的epoll,把就绪的队列返回出来。
  5. 进程就继续执行了,把内核态里面的就绪队列返回了,进行后续的循环读取。

阻塞、非阻塞、异步、同步io解释

首先 阻塞与非阻塞 是在 发送端io 是否要等待响应才继续后续操作。

同步异步 是对结果进行怎么拿到,比如异步io 就是通过回调或者主动去查询 拿到响应数据。还有一点就是 同步和异步io 如果进程参与了内核态数据拷贝到用户到 就属于同步io 如果没参与就属于异步io。因为是通过回调或者直接查询到用户态数据被异步存取了。

nio就是我们所说的 同步非阻塞io

首先nio 中的epoll 其实还是有进程主动去把内核态数据copy到用户态,属于同步,非阻塞是主进程在等待数据这段时间还可以进行其他操作,并没有影响主进程其他操作。

在这里 nio 的copy 拷贝也是一个耗费性能的过程,后续netty也有围绕这块的优化。