IO那些事07-多路复用IO之select,poll,epoll

18 篇文章 1 订阅

在这里插入图片描述

多路复用器下的实现

所有的多路复用器都是基于POSIX的规范进行实现的。

SELECT

在这里插入图片描述

最通用的多路复用器,也就是基本所有操作系统都有的,就是select

man 2 select

在这里插入图片描述

select的参数可以发现是传入若干个文件描述符(读的和写的),但是一次调用可以操作的文件描述符FD_SETSIZE最大不能超过1024个。
nfds:操作多少个文件描述符
readfds:读的文件描述符集合
writefds:写的文件描述符集合
timeout:等待的时间,超过时间后,直接返回当前的文件描述符的状态。

POLL

man 2 poll

在这里插入图片描述

linux中加强版的select,与select的区别在于,select有文件描述符个数的限制,而poll则无文件描述符的限制。

多路复用的本质

多路复用的本质,就是通过一次系统调用,就可以拿到所有socket的状态(是否可读),然后程序只需要对那些返回状态为可读的socket进行recv。
可以发现,多路复用与NIO的差别在于,多路复用将socket的状态与读写是分开了的,那这样的好处也非常明显,只做有用的付出,避免了无效的系统调用。
在这里插入图片描述

但其实询问文件描述符的状态也是一次遍历操作,但这个遍历是发生在了内核态,就避免了许多的两态切换。

同步模型/异步模型

无论是多路复用还是NIO,虽然都是可以实现非阻塞,但其实都是同步的。
理解同步的本质,就是只要是最终需要程序去完成对socket的读取,那么这就是同步的。
那异步是什么?异步就是说,完全将由内核接手一切事情,由内核完成对socket的读取,然后最终放入buffer中,这对程序而言,是没有进行读取IO操作的,看似就像是只从一个buffer中取数据的一个过程。
目前linux并未实现异步。而windows实现了异步, 因为异步将会造成许多系统安全性的问题,并且让内核干了太多的事情,而windows偏于服务用户的,所以这种实现也没什么关系。

多路复用器select,poll的弊端

为什么有弊端的原理和如何去改良的思路

既然有弊端,那么就要想办法解决弊端!
因此,接下来就是要登场的角色:EPOLL
但在说EPOLL之前,有一些前置知识可以了解下:
在这里插入图片描述

先说一说中断,中断在计算机运行中是一个很重要的机制。
中断有软中断和硬中断,无论哪种都有着相应的作用。
而中断的本质,都是CPU读到一个int开头的指令,根据后面的数去中断向量表找到对应的中断号对应的回调函数,然后去进行执行。
以系统调用来说,我们会通过int 80去找到对应的回调函数进行执行,回调函数中会根据寄存器对应的指令找到要调用的系统函数。
而更基础一些的,还有硬件级别的中断,如果只有一颗CPU,如何支撑多个进程的同时运行? 一定是通过时钟中断,晶振,产生硬中断,来强行打断CPU对某段进程的运行,进行保存现场,然后切换到其他的进程进行执行。
当然扯了这么多,实际与当前比较贴切的是与IO相关的中断。

以网卡为例,当数据到达网卡时,网卡内部也有一个buffer缓冲区,此时可以根据不同的中断策略,将数据以某种策略丢到内存的DMA(内存中一块专门用于网卡数据存储的区域)中。
策略有三种:
1、来一个数据包,发生一次IO中断,把数据包丢到内核内存中的DMA
2、先丢满网卡的buffer,满了一次性丢一次DMA。
3、如果网卡造成的中断过于频繁,CPU认为不如直接轮询网卡的buffer区域。

无论以何种策略,最终数据都会被丢到DMA中,但此时还没有与FD或者说内核进行关联,此时需要触发中断,也就是触发对应的回调函数,将DMA数据与内核中的FD进行关联,这样才能造就,程序去访问内核的FD是否可读/可写,才有状态返回。
但这里可以看到,默认的回调行为,只是单纯进行了FD的关联,而没有记录哪些FD是有数据的,当APP询问时,每次都要轮询所有FD,询问状态。
因此,解决思路就是:如果可以在回调里,除了与FD进行关联,还同时把进行了关联的FD(有数据的FD)放到另外一个地方记录起来,那么下次APP访问的时候,只需要访问这块区域是不是就可以了呢?
而这,也就是EPOLL的解决之道。

EPOOL

在这里插入图片描述

分析图上,先看右侧部分。
如果你想搭建一个服务器,你必然的需要使用socket,bind,listen这样的系统调用获得一个文件描述符。
接下来,当你想要获取那些已就绪状态的文件描述符的时候,按多路复用select或者poll来说,就是使用select或者poll,然后通过在内核中根据入参fds,去循环询问内核中每一个fd的当前状态。(此时的内核的每一个fd中一定如果接受了网卡数据,触发中断更改了自身的状态)。

epoll是如何做到呢?

开头,依旧是socket,bind,listen这样的系统调用获得一个文件描述符。
1、然后可以通过epoll_create创建一个epoll管理下的内核空间(一个红黑树存储结构的内存空间),用来记录需要监听的fds,这就解决了之前的每次需要全量传递的问题,在第一次传入后,内核就记录下了,下次无需再次传入。
2、有了管理空间,自然需要往里面塞入需要监听的fd,因此需要epoll_ctl进行传入需要监听的fd。
3、最后,可以直接通过epoll_wait向EPOLL在内核中的另外一块空间(一个链表结构的内存空间)获取到当前已就绪状态的fds,这块链表空间里面全部都是已就绪fd,因此无需遍历,直接拿取即可。

那问题来了:这块链表结构的内存空间是什么时候被放入的呢?

这就可以挂钩起刚才的中断概念铺垫,传统的像select,epoll的时候,网卡的中断回调,都只是完成了内核最基础的将网卡数据传输到内核fd中的操作。
而epoll强大之处在于,在这个中断回调中,相当于AOP了一刀,在传给fd的buffer之后,还同时将这些数据状态发生变化的fd去epoll中的红黑树空间中找到,然后丢到另外一块链表区域中。

这样的好处,就是用户可以直接得到已就绪的文件描述符, 而无需在拿取的时候再临时进行系统遍历,但这个遍历不是说整个过程没有了遍历,而只是将遍历的过程放在了网卡数据到达后的中断回调中。

我们上面描述了几个epoll的系统调用,接下来分别看一看:

epoll_create

man 2 epoll_create

在这里插入图片描述

create,创建一个代表存放EPOLL管理的FDS的FD。只会调用一次
早先SELECT,POLL的问题除了无差别的询问之外,每次的系统调用,都要传全量的文件描述符,而EPOLL针对这个问题,解决方案就是epoll_create,此方法会在内核中创建一颗红黑树,并将代表该红黑树的的文件描述符返回。

epoll_ctl

man 2 epoll_ctl

在这里插入图片描述

之后会频繁的调用的方法,操作内核中EPOLL管理空间(红黑树结构)。
epfd:传入刚刚通过epoll_create创建的代表EPOLL管理空间的fd
op:操作,可以有:
在这里插入图片描述

添加,修改,删除

fd:向里面操作(添加,修改,删除)的文件描述符,这个fd可以是listen的fd,也可以是socket的fd
event:关注的事件,是读还是写事件

epoll_wait

man 2 epoll_ctl

在这里插入图片描述

获取EPOLL中的已就绪socket区域(链表结构)的fd
当通过epoll_wait得到了已就绪的文件描述符后,用户依旧需要主动调用accept或者recv,所以epoll依旧是一个同步IO模型

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值