博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
电商系统系统笔记之select,poll,epoll深入理解
阅读量:1971 次
发布时间:2019-04-27

本文共 2796 字,大约阅读时间需要 9 分钟。

select poll

在IO多路复用模型中,select函数负责监听多个socket(文件描述符FD),如果有任何一个socket就绪,则返回可读。用户进程可以读取数据,又一次调用receFrom。其实如果并发数量不大,利用select/poll/epoll不一定比多线程+阻塞IO性能好

当用户process调用select的时候,select会将需要监控的readfds集合拷贝到内核空间(假设监控的仅仅是socket可读),然后遍历自己监控的socket sk,挨个调用sk的poll逻辑以便检查该sk是否有可读事件,遍历完所有的sk后,如果没有任何一个sk可读,那么select会调用schedule_timeout进入schedule循环,使得process进入睡眠。如果在timeout时间内某个sk上有数据可读了,或者等待timeout了,则调用select的process会被唤醒,接下来select就是遍历监控的sk集合,挨个收集可读事件并返回给用户了,相应的伪码如下:

 在用户空间创建了一个socket后,返回值是一个文件描述符,下面分析一下创建socket时怎么和文件描述符联系的。在SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)最后调用sock_map_fd进行关联,其中返回的retval就是用户空间获取的文件描述符fd,sock就是调用sock_create创建成功的socket.

缺点:

有优点就有缺点,select不足之处:

[1] 被监控的fds需要从用户空间拷贝到内核空间    为了减少数据拷贝带来的性能损坏,内核对被监控的fds集合大小做了限制,并且这个是通过宏控制的,大小不可改变(限制为1024)。[2] 被监控的fds集合中,只要有一个有数据可读,整个socket集合就会被遍历一次调用sk的poll函数收集可读事件    由于当初的需求是朴素,仅仅关心是否有数据可读这样一个事件,当事件通知来的时候,由于数据的到来是异步的,我们不知道事件来的时候,有多少个被监控的socket有数据可读了,于是,只能挨个遍历每个socket来收集可读事件。

到这里,我们有三个问题需要解决:

(1)被监控的fds集合限制为1024,1024太小了,我们希望能够有个比较大的可监控fds集合

(2)fds集合需要从用户空间拷贝到内核空间的问题,我们希望不需要拷贝

(3)当被监控的fds中某些有数据可读的时候,我们希望通知更加精细一点,就是我们希望能够从通知中得到有可读事件的fds列表,而不是需要遍历整个fds来收集。

为了做到只遍历就绪的fd,我们需要有个地方来组织那些已经就绪的fd。为此,epoll引入了一个中间层,一个双向链表(ready_list),一个单独的睡眠队列(single_epoll_wait_list)

select遗留的三个问题中,问题(1)是用法限制问题,问题(2)和(3)则是性能问题。poll和select非常相似,poll并没着手解决性能问题,poll只是解决了select的问题(1)fds集合大小1024限制问题。下面是poll的函数原型,poll改变了fds集合的描述方式,使用了pollfd结构而不是select的fd_set结构,使得poll支持的fds集合限制远大于select的1024。poll虽然解决了fds集合大小1024的限制问题,但是,它并没改变大量描述符数组被整体复制于用户态和内核态的地址空间之间,以及个别描述符就绪触发整体描述符集合的遍历的低效问题。poll随着监控的socket集合的增加性能线性下降,poll不适合用于大并发场景。

epoll(event poll)

select遗留的三个问题,问题(1)是比较好解决,poll简单两三下就解决掉了,但是poll的解决有点鸡肋。要解决问题(2)和(3)似乎比较棘手,要怎么解决呢?我们知道,在计算机行业中,有两种解决问题的思想:

[1] 计算机科学领域的任何问题, 都可以通过添加一个中间层来解决[2] 变集中(中央)处理为分散(分布式)处理

集合拷贝问题:

epoll引入了epoll_ctl系统调用,将高频调用的epoll_wait和低频的epoll_ctl隔离开。同时,epoll_ctl通过(EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL)三个操作来分散对需要监控的fds集合的修改,做到了有变化才变更,将select或poll高频、大块内存拷贝(集中处理)变成epoll_ctl的低频、小块内存的拷贝(分散处理),避免了大量的内存拷贝。

epoll通过内核与用户空间mmap(内存映射)同一块内存来解决。mmap将用户空间的一块地址和内核空间的一块地址同时映射到相同的一块物理内存地址(不管是用户空间还是内核空间都是虚拟地址,最终要通过地址映射映射到物理地址),使得这块物理内存对内核和对用户均可见,减少用户态和内核态之间的数据交换。

另外,epoll通过epoll_ctl来对监控的fds集合来进行增、删、改,那么必须涉及到fd的快速查找问题,于是,一个低时间复杂度的增、删、改、查的数据结构来组织被监控的fds集合是必不可少的了

*hash linux 2.6.8之前

*红黑树 linux 2.6.8之后

按需遍历就绪的FDS问题

epoll引入了一个中间层,一个双向链表(ready_list),一个单独的睡眠队列(single_epoll_wait_list)

epoll巧妙的引入一个中间层解决了大量监控socket的无效遍历问题。细心的同学会发现,epoll在中间层上为每个监控的socket准备了一个单独的回调函数epoll_callback_sk,而对于select/poll,所有的socket都公用一个相同的回调函数。正是这个单独的回调epoll_callback_sk使得每个socket都能单独处理自身,当自己就绪的时候将自身socket挂入epoll的ready_list。同时,epoll引入了一个睡眠队列single_epoll_wait_list,分割了两类睡眠等待。process不再睡眠在所有的socket的睡眠队列上,而是睡眠在epoll的睡眠队列上,在等待”任意一个socket可读就绪”事件。而中间wait_entry_sk则代替process睡眠在具体的socket上,当socket就绪的时候,它就可以处理自身了。

非常好的文章,由于版权不能转载,共享link:

https://cloud.tencent.com/developer/article/1005481

你可能感兴趣的文章
基础架构系列篇-系统centos7安装docker+COMPOSE
查看>>
基础架构系列篇-NGINX部署VUE
查看>>
基础架构系列篇-系统centos7安装kafka
查看>>
基础架构系列篇-系统centos7中docker安装分布式文件存储服务minio
查看>>
软件质量的8个特性
查看>>
应届渣渣前端的艰难求职之路
查看>>
2021年不可错过的17种JS优化技巧(一)
查看>>
在 Vue 中用 Axios 异步请求API
查看>>
MySQL进阶查询(SELECT 语句高级用法)
查看>>
Mysql 之主从复制
查看>>
【NLP学习笔记】中文分词(Word Segmentation,WS)
查看>>
【超越白皮书7】你需要知道关于ETH2.0的几个事实
查看>>
对于时间复杂度的通俗理解
查看>>
如何输入多组数据并输出每组数据的和?
查看>>
行阶梯型矩阵
查看>>
MATLAB指定路径保存图片方法
查看>>
JAVA学习笔记6 - 数组
查看>>
JAVA学习笔记10 - 继承
查看>>
【学习笔记】Android Activity
查看>>
诡异的 Scroll view may have only one direct child placed within it 错误
查看>>