首页天道酬勤linux poll机制(epoll和poll)

linux poll机制(epoll和poll)

admin 12-03 20:08 270次浏览

I/O多路复用

I/O复用的本质是通过一种机制(系统内核缓冲I/O数据),单个进程可以监控多个文件描述符,一旦一个描述符准备好(一般是读准备好或写准备好),就可以通知程序执行相应的读写操作。

Select、poll、epoll都是Linux API提供的IO复用方式。

相信大家都知道Unix的五种IO模式,不知道的可以=这里查一下。

阻塞IO阻塞IO阻塞IO非阻塞IO多路复用IO多路复用信号驱动IO信号驱动IO异步IO异步IO。前四种IO可以分为同步IO-同步IO。Select、poll和epoll本质上都是同步I/O,因为它们都需要在读写事件准备好之后负责读写,这意味着读写过程被阻塞。

与多进程、多线程技术相比,I/O复用技术最大的优点是系统开销小,系统不需要创建进程/线程,也不需要维护这些进程/线程,大大降低了系统开销。

在介绍select、poll和epoll之前,先介绍一下Linux操作系统中的基本概念:

用户空间/内核空间现在操作系统都使用虚拟内存,所以对于32位操作系统,它的寻址空间(虚拟内存空间)是4G(2的32次方)。操作系统的核心是内核,它独立于普通应用程序,可以访问受保护的内存空间,拥有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核,保证内核的安全性,操作系统将虚拟空间分为两部分,一部分是内核空间,另一部分是用户空间。为了通过进程切换来控制进程的执行,内核必须能够挂起在CPU上运行的进程,并恢复先前挂起的进程的执行。这种行为称为进程切换。所以可以说任何进程都是在操作系统内核的支持下运行的,与内核关系密切,进程切换非常耗费资源。进程阻止正在执行的进程。因为一些预期的事件没有发生,比如请求系统资源失败,等待某些操作完成,新数据还没有到达或者没有新的工作要做等等。系统自动执行阻塞原语(block)以将其自身从运行状态更改为阻塞状态。可以看出,进程的阻塞是进程本身的一种主动行为,所以只有正在运行的进程(获得CPU资源)才能将其转为阻塞状态。当进程进入阻塞状态时,不会占用CPU资源。文件描述符文件描述符是计算机科学中的一个术语,是一个抽象的概念,用来表示对文件的引用。文件描述符的形式是非负整数。实际上,它是一个索引值,指向内核维护的每个进程的打开文件的记录表。当程序打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。在编程中,一些涉及底层的编程通常围绕着文件描述符。然而,文件描述符的概念通常只适用于操作系统,如UNIX和Linux。缓存输入/输出缓存输入/输出,也称为标准输入/输出,是大多数文件系统的默认输入/输出操作。在Linux的缓存I/O机制中,操作系统会将I/O数据缓存在文件系统的页面缓存中,即先将数据复制到操作系统内核的缓冲区,再从操作系统内核的缓冲区复制到应用程序的地址空间。

Select

我们先来分析一下select函数。

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval * time out);【参数描述】int maxfdp1指定需要测试的文件描述符个数,其值为需要测试的最大描述符加1。Fd _ set * readset、Fd _ set * writeset、FD _ set *除了setfd _ set可以理解为一个集合,其中存储了文件描述符,即文件句柄。的中间三个参数指定了一组文件描述符,我们希望内核测试这些描述符的读、写和异常情况。如果对某个条件不感兴趣,可以将其设置为空指针。Conststruct Timeval * Timeout告诉内核等待任何一组指定的文件描述符准备就绪需要多长时间。它的timeval结构用于指定这个周期中的秒数和微秒数。

[返回值]如果有就绪描述符,int返回其数字,如果超时,则返回0,如果有错误,则返回-1。

select运行机制

select()的机制提供了fd_set的数据结构,它实际上是一个长类型的数组。每个数组元素都可以与一个打开的文件句柄(无论它是套接字句柄还是其他文件或命名管道或设备的句柄)连接。连接是由程序员完成的。当调用select()时,内核根据IO状态修改fd_set的内容,从而通知执行。

从流程来看,使用select函数进行IO请求和同步阻塞模型没有太大区别,甚至增加监控套接字和调用select函数的额外操作效率更低。然而,使用select后最大的优势是用户可以在一个线程中同时处理多个套接字。

IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

select机制的问题

