1.IO多路复用的概念
- IO多路复用形象举例 - ZqurGy - 博客园 (cnblogs.com)
- 单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力。
2.为什么出现IO多路复用
-
服务器需要维护N多个与客户端之间的socketfd;并且在receive之前需要知道数据知否出现---》组件IO多路复用技术出现---》解决检测服务器端N多个fd的状态
- Tcp是有连接的,Udp是无连接---》上述情况出现在Tcp连接情况
-
IO多路复用的三种方案:select/poll/epoll
select(fds+1, rds, wds, timeout)
poll(fds, nfd, timeout)
- epoll
epoll_create(size/flags)
--》创建根节点---》epoll实例epoll_ctl(fd, op, events)
---》挂载fd节点epoll_wait(fd, events, maxevents, timeout)
---》检测
-
Tcp建立连接---》需要的fds放入IO多路复用中管理---》检测fd缓存状态---》receive
receive(fd,buff,sizeof(buff),0)
3.IO多路复用区别特点
select | poll | epoll | |
---|---|---|---|
性能 | 随IO处理数增加,性能逐渐下降;并且连接数有限制 | 随IO处理数增加,性能逐渐下降 | 随IO处理数增加,性能基本上不会下降 |
连接数 | 最大连接数1024;处理1024以上则需要修改宏FD_SETSIZE,重新编译 | 无限制 | 无限制 |
数据结构 | 不确定,有数组的可能,但是为线性结构 | 不确定,有链表的可能,但是为线性结构 | 红黑树,队列 |
处理方式 | 线性轮询 | 线性轮询 | 回调callback |
内存拷贝 | 所有fds从用户空间和内核空间来回拷贝** | 所有fds从用户空间和内核空间来回拷贝 | epoll_wait()只需要从就绪队列上拷贝由内核空间到用户空间 |
使用复杂度 | 低 | 低 | 中 |
时间复杂度 | O(n) | O(n) | O() |
- select/poll
- 拷贝fds--》从用户空间至内核
- for(;;)进行遍历判断
- select:进行分类rds,wds,eds
- poll:pollfd维护自身状态
- 从内核拷贝至用户空间
- epoll
- epoll_create--->创建一个根节点
- epoll_ctl---》挂载节点
- epoll_wait---》从内核到用户只拷贝满足条件的节点,从就绪队列中拷贝
- 在哪些场景下 select比epoll更合适?
- IO数量不多,且使用多线程多进程的情况使用select比epoll更合适
- epoll使用红黑树,必须需要加锁,消耗的资源更多
- IO数量不多,且使用多线程多进程的情况使用select比epoll更合适
红黑树:
? 等待补充链接
3.IO多路复用源码
-
select:kern_select---》core_sys_select---》do_select---linux-6.0.2\fs\select.c
-
static int kern_select(int n, fd_set __user *inp, fd_set __user *outp,fd_set __user *exp, struct __kernel_old_timeva l __user *tvp){ struct timespec64 end_time, *to = NULL; struct __kernel_old_timeva l tv; int ret; if (tvp) { //copy_from_user从用户空间拷贝fds 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; } //调用select--》core_sys_select ret = core_sys_select(n, inp, outp, exp, to); return poll_select_finish(&end_time, tvp, PT_TIMeva l, ret); }
-
int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timespec64 *end_time) { ...... //加读锁,读取文件数量 rcu_read_lock(); fdt = files_fdtable(current->files); max_fds = fdt->max_fds; rcu_read_unlock(); ...... //select底层处理---》do_select ret = do_select(n, &fds, end_time); if (ret < 0) goto out; if (!ret) { ret = -ERESTARTNOHAND; if (signal_pending(current)) goto out; ret = 0; } ...... out_nofds: return ret; }
-
static int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time) { ..... //读锁,读取文件数量 rcu_read_lock(); retval = max_select_fd(n, fds); rcu_read_unlock(); ..... //通过循环遍历,主要流程流程 for (;;) { unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp; bool can_busy_loop = false; 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, j; unsigned long res_in = 0, res_out = 0, res_ex = 0; __poll_t mask; in = *inp++; out = *outp++; ex = *exp++; all_bits = in | out | ex; if (all_bits == 0) { i += BITS_PER_LONG; continue; } for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit &
-