一文明白IO模型和常问多路复用机制

1. IO模型

Socket的输入操作有两步。

  1. wait for data - 等待网络传输数据到达,到达后复制到内核缓冲区;
  2. copy data from kernel to user - 把数据从内核缓冲区复制到应用进程缓冲区。

涉及到两个对象:process调用这个IO的进程/线程,kernel系统内核。

1.1 同步阻塞IO

用户线程发出请求后就一直被阻塞,直到数据到达并从内核缓冲区复制到进程缓冲区才返回。
在这里插入图片描述

1.2 同步非阻塞IO

用户线程发出请求后,内核立即返回错误码,但用户线程需要不断发出IO请求询问内核数据到达没,到达了才进行第二阶段。这个过程叫做轮询。

在这里插入图片描述

1.3 IO多路复用/异步阻塞IO

又称为事件驱动IO,单个进程有处理多个IO的能力,避免一个socket一个线程的开销和切换。用户注册多个socket,reactor一对多监听,不断调用select读取激活的socket,再一对多分发给对应处理器处理。

在这里插入图片描述
使用了 Reactor 反应堆模型。

  • 将用户线程轮询IO操作状态的工作交给事件处理器,用户线程可以继续执行做其他的工作,Reactor线程负责调用内核的select函数检查socket状态。
  • 当有socket被激活时,则通知相应的用户线程,执行handle_event进行数据读取、处理的工作。
  • 由于select函数是阻塞的,因此多路IO复用模型也被称为异步阻塞IO模型。

1.4 信号驱动IO/异步非阻塞IO

sigaction系统调用,内核立即返回,应用进程可以去干其他事,当数据到达时,内核发送SIGIO信号给应用进程通知应用进程可以进行IO的数据复制。

相比于轮询方式,CPU利用率更高。
在这里插入图片描述

1.5 异步IO

aio_read系统调用立即返回,应用进程可以去干其他事,内核在完成所有操作后发送信号,通知应用进程IO已经完成。
在这里插入图片描述

1.6 比较

  • 同步IO:将数据从内核缓冲区复制到应用进程缓冲区时,应用进程会阻塞。
  • 异步IO:第二阶段不会阻塞。
  • 阻塞IO:第一阶段就阻塞线程,直到获得数据。
  • 非阻塞IO:线程发出请求立即返回,但需要轮询或者再次进行系统调用进行第二阶段。

2. wakeup callback机制

Linux 内核的事件唤醒回调机制是IO多路复用的本质。

概括来说,Linux通过睡眠队列管理所有等待 Socket 事件的线程,通过 wakeup 机制异步唤醒整个睡眠队列上等待事件的线程,通知线程事件发生

  1. 睡眠等待
    • select、poll、epoll 陷入内核,判断监控的 socket 是否有关心的事件发生,如果没,则为当前 process 构建一个 wait_entry 节点,插入到监控 socket 的 sleep_list;
    • 进入循环的 schedule 直到关心的事件发生;
    • 事件发生后,将当前线程的 wait_entry 节点从 socket 的 sleep_list 中删除。
  2. 异步唤醒
    • socket 顺序遍历其睡眠队列,依次调用每个节点的 callback 函数;
    • 直到完成遍历或者遇到某个排他节点。

3. IO复用机制

fd 文件描述符,用于表述指向文件的引用的抽象化概念。

3.1 Select

底层通过一个long类型的数组 fd_set,存放文件句柄。

每次调用select时,把 fd_set 集合从用户态复制到内核态,在内核轮询遍历集合,且对集合有 1024 的大小限制。

内部的轮询,是通过为每个 socket 添加 poll 逻辑,用来收集该 socket 发生的事件。轮询就是遍历 socket 调用 poll 逻辑,直到有事件发生。

所以存在三个问题:

  1. fd 集合限制为 1024,底层数组;
  2. fd 集合每次都要从用户态拷贝到内核态;
  3. 每次都在遍历集合收集可读数据。

3.2 poll

本质上和select一样,解决了 fd 集合大小限制问题。底层通过链表形式 pollfd 实现,所以没有最大连接数的限制。

其他两个缺点并没有改进,不适用大并发场景。

3.3 epoll

只适用于Linux。

  1. 针对集合拷贝。
    • 使用事件回调通知,通过 epoll_ctl() 注册fd进行增删改,调用 epoll_wait() 等待事件产生。
    • 内核 2.6.8 之前底层使用哈希表存储,之后使用红黑树。
    • epoll_wait() 通过将内核空间和用户空间(都是虚拟地址)映射到同一块物理内存地址,用来减少用户态和内核态间的数据交换。
  2. 针对集合遍历。
    • 引入中间层,为每个 socket 提供单独的回调函数,当其就绪时将自身加入准备队列 ready_list 中;
    • 等待线程的回调函数遍历 ready_list 上所有的 socket,调用 poll 逻辑收集事件,唤醒线程。

3.3.1 工作模式

  • Level Triggered 水平触发。默认模式,只要fd还有事件,每次 epoll_wait() 都会再次通知进程。
  • Edge Triggered 边沿触发。通知之后进程必须立即处理事件,下次 epoll_wait() 不会收到该fd的通知。

3.4 比较

方式selectpollepoll
操作方式遍历遍历回调
底层实现数组链表哈希表/红黑树
最大连接数1024 or 2048无上限无上限
IO效率轮询O(n)轮询O(n)事件回调,将就绪的fd放进就绪队列,每次只用判断队列是否为空O(1)
fd拷贝每次调用都把fd集合从用户态拷贝到内核态每次调用都把fd集合从用户态拷贝到内核态调用epoll_ctl()时fd拷贝进内核并保存

3.5 适用场景

  1. select的时间精度是微秒,适用实时性要求高的场景。
  2. poll没有最大描述符限制,若实时性要求不高且平台支持,用poll。
  3. epoll适用Linux平台,且有大量描述符需要同时轮询。

推荐阅读 大话select,poll,epoll

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值