每次调用select,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合很大时,那这个开销也很大同时每次调用select都需要在内核遍历传递进来的所有fd_set,如果fd_set集合很大时,那这个开销也很大为了减少数据拷贝带来的性能损坏,内核对被监控的fd_set集合大小做了限制,并且这个是通过宏控制的,大小不可改变(限制为1024)

Poll

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。也就是说,poll只解决了上面的问题3,并没有解决问题1,2的性能开销问题。

下面是pll的函数原型:

int poll(struct pollfd *fds, nfds_t nfds, int timeout); typedef struct pollfd { int fd; // 需要被检测或选择的文件描述符 short events; // 对文件描述符fd上感兴趣的事件 short revents; // 文件描述符fd上当前实际发生的事件 } pollfd_t;

poll改变了文件描述符集合的描述方式,使用了pollfd结构而不是select的fd_set结构,使得poll支持的文件描述符集合限制远大于select的1024

【参数说明】struct pollfd *fds fds是一个struct pollfd类型的数组,用于存放需要检测其状态的socket描述符,并且调用poll函数之后fds数组不会被清空;一个pollfd结构体表示一个被监视的文件描述符,通过传递fds指示 poll() 监视多个文件描述符。其中,结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域,结构体的revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域

nfds_t nfds 记录数组fds中描述符的总数量

【返回值】int 函数返回fds集合中就绪的读、写,或出错的描述符数量,返回0表示超时,返回-1表示出错;

Epoll

epoll在Linux2.6内核正式提出,是基于事件驱动的I/O方式,相对于select来说,epoll没有描述符个数限制,使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

Linux中提供的epoll相关函数如下:

int epoll_create(int 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);epoll_create 函数创建一个epoll句柄,参数size表明内核要监听的描述符数量。调用成功时返回一个epoll句柄描述符,失败时返回-1。epoll_ctl 函数注册要监听的事件类型。四个参数解释如下:epfd 表示epoll句柄op 表示fd操作类型,有如下3种EPOLL_CTL_ADD 注册新的fd到epfd中EPOLL_CTL_MOD 修改已注册的fd的监听事件EPOLL_CTL_DEL 从epfd中删除一个fdfd 是要监听的描述符event 表示要监听的事件epoll_event 结构体定义如下:struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t;epoll_wait 函数等待事件的就绪,成功时返回就绪的事件数目,调用失败时返回 -1,等待超时返回 0。epfd 是epoll句柄events 表示从内核得到的就绪事件集合maxevents 告诉内核events的大小timeout 表示等待的超时事件

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

水平触发(LT):默认工作模式,即当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次通知此事件边缘触发(ET): 当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。(直到你做了某些操作导致该描述符变成未就绪状态了,也就是说边缘触发只在状态由未就绪变为就绪时只通知一次)。

LT和ET原本应该是用于脉冲信号的,可能用它来解释更加形象。Level和Edge指的就是触发点,Level为只要处于水平,那么就一直触发,而Edge则为上升沿和下降沿的时候触发。比如:0->1 就是Edge,1->1 就是Level。

ET模式很大程度上减少了epoll事件的触发次数,因此效率比LT模式下高。

总结

一张图总结一下select,poll,epoll的区别:

select

poll

epoll

操作方式

遍历

遍历

回调

底层实现

数组

链表

哈希表

IO效率

每次调用都进行线性遍历,时间复杂度为O(n)

每次调用都进行线性遍历,时间复杂度为O(n)

事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面,时间复杂度O(1)

最大连接数

1024(x86)或2048(x64)

无上限

无上限

fd拷贝

每次调用select,都需要把fd集合从用户态拷贝到内核态

每次调用poll,都需要把fd集合从用户态拷贝到内核态

调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝

epoll是Linux目前大规模网络并发程序开发的首选模型。在绝大多数情况下性能远超select和poll。目前流行的高性能web服务器Nginx正式依赖于epoll提供的高效网络套接字轮询服务。但是,在并发连接不高的情况下,多线程+阻塞I/O方式可能性能更好。

推荐几款用SublimeText开发Laravel所用到的插件支撑阿里“双十一”的消息中间件Vue面试题实例分析#黑五#CloudCone:黑色星期五闪购正式开始IOS开发之手势响应事件优先级的实例详解超多低价VPS套餐服务节点的操作指南每小时更新一次
struct函数(c语言绝对值函数) c语言程序100例(c语言基础代码)
相关内容