IO多路复用中的select poll epoll的区别

1 select系统调用

第一个参数:限定了内核态遍历bitmap的最大长度

第二个参数:读文件描述符的集合(实质上是一个bitmap,先对所有的文件描述符进行置0,然后对关注的文件描述符置1,第0 1 2个文件描述符是为操作系统预留的)

第三个参数:写文件描述符的集合

第四个参数:监听的异常文件描述符的集合

第五个参数:超时时间

1.1 select的执行原理

①将当前进程的所有文件描述符,一次性从用户态拷贝到内核态

②在内核中快速的无差别遍历每个fd,判断是否有数据到达

③将所有fd状态,从内核态拷贝到用户态,返回就绪的fd个数

④用户态遍历判断哪个fd就绪

1.2 select的限制

①内核态储存文件描述符的数据结构为bitmap数组,长度有限制

②频繁用户态和内核态数据拷贝

③内核态对文件描述符表进行轮询,时间复杂度为o(n),在应对大规模数据效率很低

2 poll:select模型的改进版

用来保存监听的文件描述符数组由bitmap数组改为了结构体数组fds

select使用 fd_set 结构体来存放被监听的文件描述符的,本质上是使用一个位图结构来存放这些被监听的文件描述符的,因此select能够监听的文件描述符数量是有限制的。同时,fd_set 没有将文件描述符和事件进行绑定,它仅仅是一个文件描述符集合,因此,select需要提供3个fd_set类型的参数来分别传入和传出可读、可写及异常事件。fd_set无法做到重用每次必须重新创建。

poll使用pollfd结构体来存放被监听的文件描述符,它比select“聪明”的地方就在于它把文件描述符和与其关联的事件都定义在这个结构体中了,从而使得编程接口变得简洁很多,同时内核每次修改的都是pollfd结构体的revents成员,而events成员保持不变,因此下次调用poll()函数时应用程序无须重置pollfd类型的事件集参数。

poll和select最大的区别就是保存文件描述符的数据结构的不同。

3 epoll:解决select和poll性能不足的问题

3.1 epoll模型的执行原理

1、首先创建结构体epoll_event,主要包含需要监听的事件类型和文件描述符

2、调用epoll_create方法,创建结构体eventpoll,eventpoll结构体中有三个重要字段,分别是rdyList(已就绪的文件描述符双链表,某个读写事件就绪时,就将文件描述符放入rdyList中)、rbr(一颗红黑树管理所有用户进程放进来的socket连接)、wq(一个等待队列,当某个进程关注的事件未就绪时,就会把当前进程的描述符以及回调函数放入这个进程等待队列中,数据到达时就会检查阻塞队列找到阻塞进程进行唤醒)

3、调用event.events注册需要关注的文件描述符上的事件

4、调用epoll_ctl()函数创建客户端和服务端的连接,并把连接注册到eventpoll中。每一个socket连接都会在调用epoll_ctl时被分配一个epitem(也是一个结构体),是每个socket连接对应的红黑树节点,首先判断是红黑树上是否有此文件描述符节点,如果有,则立即返回。如果没有, 则在树干上插入新的节点,并且告知内核注册回调函数。

第一个参数是连接对应的红黑树节点,第二个参数是对应的文件描述符信息,第三个参数是连接对应的eventpoll结构体,第四个参数是wq进程等待队列

6、调用epoll_wait函数(阻塞),进程a检查rdyList中已就绪的事件,如果有事件就绪则立即返回否则阻塞,进程a进入到wq阻塞进程队列。当socket数据接收队列中有数据,就会将已就绪的读事件通过回调函数加入到rdyList中,然后内核检查是否有阻塞进程存在,唤醒进程A返回事件,这时用户态进行处理。

所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。

3.2 epoll改进的内容

①在epoll_ctl函数中,每个文件描述符都指定了回调函数,基于回调函数把就绪事件放到就绪队列rdyList中,将轮询方式改善为了回调。时间复杂度降为o(1)。

②只需要在每次加入新的socket连接时传递文件描述符(epoll_ctl)。epoll_wait()方法不需要传递文件描述符,也只返回已就绪的文件描述符,减少了内核态用户态的数据拷贝。

③保存文件描述符的数据结构是红黑树,没有最大连接数限制。

3.3 epoll快在什么地方

第一、epoll模型只是在调用epoll_ctl方法时,把文件描述符从用户态拷贝到内核态,而select和poll每次执行都要将要监听的所有事件拷贝到内核态。第二、epoll只返回已就绪的事件,select和poll返回所有的事件,由用户态判断哪些文件描述符上有数据到达。第三、epoll采用回调机制,数据到达就将已就绪的文件描述符加入到就绪链表中去,select和poll用主动轮询的方式去判断哪个事件已就绪。epoll检查就绪事件的时间复杂度为o(1)。

3.4 epoll为什么采用红黑树,而不是hash表和b+树

如果使用哈希表的话优点是查询速度快,但使用hash表的话,调用epoll_create方法时hash表底层该创建多大的桶数组合适呢?我们不能够提前知道客户端的连接数,几百万的客户端连接和十几个客户端的连接需要的数组长度差很远。hash表的扩容又比较复杂,所以不适合使用hash表。b+树最主要的特性其实是多叉树,一个节点可以存储多个key,主要目的是降低树的高度,作为磁盘索引时可以减少磁盘的IO数,而且磁盘IO数非常平均,且B+树所有叶子节点由双向链表进行相连,能发挥磁盘顺序读取的优势。在磁盘中适用,在内存中不适用。红黑树的增删性能更高,而每次epoll_ctl会对红黑树进行频繁地写入和删除,b+树每次增删都需要重新平衡,不适合。

3.5 rdyList使用双链表的原因

3.6 epoll的水平触发和边缘触发

水平触发(level-trggered)

只要文件描述符关联的读内核缓冲区非空,有数据可以读取,就一直发出可读信号进行通知,当文件描述符关联的内核写缓冲区不满,有空间可以写入,就一直发出可写信号进行通知LT模式支持阻塞和非阻塞两种方式。epoll默认的模式LT。

边缘触发(edge-triggered)

当文件描述符关联的读内核缓冲区由空转化为非空的时候,则发出可读信号进行通知,当文件描述符关联的内核写缓冲区由满转化为不满的时候,则发出可写信号进行通知两者的区别在哪里呢?水平触发是只要读缓冲区有数据,就会一直触发可读信号,而边缘触发仅仅在空变为非空的时候通知一次。

nginx采用了边缘触发的机制:每次数据到达,内核只会通知用户态一次,大大减少了内核资源的浪费,提高效率。

redis采用了水平出发的机制。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值