select 和 epoll的编程实现区别
来源:互联网 发布:linux压缩文件夹zip 编辑:程序博客网 时间:2024/05/19 05:03
原文:http://www.cppblog.com/feixuwu/archive/2010/07/10/119995.html
最近有朋友在面试的时候被问了select 和epoll效率差的原因,和一般人一样,大部分都会回答select是轮询、epoll是触发式的,所以效率高。这个答案听上去很完美,大致也说出了二者的主要区别。
今天闲来无事,翻看了下内核代码,结合内核代码和大家分享下我的观点。
一、连接数
我本人也曾经在项目中用过select和epoll,对于select,感触最深的是linux下select最大数目限制(windows 下似乎没有限制),每个进程的select最多能处理FD_SETSIZE个FD(文件句柄),
如果要处理超过1024个句柄,只能采用多进程了。
常见的使用slect的多进程模型是这样的: 一个进程专门accept,成功后将fd通过unix socket传递给子进程处理,父进程可以根据子进程负载分派。曾经用过1个父进程+4个子进程 承载了超过4000个的负载。
这种模型在我们当时的业务运行的非常好。epoll在连接数方面没有限制,当然可能需要用户调用API重现设置进程的资源限制。
二、IO差别
1、select的实现
这段可以结合linux内核代码描述了,我使用的是2.6.28,其他2.6的代码应该差不多吧。
先看看select:
select系统调用的代码在fs/Select.c下,
- asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp,
- fd_set __user *exp, struct timeval __user *tvp)
- {
- struct timespec end_time, *to = NULL;
- struct timeval tv;
- int ret;
- if (tvp) {
- if (copy_from_user(&tv, tvp, sizeof(tv)))
- return -EFAULT;
- to = &end_time;
- if (poll_select_set_timeout(to,
- tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),
- (tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC))
- return -EINVAL;
- }
- ret = core_sys_select(n, inp, outp, exp, to);
- ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);
- return ret;
- }
前面是从用户控件拷贝各个fd_set到内核空间,接下来的具体工作在core_sys_select中,
- core_sys_select->do_select,真正的核心内容在do_select里:
- int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
- {
- ktime_t expire, *to = NULL;
- struct poll_wqueues table;
- poll_table *wait;
- int retval, i, timed_out = 0;
- unsigned long slack = 0;
- rcu_read_lock();
- retval = max_select_fd(n, fds);
- rcu_read_unlock();
- if (retval < 0)
- return retval;
- n = retval;
- poll_initwait(&table);
- wait = &table.pt;
- if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
- wait = NULL;
- timed_out = 1;
- }
- if (end_time && !timed_out)
- slack = estimate_accuracy(end_time);
- retval = 0;
- for (;;) {
- unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
- set_current_state(TASK_INTERRUPTIBLE);
- inp = fds->in; outp = fds->out; exp = fds->ex;
- rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
- for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
- unsigned long in, out, ex, all_bits, bit = 1, mask, j;
- unsigned long res_in = 0, res_out = 0, res_ex = 0;
- const struct file_operations *f_op = NULL;
- struct file *file = NULL;
- in = *inp++; out = *outp++; ex = *exp++;
- all_bits = in | out | ex;
- if (all_bits == 0) {
- i += __NFDBITS;
- continue;
- }
- for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {
- int fput_needed;
- if (i >= n)
- break;
- if (!(bit & all_bits))
- continue;
- file = fget_light(i, &fput_needed);
- if (file) {
- f_op = file->f_op;
- mask = DEFAULT_POLLMASK;
- if (f_op && f_op->poll)
- mask = (*f_op->poll)(file, retval ? NULL : wait);
- fput_light(file, fput_needed);
- if ((mask & POLLIN_SET) && (in & bit)) {
- res_in |= bit;
- retval++;
- }
- if ((mask & POLLOUT_SET) && (out & bit)) {
- res_out |= bit;
- retval++;
- }
- if ((mask & POLLEX_SET) && (ex & bit)) {
- res_ex |= bit;
- retval++;
- }
- }
- }
- if (res_in)
- *rinp = res_in;
- if (res_out)
- *routp = res_out;
- if (res_ex)
- *rexp = res_ex;
- cond_resched();
- }
- wait = NULL;
- if (retval || timed_out || signal_pending(current))
- break;
- if (table.error) {
- retval = table.error;
- break;
- }
- /*
- * If this is the first loop and we have a timeout
- * given, then we convert to ktime_t and set the to
- * pointer to the expiry value.
- */
- if (end_time && !to) {
- expire = timespec_to_ktime(*end_time);
- to = &expire;
- }
- if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))
- timed_out = 1;
- }
- __set_current_state(TASK_RUNNING);
- poll_freewait(&table);
- return retval;
- }
上面的代码很多,其实真正关键的代码是这一句:
- mask = (*f_op->poll)(file, retval ? NULL : wait);
这个是调用文件系统的 poll函数,不同的文件系统poll函数自然不同,由于我们这里关注的是tcp连接,而socketfs的注册在 net/Socket.c里。
register_filesystem(&sock_fs_type);
socket文件系统的函数也是在net/Socket.c里:
- static const struct file_operations socket_file_ops = {
- .owner = THIS_MODULE,
- .llseek = no_llseek,
- .aio_read = sock_aio_read,
- .aio_write = sock_aio_write,
- .poll = sock_poll,
- .unlocked_ioctl = sock_ioctl,
- #ifdef CONFIG_COMPAT
- .compat_ioctl = compat_sock_ioctl,
- #endif
- .mmap = sock_mmap,
- .open = sock_no_open, /* special open code to disallow open via /proc */
- .release = sock_close,
- .fasync = sock_fasync,
- .sendpage = sock_sendpage,
- .splice_write = generic_splice_sendpage,
- .splice_read = sock_splice_read,
- };
从sock_poll跟随下去,
最后可以到 net/ipv4/tcp.c的
unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait)
这个是最终的查询函数,
也就是说select 的核心功能是调用tcp文件系统的poll函数,不停的查询,如果没有想要的数据,主动执行一次调度(防止一直占用cpu),直到有一个连接有想要的消息为止。
从这里可以看出select的执行方式基本就是不同的调用poll,直到有需要的消息为止,如果select 处理的socket很多,这其实对整个机器的性能也是一个消耗。
2、epoll的实现
epoll的实现代码在 fs/EventPoll.c下,
由于epoll涉及到几个系统调用,这里不逐个分析了,仅仅分析几个关键点,
第一个关键点在
static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
struct file *tfile, int fd)
这是在我们调用sys_epoll_ctl 添加一个被管理socket的时候调用的函数,关键的几行如下:
epq.epi = epi;
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
/*
* Attach the item to the poll hooks and get current event bits.
* We can safely use the file* here because its usage count has
* been increased by the caller of this function. Note that after
* this operation completes, the poll callback can start hitting
* the new item.
*/
revents = tfile->f_op->poll(tfile, &epq.pt);
这里也是调用文件系统的poll函数,不过这次初始化了一个结构,这个结构会带有一个poll函数的callback函数:ep_ptable_queue_proc,
在调用poll函数的时候,会执行这个callback,这个callback的功能就是将当前进程添加到 socket的等待进程上。
- static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
- poll_table *pt)
- {
- struct epitem *epi = ep_item_from_epqueue(pt);
- struct eppoll_entry *pwq;
- if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {
- init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
- pwq->whead = whead;
- pwq->base = epi;
- add_wait_queue(whead, &pwq->wait);
- list_add_tail(&pwq->llink, &epi->pwqlist);
- epi->nwait++;
- } else {
- /* We have to signal that an error occurred */
- epi->nwait = -1;
- }
- }
注意到参数 whead 实际上是 sk->sleep,其实就是将当前进程添加到sk的等待队列里,当该socket收到数据或者其他事件触发时,会调用
sock_def_readable 或者sock_def_write_space 通知函数来唤醒等待进程,这2个函数都是在socket创建的时候填充在sk结构里的。
从前面的分析来看,epoll确实是比select聪明的多、轻松的多,不用再苦哈哈的去轮询了。
0
上一篇:基于 Android NDK 的学习之旅-----环境搭建[Eclipse ADT集成Cygwin编译]
下一篇:select 和 epoll的编程实现区别(2)
相关热门文章
- mmap 文件映射内存详解...
- 基础之AWK
- MySQL执行计划分析工具EXPLAIN...
- cocos2d-x简述与HelloWorld程...
- oracle查找索引字段相同的sql...
- test123
- 编写安全代码——小心有符号数...
- 使用openssl api进行加密解密...
- 一段自己打印自己的c程序...
- sql relay的c++接口
- linux dhcp peizhi roc
- 关于Unix文件的软链接
- 求教这个命令什么意思,我是新...
- sed -e "/grep/d" 是什么意思...
- 谁能够帮我解决LINUX 2.6 10...
给主人留下些什么吧!~~
评论热议
0 0
- select 和 epoll的编程实现区别
- select 和 epoll的编程实现区别(2)
- Select和epoll的区别
- Select和epoll的区别
- Select和epoll的区别
- select和epoll的区别
- Select和epoll的区别
- select和epoll的区别
- Select和epoll的区别
- Select和epoll的区别
- epoll 和 select 的区别
- select和Epoll的区别
- epoll和select的区别
- epoll和select的区别
- epoll和select的区别
- Select和epoll的区别
- select和epoll的区别
- select 和 epoll的区别
- GCC编译器选项及优化提示
- Java socket示例(demo)TCP/IP
- Android 模拟器(JAVA)与C++ socket 通讯(IP设置)
- 基于 Android NDK 的学习之旅-----环境搭建[Eclipse ADT集成Cygwin编译]
- Android使用contentprovide实现对其他应用数据库的读写
- select 和 epoll的编程实现区别
- select 和 epoll的编程实现区别(2)
- linux非阻塞式socket编程之select()用法
- fd_set 用法 socket
- necessitas
- 进程间通信——管道
- 获取Java项目根目录 N多方法(转载)
- appledoc格式
- 世界首富如何炼成? 看盖茨20条箴言
原创粉丝点击
热门IT博客
热门问题
老师的惩罚
人脸识别
我在镇武司摸鱼那些年
重生之率土为王
我在大康的咸鱼生活
盘龙之生命进化
天生仙种
凡人之先天五行
春回大明朝
姑娘不必设防,我是瞎子
重庆市彭水县属于那个区
彭水根
彭浪屿
彭浪屿在哪
彭浪屿在哪里
厦门鼓浪屿好玩吗
彭清一
产后阴前壁彭出多久才恢复
产后阴道壁彭出
彭炽权
彭玉麟
彭登怀
抖音里真的是彭家声三女儿吗
彭禹
彭禹幺
彭秀霞
彭秀霞年轻照片
彭的组词是什么
彭组词有哪些
彭罗斯三角
彭莹玉
彭蕙蘅
彭薇薇
彭豆豆
彭述之
彭述
彭金章
彭铠立
双流彭镇
彭长健
彭阳客栈
宁夏彭阳
中国彭阳
彭雪峰
韵母彭雪txt
彭雪茹
彭雪
彭雪芬
彭雪茹高考成绩
韵母风情方烁彭雪
彭青