Linux select、poll、epoll解析

一个已打开的文件可以是一个常规文件,也可以是一个设备文件,还可以是进程为了实现通信建立的管道或者套接字等等。如果现在有一个进程要监听一个打开文件,while循环就好了。但是如果要监听多个文件,那就得是异步IO。这就是我们今天要讨论的三种方法。

 

我们先从select开始

 

 int select(

int maxfdp1,//一共有多少个文件描述符

fd_set *readset,//这些文件描述符是否出现可读事件

fd_set *writeset, //这些描述符是否出现可写事件

fd_set *exceptset,//这些描述符是否出现错误异常事件

struct timeval *timeout//设立超时时间的结构体

);

 

fd_set结构体是文件描述符集,该结构体实际上是一个整型数组,数组中的每个元素的每一位标记一个文件描述符。fd_set能容纳的文件描述符数量由FD_SETSIZE指定,一般情况下,FD_SETSIZE等于1024,这就限制了select能同时处理的文件描述符的总量。

 

select的底层是sys_select()

 

Sys_select先将用户数据拷到内核然后处理超时时间,接下来调用core_sys_select()Core_sys_select()主要是建立了一个含有六个位图的结构体,分别监视可读、可写、错误异常等等,接着调用do_select()do_select()是整个select的主体。它先注册了回调函数,接下来就是一个死循环,每次循环检查32fd(32*32=1024)是否出现需要检测的状态。如果是,调用poll方法。poll方法最后返回一个描述读写操作是否就绪的mask掩码,通过mask掩码修改fd_set的值。最后将fd_set拷回用户空间。如果遍历一圈发现没有就绪的,就进入睡眠。等待唤醒(就绪或者超时)。

 

说完整个流程,我们来说说这个select的不足。我们可以看到,select的每一次调用都是需要将用户数据拷贝到内核的,这也就极大的限制了它在处理大量监听IO的效率。在do_select里需要检测所有的fd,也就是说每次调用select都需要检测所有的fd,直到超时时间到、接收到信号等。对于监听的最大值是1024。虽然能够对宏进行修改,但是修改会造成select性能的下降。

 

说完了select()我们来说说poll

 

int poll(

struct pollfd *fds, //测试某个给定描述符的条件

 nfds_t nfds, //需要监听的数目

int timeout//超时时间

);

 

struct pollfd {

int fd; //指定要监听的文件描述符

short events; //指定监听fd上的什么事件 

short revents;//fd上事件就绪后,用于保存实际发生的时间 

};

 

poll的底层是sys_poll,sys_poll先处理超时时间,接着调用do_sys_poll()(有的版本会在sys_poll里通过initwait()注册回调函数,有的是将这个操作放在do_sys_poll()里)。Do_sys_poll将用户空间的数据拷贝到系统空间,在系统空间里,poll实现了一个栈来存放各种待检测的描述符,使用一个链表来管理。如果栈的大小不够用,那就申请更多的内存,知道放完为止。最后调用do_poll(),do_poll()之后释放掉之前申请的内存(包括栈),将数据拷贝到用户空间。

 

接下来我们谈谈do_poll()

Do_polldo_select没有太多的区别,都是线性的检查描述符是否出现所需要的状态,发现状态调用do_pollfd()。没有就进入睡眠等待唤醒,唤醒后又检查一次。

 

select相比,poll没有太多的区别,唯一的改进是使用了链表来保存fd,使得能够监听的数量远远超过了1024,但是,对于将用户数据拷贝到系统空间、线性遍历fd这两个并没有太大的改变。

 

我们看到,selectpoll的效率瓶颈有两个,现在的问题是怎么改进。每次调用的时候都需要将fd拷入内核,这太麻烦了。为什么内核不自己保存已经拷入的fd呢?是的,epoll就是自己保存拷入的fd。在epoll_ctl的时候就把所有的fd拷进内核,之后再一起wait。第二,selectpoll都是线性的检查整个数组(链表)里的活跃fd,这无疑是一个巨大的浪费。如果我们不再检查活跃的fd,而是活跃的fd自动调用一个回调函数,把自己挂到就绪队列里。那不是简单的多么?

 

 

epoll有这几个函数:epoll_create()epoll_ctl()epoll_wait()。我们先从epoll_create开始说起

 

epoll_create()会先检查是否存在这么一个文件系统:eventpollfs,如果有,就创建两个内核cache,分别用来放eppoll_entryepitem就在该目录下创建一个文件,返回打开文件号fid。如果没有,就安装这个文件系统,再建cache和文件。

 

epoll_ctl()是对fd操作的重要函数,以EPOLL_CTL_ADD为例,它会调用ep_insert()fd拷入内核,安装回调函数等等。fd出现了需要监听的状态后就调用回调函数,将该epitem结构挂载到就绪队列上。为了提高效率,内核实现了一个红黑树来存放fd。每添加一个fd,就会从cache里获得一个epitem数据结构。系统通过eventpoll来管理epitem结构的红黑树。

 

相比其他两个而言,epoll_wait()需要做的事情就少了很多。epoll_wait()的底层是sys_epoll_wait()。在经过一些参数检查之后,sys_epoll_wait()会调用ep_poll()ep_poll()设置了超时时间,接着就进入睡眠。等待某个fd的回调函数将自身唤醒。唤醒之后,由于就绪队列不为空,退出循环。最后链表里的数据拷贝回用户空间,返回获取到事件的个数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值