<干货来袭系列>之一:select poll epoll

好久没有写文章,好久没有总结自己的学习了。最近,会带了一系列干货,总结过去,勉励以后。废话不多说,先来一系列I/O复用浅析。


先思考一个问题:你觉得I/O复用有什么好处?有没有用过select、poll、epoll?epoll为什么比较好?

活跃你的思维,想一想,如果是你,你会怎么回答这几个问题?


那么,我想先说一下,什么是I/O多路复用机制?


所谓I/O多路复用机制就是说通过一种机制,可以监视多个文件描述符,一旦某个文件描述符就绪,能够通知程序进行相应的读写操作。(I/O复用虽然能同时监听多个文件描述符,但是它本身却是阻塞的。并且当一个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依次处理其中的每个文件描述符,这样就相当于服务端工作方式为串行的;所以必须采用多进程或多线程来实现并发。)那么,我选择其中的select、poll、epoll三组I/O复用系统调用进行解说。

首先,说说select

select系统调用的用途:在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写、异常事件。(摘自《Linux高性能服务器编程》)

#include <sys/select.h>
int select(int nfds, fd_set* readfds, fd_set* writefds,fd_set* exceptfds, struct timeval *timeout);
       这是select的函数原型,关于返回值,select成功时返回就绪(可读、可写、异常)文件描述符的总数;如果在超时时间内没有任何文件描述符就绪,select将返回0;失败返回-1并设置errno。如果在select等待期间,程序接收到信号,则select立即返回-1,并设置errno为EINTR。

参数:

           ndfs指定被监听的文件描述符的总数(int);

           readfds、writefds、exceptfds参数分别指向可读、可写、异常时间对应的文件描述符集合,其中fd_set结构体包含一个整型数组,该数组的每个元素的每1位标记一个文件描述符,并且fd_set能容纳的文件描述符数量由FD_SIZE指定,所以说select所能同时处理的文件描述符总量是有一定限制的(默认1024);

            timeout参数用来设置select函数的超时时间,类型是一个timeval类型的指针,如果给timeout变量的tv_sec成员和tv_usec成员都传递0,则sleect立即返回;如果给timeout传递NULL,则select将一直阻塞,直到某个文件描述符就绪。


那么,select实现同时监听多个文件描述符效果真的很好吗,它真的能够提供效率吗?

答案是否定的,select也有一些缺点:

<1>每次调用select,都需要将fd集合从用户态拷贝到内核态,这个开销在fd很多时也很大;

<2>每次调用select,都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大;

<3>select支持的文件描述符数量太小了,默认的是1024


再说说poll

poll系统调用和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。

#include <poll.h>
int poll(struct pollfd*  fds, nfds_t  nfds,  int timeout);
这是poll的函数原型,关于返回值的含义与select相同。

参数:

fds参数是一个pollfd结构类型的数组,struct pollfd{int  fd;/*文件描述符*/  short  events; /*注册的事件*/     short   revents; /*实际发生的事件由内核填充*/};

nfds参数指定被监听集合fds的大小;

timeout参数指定poll的超时值;当timeout为-1时,poll调用将永远阻塞,直到某个事件的发生;当timeout为0时,poll调用将立即返回。


事实上,poll和select没有什么大的区别,最主要的区别就是poll只有一个事件集参数,统一处理所有事件类型;其次,poll所最大支持的文件描述符数是65535(目前没有验证,内容来自《Linux高性能服务器编程》)


虽然说poll支持的文件描述符的数目有所增加,但是,,,,它的效率还是不能满足编程人员的需求啊。。。。


接下来就是,epoll的引入(底层实现是“mmap、红黑树、链表”)

epoll原型如下:

#include<sys/epoll.h>
int  epoll_create(int  size);                          //创建文件描述符,标识内核中的事件列表

该函数返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定要访问的内核事件表。(创建好后,会占用一个fd的值,所以在使用完epoll后,必须调用close关闭,否则可能导致df被耗尽

参数:

size现在并不起作用,只是给内核一个提示,告诉它事件表需要多大

#include<sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);           //操作epoll的内核事件表
epoll_ctl函数调用成功时返回0,失败则返回-1并设置errno。(不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是先注册要监听的事件类型)

参数:

epfd参数是epoll_create函数的返回值;

fd参数是要操作的文件描述符;

op参数则指定操作类型(EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL);

event参数指定事件;它是epoll_event结构指针类型


其中epoll_event结构体定义如下:

struct epoll_event{
    __uint21_t   events;               //epoll事件
    epoll_data_t  data;                //用户数据
};
typedef <span style="color:#ff0000;">union</span> epoll{                    //公用体
    void *ptr;                        //通用指针类型,也就意味着ptr的类型可以由使用者定义
    int fd;                           //使用最多,指定事件所从属的目标文件描述符
    uint32_t  u32;                    //typedef unsigned int   uint32_t;   用来保证平台的通用性,增加可移植性 
    uint64_t  u64;                   //同上
}epoll_data_t;

一个fd被添加到epoll中之后(EPOLL_ADD),内核会为它生成一个对应的epitem结构对象,epitem被添加到epoll_event的红黑树中。(红黑树的作用是使用者调用EPOLL_MOD操作时能快速找到fd对应的epitem)。

通过调用epoll_ctl函数添加进来的事件都会被放在红黑树的某个结点上,事件被添加进来后都会与相应的设备驱动建立回调函数,当相应的事件发生后,就会调用这个回调函数(ep_poll_back回调函数,就是将事件添加到事件链表中)


#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_struct* events, int maxevents, int timeout);
epoll_wait用于收集在epoll监控的事件中已经发送的事件,该函数成功时返回就绪的文件描述符个数,失败时返回-1并设置errno。

参数:

event参数用来存放事件内容,maxevents参数指定最多监听多少个事件,它必须大于0;

epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表中复制到它的第二个参数events指向的数组中,由于这个数组只用于输出epoll_wait检测到的就绪事件,不用理会传入用户注册的事件,所以应用程序索引就绪文件描述符的效率有了很大的提高。采用回调的方式,内核检测到就绪文件描述符时,将触发回调函数,回调函数就将该文件描述符上对应的事件插入到内核就绪事件队列。内核最后在适当的时机将就绪事件队列中的内容拷贝到用户空间。

epoll的优点:(epoll_wait适用于连接数量多,但活跃连接较少的情况;当活跃连接数量较多时,效率未必比)

<1>支持一个进程打开大数目的socket描述符;

<2>IO效率不随fd数目的增加而线性下降;

<3>使用mmap加速内核与用户空间的消息队列,mmap内存映射技术省略掉了文件描述符在系统调用时的开销。(无论select、poll、epoll都需要内核把fd通知给用户空间,epoll通过内核与用户空间mmap同一块内存实现的,mmap将用户空间的一块虚拟地址和内核空间的一块虚拟地址同时映射到相同的一块物理内存地址,使得这块物理内存对内核和用户均可见,减少用户态和内核态之间的数据交换,可避免不必要的内存拷贝)

与select和poll不同的是:epoll使用一组函数来完成任务,而不是单个函数;其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表;但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件列表;select和poll采用轮询的方式检测就绪事件,而epoll采用回调方式检测就绪事件。


最后,推荐这篇文章给大家:http://www.it165.net/os/html/201407/8871.html  个人觉得写的很赞!


select和poll只能工作在相对低效的LT模式,而epoll则可以工作在ET高效模式。可能有人会问:什么是低效的LT模式,什么又是ET的高效模式?他们有什么不同?有什么使用场合呢?


那么接下来会为大家带来:

<干货来袭系列>之二:epoll的两种工作方式:LT   ET

敬请期待~

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值