select poll epoll总结

来源:互联网 发布:海量数据排序 编辑:程序博客网 时间:2024/06/06 16:51

I/O请求分两步

1、先将数据从存储介质(磁盘、网络等)拷贝到内核缓冲区,此时数据已准备好,可以被用户应用程序读取。
2、由用户应用程序拷贝内核缓冲区的数据到用户缓冲区。

duo和dup2函数

dup和dup2的作用都是用复制一个文件描述符。它们经常用来重定向进程的stdin,stdout和stderr。
两个函数的原型:
#include <unistd.h> 
 int dup( int oldfd ); 
int dup2( int oldfd, int targetfd );
dup()函数
利用函数dup可以复制描述符,传给该函数一个既有的描述符,它就会返回一个新的描述符,这个新的描述符是传给它的的描述符,这意味着,这两个描述符共享同一个数据结构。
需要注意的是:在调用fork之前建立一个描述符,这与调用dup建立描述符的效果是一样的,子进程同样会收到一个复制出来的描述符。
dup2()函数
dup2函数跟dup函数相似,但dup2函数允许调用者规定一个有效描述符和目的描述符的id。dup2函数成功返回时,目标描述符(dup2函数的第二个参数)将变成(dup2函数的第一个参数)的复制品,换句话说,两个文件描述符都指向同一个文件,并且是函数第一个参数指向的文件。
例子:
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h>   
int main() 
    int pfds[2]; 
    if ( pipe(pfds) == 0 ) {//创建管道
    if ( fork() == 0 ) {//子进程
        close(1);//关闭stdout描述符(也就是输出描述符)
        dup2( pfds[1], 1 );//重定向描述符把stdout重定向到管道(pfds[1])stdout变成管道的输入端(向管道写)
        close( pfds[0] );//关掉管道的输入端
        execlp( "ls", "ls", "-l", NULL ); 
    } else {//父进程
        close(0);//关闭stdin描述符(也就是输入描述符)
        dup2( pfds[0], 0 );//重定向描述符把stdin重定向到管道(pfds[0])stdin变成管道的输出端(从管道读)
        close( pfds[1] );//关闭管道的输出端
        execlp( "wc", "wc", "-l", NULL ); 
    } 
    return 0; 

select函数

系统提供select函数来实现多路复用输入/输出模型,select系统调用是用来让我们的程序监视多个文件句柄的状态变化。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态变化。
函数原型:
句柄:其实就是一个整数,最熟悉的句柄是0(标准输入)、1(标准输出)、2(标准错误)。
int select (int maxfdp,fd_set *readset,fd_set *writeset, fd_set *exceptset,const struct timeval * timeout);
参数一:最大文件描述符加1。
参数二:用于检查可读性。
参数三:用于检查可写性。
参数四:用于检查带外数据。
参数五:一个指向timeval结构的指针,用于决定select等待I/O的事时间。
readset writeset exceptset指定我们要让内核测试读、写、和异常条件的描述字,如果对某一个 不感兴趣就可以把它设为空。如果3个指针都为空,则就有了一个比sleep()函数更为精确的定时器(sleep是秒为单位,这个是以微妙为单位)。
struct timeval{
long tv_sec; // seconds
long tv_usec; // microseconds
}
如果参数timeout设为:
NULL:表示select函数没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件。
0: 一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值。
特定时间值:如果在指定时间段里没有事件发生,select将超时返回。
函数返回值:
成功则返回文件描述符状态改变的个数。
返回0代表在描述符状态改变前已超过timeout时间,没有返回。
错误发生时返回-1。
下面的宏提供了处理这三种描述词组的方式:
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set);用来清除描述词组set的全部位
select的优点:
1、它能够同时监视多个文件描述符,当select返回后,数组中就绪的fd中的标志位会被修改,用户进程需要判断哪个fd标志修改了,从而进行后续的读写操作。
2、select有良好的跨平台性,几乎支持所有的平台。
select的缺点:
1、每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时候会很大。
2、同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时候也很大。
3、select支持的文件描述符数量太小,默认是1024。
select参考博客:http://blog.csdn.net/xiaocherry1128/article/details/77836583

poll函数

poll和select的实现功能差不多,但poll的效率高,不同与select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现。
pollfd的结构:
struct pollfd{
int fd;//文件描述符
short events;//监视的事件
short revents;//实际发生的事件
};
pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递方式。同时pollfd没有最大数量的限制(但数量过大性能会下降)。和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。
函数返回值
当返回正值时,代表满足响应事件的文件描述符的个数,如果返回0则代表在规定事件内没有事件发生。如发现返回为负则应该立即查看 errno,因为这代表有错误发生,如果没有事件发生,revents会被清空。

epoll函数

epoll的相关调用
epoll只有epoll_create,epoll_ctl,epoll_wait三个系统调用。
1、int epoll_create(intsize);
创建一个epoll的句柄,创建好epoll句柄后,它就会占用一个fd值。使用epoll后,必须close()关闭,否则可能导致fd被耗尽。
2、int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
参数:
第一个参数:epoll_create()的返回值
第二个参数:表示动作。
                         用三个宏来表示:
                                        EPOLL_CTL_ADD:注册新的fd到epfd中。
                                        EPOLL_CTL_MOD:修改已经在注册的监听事件。
                                        EPOLL_CTL_DEL:从epfd中删除一个fd。
第三个参数:需要监听的fd。
第四个参数:一个指向epoll_event结构的指针,告诉内核需要监听什么事。
typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;
struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
}
events可以是以下几个宏的集合:
EOOLLIN:表示对应的文件描述符可以读(包括对端socket正常关闭)
EOPLLOUT:表示对应的文件描述符可以写。
EPOLLPRI:表示对应的文件描述符有紧急数据可读(应该是表示带外数据到来)。
EPOLLERR:表示对应的文件描述符发生错误。
EPOLLHUP:表示对应的文件描述符被挂断
EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,相对于水平触发(Level Targgered)来说的。
EPOLLONESHOT:只监听一次事件,监听完事件后,如果还需监听这个socket的话,需要再次把这个socket加入到EPOLL队列中。
3、int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
收集epoll监控的事件中已经发送的事件
参数:
epfd:epoll_create()的返回值。
events:分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个数组中,不会去帮助我们在用户态中分配内存)。
maxevents:告诉内核这个 events有多大,这个值不能大于创建epoll_create()时的size,
timeout:是超时 时间(毫秒,0会⽴立即返回,-1将不确定,也有说法说是永久阻塞)。
函数返回值:
如果函数调用成功, 返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。
epoll工作原理
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时, 返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一 个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这 样便彻底省掉了这些文件描述符在系统调用时复制的开销。 另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调 用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来 注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机 制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
epoll的优点
1、支持一个进程打开最大数目的socket描述符(select的fd数目太小)
2、I/O效率不随fd数目的增加而线性下降(select和poll效率会下降:select/poll每次调⽤用都会线性扫描全部的集合,导致效率呈现线性下降)
3、使用mmap加速内核与用户空间的消息传递。

总结:

(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
原创粉丝点击