Unix IO 复用模型之 select & poll & epoll

IO 复用指的是,可以一次在多个 socket 上等待事件发生。


select 模型是提交给内核多个 socket 句柄(默认不超过 32 * 32 = 1024 个),循环询问内核,我给你的这三类 socket 集合中,有没有事件发生的某些 socket,如果有,则把发生事件的 socket 放在其对应类别的集合中返回给我!因为如果你不分类给我,我判断不了它发生了什么网络事件!另外,如果我不分类给你,你也不能高效工作,当发生一类网络事件时,你必须要遍历所有句柄,而我归类后,你就可以有针对的遍历!


poll 模型的本质与 select 相差无几,在此文后面会介绍到差别。


epoll 模型有较大改进,它将要监控的 socket 一次一个地加入到监控集合中,此集合使用红黑树作为数据结构,因此,可以很快地查询,更改 socket 的状态。另外,由于此集合长驻内核的内存中,所以不需要用户进程与内核之间的内存拷贝。更多的区别见于后面。


1.select 和 poll 将所有的事情交给内核完成,select/poll 通过循环被调用,不停向操作系统提这样的问题:“你看一下我给你的这些 socket 集合中的一个或多个是否有发生事件”,

此时,内核必须要将用户给定的 socket 集合拷贝到内核里面,这必须要使用系统调用才能完成,所以比较费空间。由于每一次询问都会导致内存拷贝,所以效率比较低。


2.内核在接受 socket 集合后,去查询这些 socket 的状态,(从系统感知到发生事件的 socket 的 socket 队列中查询),这里涉及到挨个遍历,所以效率比较低。


3.对于 select 而言,使用一个位来表示一个 socket,以减少数据从用户进程拷贝到内核。但这样做会产生一个弊端,即,使用 n 位只能表示 n 个 socket,实际上,select 默认的使用 32 个 long 型去表示 socket 集合,即总共只有: 32 * 32。当然也可以更改这个上限。select 使用了三个这样的位图结构,分别表示发生了读,写,异常这三种事件的 socket.。如 socket 句柄值为 5 ,则位图数据为 10000,第五位为 1。传入集合为 5,2,1,则对应的值为 10011。

对于 poll 而言,它不再使用位来表示 socket,而直接使用一个数据结构来表示一个 socket,把读,写,异常各种状态作为这个数据结构的成员,表示一个 socket 发生了什么事件。由于不使用位图数据,因而它解决了监控 socket 集合的上限问题。但是它还是不能解决遍历的问题。所以效率还是比较低。



epoll 就是为了解决上面两种模型的缺点。
1.将需要监控的 socket 一开始就放入内核,只会有一次用户进程内存拷贝到内核内存,之后就使用内存映射,将内核进程区的内存映射到用户进程,也不会产生内存拷贝。
2.每次将一个 socket 被加入到 epoll 监控队列中时,绑定在一起被加入的还有此 socket 希望被监控事件和用户数据(一般用来传递数据)。它睡一会儿,判断一下此 socket 的事件是否发生,如果发生了立即返回,没有发生继续以后的动作直到超时返回。注意,因为 epoll 使用红黑树来存放 socket,故查找效率为 lgn,效率比 select,poll 高了不少。
另外,由于使用红黑树结构,所以动态增加,删除一个 socket 效率也非常高,性能比较稳定。
3.一次监控的 socket 的数目与内存或系统设置的最多打开文件数目有关系,没有明确的限制。




实际上,select , poll, epoll 都是同步 socket ,因为必须使用某一个函数循环去判断有没有发生某事件,如果发生,则去读数据,如果没有发生,则继续判断。直到在 socket 上发生了某事件,然后才去读数据。这种思想本质上还是同步的。因为它有一个明确的流程:没有数据 -> 阻塞或者非阻塞循环判断;有数据 -> 读数据;这一流程是不可以乱序的,所以
它们都是同步的 socket.


同步的 socket 免了去使用轮询那种无聊地,占用 CPU 的方式。接下来说的 异步 socket 会改为挂起自已(不再浪费 CPU),等待通知的方式。


异步的 socket 是:提出一个申请,我并不是”主动“地,浪费 CPU 时间去轮询,直到我知道完成了,好处理接下来的事情。而是 socket 就绪后,我”被动地“接到一个通知,事情已经处理完了。
异步 socket 可以有回调和事件通知两种方式
先说说回调,我们不再以循环询问内核的方式,而代以回调的方式。每次内核收到网络设备中断,让内核去调用一个函数(注意,是内核调用的,也即花费内核时间片),进行某些处理。
再说事件通知,异步 socket 代以类似内核中断的方式,一旦某个底层的中断发生,内核会使用事件唤醒相应的线程
(这里,想必你已经猜到,异步 socket 需要与线程关联(间接或直接))。

唤醒的机制一般是通过触发线程正在等待的事件内核对象,即到了某个事件发生的时候,线程一定会从内核态切换到用户态(即被唤醒),进行接下来的处理。


下一篇文章将展示异步 IO

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值