IO多路复用-Select、Poll、Epoll

IO - 同步、异步、阻塞、非阻塞

5种IO模型(前面4中都属于同步):

  • blocking IO - 阻塞IO
  • nonblocking IO - 非阻塞IO
  • IO multiplexing - IO多路复用
  • signal driven IO - 信号驱动IO
  • asynchronous IO - 异步IO

一次网络IO读操作会涉及两个系统对象:
(1) 用户进程(线程)Process;(2)内核对象kernel,两个处理阶段,IO模型的异同点就是区分在这两个系统对象、两个处理阶段的不同上。

1 同步IO 之 Blocking IO

在这里插入图片描述

如上图所示,用户进程process在Blocking IO读recvfrom操作的两个阶段都是等待的。在数据没准备好的时候,process原地等待kernel准备数据。kernel准备好数据后,process继续等待kernel将数据copy到自己的buffer。在kernel完成数据的copy后process才会从recvfrom系统调用中返回。

2 同步IO 之 NonBlocking IO

在这里插入图片描述

从图中可以看出,process在NonBlocking IO读recvfrom操作的第一个阶段是不会block等待的,如果kernel数据还没准备好,那么recvfrom会立刻返回一个EWOULDBLOCK错误。当kernel准备好数据后,进入处理的第二阶段的时候,process会等待kernel将数据copy到自己的buffer,在kernel完成数据的copy后process才会从recvfrom系统调用中返回。

3 同步IO 之 IO multiplexing

在这里插入图片描述

IO多路复用,就是我们熟知的select、poll、epoll模型。从图上可见,在IO多路复用的时候,process在两个处理阶段都是block住等待的。初看好像IO多路复用没什么用,其实select、poll、epoll的优势在于可以以较少的代价来同时监听处理多个IO。

4 异步IO

在这里插入图片描述

从上图看出,异步IO要求process在recvfrom操作的两个处理阶段上都不能等待,也就是process调用recvfrom后立刻返回,kernel自行去准备好数据并将数据从kernel的buffer中copy到process的buffer在通知process读操作完成了,然后process在去处理。遗憾的是,linux的网络IO中是不存在异步IO的,linux的网络IO处理的第二阶段总是阻塞等待数据copy完成的。真正意义上的网络异步IO是Windows下的IOCP(IO完成端口)模型。

在这里插入图片描述

non-blocking IO和asynchronous IO的区别还是很明显的,non-blocking
IO仅仅要求处理的第一阶段不block即可,而asynchronous IO要求两个阶段都不能block住。

Linux的socket 事件wakeup callback机制

linux(2.6+)内核的事件wakeup callback机制,这是IO多路复用机制存在的本质。Linux通过socket睡眠队列来管理所有等待socket的某个事件的process,同时通过wakeup机制来异步唤醒整个睡眠队列上等待事件的process,通知process相关事件发生。通常情况,socket的事件发生的时候,其会顺序遍历socket睡眠队列上的每个process节点,调用每个process节点挂载的callback函数。在遍历的过程中,如果遇到某个节点是排他的,那么就终止遍历,总体上会涉及两大逻辑:

(1)睡眠等待逻辑;涉及select、poll、epoll_wait的阻塞等待逻辑
[1]select、poll、epoll_wait陷入内核,判断监控的socket是否有关心的事件发生了,如果没,则为当前process构建一个wait_entry节点,然后插入到监控socket的sleep_list
[2]进入循环的schedule直到关心的事件发生了
[3]关心的事件发生后,将当前process的wait_entry节点从socket的sleep_list中删除。

(2)唤醒逻辑

[1]socket的事件发生了,然后socket顺序遍历其睡眠队列,依次调用每个wait_entry节点的callback函数
[2]直到完成队列的遍历或遇到某个wait_entry节点是排他的才停止。
[3]一般情况下callback包含两个逻辑:1.wait_entry自定义的私有逻辑;2.唤醒的公共逻辑,主要用于将该wait_entry的process放入CPU的就绪队列,让CPU随后可以调度其执行。

Select、Poll、Epoll

三个都是IO多路复用的机制(都是同步I/O)
(1)select==>时间复杂度O(n),本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理
它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。

缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024

(2)poll==>时间复杂度O(n),poll本质上和select没有区别,它没有最大连接数的限制,原因是它是基于链表来存储的
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.

缺点:
1、大量的fd的数组被整体复制于用户态和内核地址空间之间
2、poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,下次poll时会再次报告 (3)epoll==>时间复杂度O(1) 有EPOLLLT【默认】和EPOLLET两种触发模式

epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))

epoll的优点:
1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口);
2、效率提升,不是轮询的方式(它只管你“活跃”的连接,而跟连接总数无关) 3、
内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递(内存共享)

综上,在选择select,poll,epoll时要根据具体的使用场合以及这三种方式的自身特点。

1、表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。
2、select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值