select、poll、epoll之间的区别总结
2016-07-30 12:05
323 查看
0. 背景
本文摘自 http://www.cnblogs.com/Anker/p/3265058.htmlselect,
poll,
epoll都是
IO多路复用的机制。
I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但
select,
poll,
epoll本质上都是同步
I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步
I/O则无需自己负责进行读写,异步
I/O的实现会负责把数据从内核拷贝到用户空间。关于这三种
IO多路复用的用法,前面三篇总结写的很清楚,并用服务器回射
echo程序进行了测试。连接如下所示:
select:http://www.cnblogs.com/Anker/archive/2013/08/14/3258674.html
poll:http://www.cnblogs.com/Anker/archive/2013/08/15/3261006.html
epoll:http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html
今天对这三种
IO多路复用进行对比,参考网上和书上面的资料,整理如下:
1. select实现
select的调用过程如下所示:(1)使用
copy_from_user从用户空间拷贝
fd_set到内核空间
(2)注册回调函数
__pollwait
(3)遍历所有
fd,调用其对应的
poll方法(对于
socket,这个
poll方法是
sock_poll,
sock_poll根据情况会调用到
tcp_poll,
udp_
poll或者
datagram_poll)
(4)以
tcp_poll为例,其核心实现就是
__pollwait,也就是上面注册的回调函数。
(5)
__pollwait的主要工作就是把
current(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于
tcp_poll来说,其等待队列是
sk->sk_sleep(注意把进程挂到等待队列中并不代表进程已经睡眠了)。在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时
current便被唤醒了。
(6)
poll方法返回时会返回一个描述读写操作是否就绪的
mask掩码,根据这个
mask掩码给
fd_set赋值。
(7)如果遍历完所有的
fd,还没有返回一个可读写的
mask掩码,则会调用
schedule_timeout是调用
select的进程(也就是
current)进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间(
schedule_timeout指定),还是没人唤醒,则调用
select的进程会重新被唤醒获得
CPU,进而重新遍历
fd,判断有没有就绪的
fd。
(8)把
fd_set从内核空间拷贝到用户空间。
总结:select的几大缺点:
(1)每次调用
select,都需要把
fd集合从用户态拷贝到内核态,这个开销在
fd很多时会很大
(2)同时每次调用
select都需要在内核遍历传递进来的所有
fd,这个开销在
fd很多时也很大
(3)
select支持的文件描述符数量太小了,默认是
1024
2 poll
实现
poll的实现和
select非常相似,只是描述
fd集合的方式不同,
poll使用
pollfd结构而不是
select的
fd_set结构,其他的都差不多。
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
不同与
select使用三个位图来表示三个
fdset的方式,
poll使用一个
pollfd的指针实现。
struct pollfd { int fd; /* file descriptor */ short events; /* requested events to watch */ short revents; /* returned events witnessed */ };
pollfd结构包含了要监视的
event和发生的
event,不再使用
select“参数-值”传递的方式。同时,
pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。 和
select函数一样,
poll返回后,需要轮询
pollfd来获取就绪的描述符。
从上面看,
select和
poll都需要在返回后,通过遍历文件描述符来获取已经就绪的
socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。
关于
select和
poll的实现分析,可以参考下面几篇博文:
http://blog.csdn.net/lizhiguo0532/article/details/6568964#comments
http://blog.csdn.net/lizhiguo0532/article/details/6568968
http://blog.csdn.net/lizhiguo0532/article/details/6568969
http://www.ibm.com/developerworks/cn/linux/l-cn-edntwk/index.html?ca=drs-
http://linux.chinaunix.net/techdoc/net/2009/05/03/1109887.shtml
3、epoll
epoll既然是对
select和
poll的改进,就应该能避免上述的三个缺点。那
epoll都是怎么解决的呢?在此之前,我们先看一下
epoll和
select和
poll的调用接口上的不同,
select和
poll都只提供了一个函数——
select或者
poll函数。而
epoll提供了三个函数,
epoll_create,
epoll_ctl和
epoll_wait。
int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
1). int epoll_create(int size);
创建一个
epoll的句柄,
size用来告诉内核这个监听的数目一共有多大,这个参数不同于
select()中的第一个参数,给出最大监听的
fd+1的值,参数
size并不是限制了
epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。
当创建好
epoll句柄后,它就会占用一个
fd值,在
linux下如果查看
/proc/process_id/fd/,是能够看到这个
fd的,所以在使用完
epoll后,必须调用
close()关闭,否则可能导致
fd被耗尽。
2). int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数是对指定描述符
fd执行
op操作。
epfd:是
epoll_create()的返回值。
op:表示op操作,用三个宏来表示:添加
EPOLL_CTL_ADD,删除
EPOLL_CTL_DEL,修改
EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。
-
fd:是需要监听的
fd(文件描述符)
epoll_event:是告诉内核需要监听什么事,
struct epoll_event结构如下:
struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; //events可以是以下几个宏的集合: EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭); EPOLLOUT:表示对应的文件描述符可以写; EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来); EPOLLERR:表示对应的文件描述符发生错误; EPOLLHUP:表示对应的文件描述符被挂断; EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。 EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
3). int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待
epfd上的
io事件,最多返回
maxevents个事件。
参数
events用来从内核得到事件的集合,
maxevents告之内核这个
events有多大,这个
maxevents的值不能大于创建
epoll_create()时的
size,参数
timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
对于第一个缺点,
epoll的解决方案在
epoll_ctl函数中。每次注册新的事件到
epoll句柄中时(在
epoll_ctl中指定
EPOLL_CTL_ADD),会把所有的
fd拷贝进内核,而不是在
epoll_wait的时候重复拷贝。
epoll保证了每个
fd在整个过程中只会拷贝一次。
对于第二个缺点,
epoll的解决方案不像
select或
poll一样每次都把
current轮流加入
fd对应的设备等待队列中,而只在
epoll_ctl时把
current挂一遍(这一遍必不可少)并为每个
fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的
fd加入一个就绪链表)。
epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的
fd(利用
schedule_timeout()实现睡一会,判断一会的效果,和
select实现中的第7步是类似的)。
对于第三个缺点,
epoll没有这个限制,它所支持的
FD上限是最大可以打开文件的数目,这个数字一般远大于
2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以
cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。
4. 总结
(1)select,
poll实现需要自己不断轮询所有
fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而
epoll其实也需要调用
epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪
fd放入就绪链表中,并唤醒在
epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是
select和
poll在“醒着”的时候要遍历整个
fd集合,而
epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
(2)
select,
poll每次调用都要把
fd集合从用户态往内核态拷贝一次,并且要把
current往设备等待队列中挂一次,而
epoll只要一次拷贝,而且把
current往等待队列上挂也只挂一次(在
epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个
epoll内部定义的等待队列)。这也能节省不少的开销。
参考资料:
http://www.cnblogs.com/apprentice89/archive/2013/05/09/3070051.html
http://www.linuxidc.com/Linux/2012-05/59873p3.htm
http://xingyunbaijunwei.blog.163.com/blog/static/76538067201241685556302/
http://blog.csdn.net/kkxgx/article/details/7717125
https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/epoll-example.c
相关文章推荐
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结[整理]
- select、poll、epoll之间的区别总结[整理]
- select、poll、epoll之间的区别总结[整理]
- select、poll、epoll之间的区别总结[整理]
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结[整理]
- select、poll、epoll之间的区别总结[整理]
- select、poll、epoll之间的区别总结[整理]
- select、poll、epoll之间的区别总结[整理]
- select、poll、epoll之间的区别总结[整理]
- select、poll、epoll之间的区别总结[整理]
- select、poll、epoll之间的区别总结[整理]
- select、poll、epoll之间的区别总结[整理]
- select、poll、epoll之间的区别总结
- select、poll、epoll之间的区别总结[整理]
- 转:select、poll、epoll之间的区别总结[整理]
- select/poll/epoll之间的区别总结(个人理解)
- select、poll、epoll之间的区别总结[整理]
- select、poll、epoll之间的区别总结[整理]