您的位置:首页 > 其它

select poll epoll使用示例

2016-03-17 19:59 357 查看
select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

这三个方法都要调用驱动中的struct file_operations中的.poll方法。poll是一个函数指针,申明如下

unsigned int (*poll) (struct file *, struct poll_table_struct *);

在驱动中poll的实现方法如下:

.....

static unsigned int Test_poll(struct file *filp, poll_table *wait)

{

        unsigned int mask = 0;

        ......... 

        poll_wait(filp, &(int_queue), wait);

        if(read ready)

        { 

                mask |=POLLIN | POLLRDNORM;

        }

        if(write ready)

        { 

                mask |=return POLLOUT | POLLWRNORM;

        }

        ............

        return  mask ;

}

static const struct file_operations Test_fops = {

        .owner = THIS_MODULE,

        .......

        .poll = Test_poll ,

        .......

};

注意:.poll本身是不会阻塞的,不论读写条件是否满足,都会立即返回。阻塞是在调用 .poll的时候实现的,poll返回0的时候,将阻塞,等待int_queue。

int_queue是一个 类型为wait_queue_head_t结构的等待队列。

在驱动初始化的时候调用init_waitqueue_head(&(int_queue));初始化等待队列

当读写条件满足的时候,调用wake_up_interruptible(&( int_queue));唤醒等待队列

poll方法返回的数据如下:

常量             说明

POLLIN           普通或优先级数据可读

POLLRDNORM       普通数据可读

POLLRDBAND       优先级数据可读

POLLPRI          高优先级数据可读

POLLOUT          普通数据可写

POLLWRNORM       普通数据可写

POLLWRBAND       优先级数据可写

POLLERR          发生错误

POLLHUP          发生挂起

POLLNVAL         描述字不是一个打开的文件

1 select的实现示例

  select相关函数原型如下:

  

  int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout);

  maxfd是需要监视的最大的文件描述符值+1,例如如果我们分别要监视3个文件描述符fd1 fd2 fd3 ,数值分别为100,500,1000,那么maxfd就必须写1001。

  rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合。

  timeout 为设置的超时时间,如果为NULL,则一直等待,直到监视的资源可用。

  函数返回值:小于0 函数执行出错 ,超时将返回0 。大于0 表示有资源可用的数目,例如我们分别监视了fd1 fd2 fd3 那么如果fd1 发的同时可以写了则返回2。

  对fd_set的操作可以通过下面的宏来操作:

  FD_ZERO(fd_set*); 用来清空fd_set集合,即让fd_set集合不再包含任何文件句柄。

  FD_SET(int ,fd_set *); 用来将一个给定的文件描述符加入fd_set集合之中

  FD_CLR(int ,fd_set*); 用来将一个给定的文件描述符从集合中删除

  FD_ISSET(int ,fd_set*);检测fd在fdset集合中是否存在,存在返回真,否则,返回假(也可以认为集合中指定的文件描述符是否可以读写)。

  这里需要注意的是select执行的时候,rdset、wrset、exset、timeout、会被改变,所以每次执行select前一定要重新设置下。例如rdset分别监视fd1 fd2 fd3,当fd3可以读都,select返回,那么这个时候rdset就只设置了fd3,如果我们没有重新设置,那fd1 fd2就监视不到了。同样timeout也是一样,当超时返回后,timeout变为0,如果这个时候没有重新设置,那么select函数就会立即返回了,无论是否资源可用。

  下面是示例代码:

void * Test_select( void*  arg )

{

        fd_set fdsi;

        fd_set fdso;

        int maxfdp ;

        int ret;

        struct timeval timeout;

        int fd ,fd2;

        fd = open(DEV_FILENAME, O_RDWR  );

        if(fd<0) 

        {

                printf("open device error\r\n");

                return NULL ;

        }

        fd2 = open(DEV_FILENAME2, O_RDWR  );

        if(fd<0) 

        {

                printf("open device error\r\n");

        

                return NULL ;

        }

        maxfdp = fd>fd2?fd+1:fd2+1 ;

        while(exitFlag==0)

        {

                timeout.tv_sec=1;//1秒超时

                timeout.tv_usec = 0;

                FD_ZERO(&fdsi); 

                FD_SET(fd,&fdsi);

                FD_SET(fd2,&fdsi);

                

                FD_ZERO(&fdso); 

                FD_SET(fd,&fdso);

                FD_SET(fd2,&fdso); 

                

                ret = select(maxfdp,&fdsi,&fdso,NULL,&timeout);

                  //如果不需要超时可以改为  ret = select(maxfdp,&fdsi,&fdso,NULL,NULL);

                if(ret==0)

                {

                        printf("select timeout\r\n");

                        continue;

                }

                else if(ret<0)

                {

                        printf("select error\r\n");

                        exitFlag= 1;

                }

                else

                {

             

                        if(FD_ISSET(fd,&fdsi)) 

                        {

                           //read data;

                        }

                        else if(FD_ISSET(fd,&fdso)) 

                        {

                           //write data;

                        }

                        

                        if(FD_ISSET(fd2,&fdsi)) 

                        {

                           //read data;

                        }

                        else if(FD_ISSET(fd2,&fdso)) 

                        {

                           //write data;

                        }

                }

        }

        close(fd );

        close(fd2 );

        return NULL;

}

说明:在系统内核中select是采用轮询来处理的,如果需要监视的文件描述符越多,就需要消耗更多的资源。所以如果要监视很多文件描述符,最好使用epoll(epoll在后面会介绍)。

select监视的最大数量有限制,通常是1024。

2 poll的实现示例

  poll实现比select简单,只有一个函数就可以了,函数原型如下

  int poll (struct pollfd *fds, nfds_t nfds, int timeout);

  参数fds :pollfd 结构体的指针,这个结构描述了需要监视的文件描述符和我们关心的事件。这个函数也可以监视多个文件描述符。

            struct pollfd结构描述如下

  struct pollfd

  {

                int fd;                     /* poll 的文件描述符.  */

                short int events;           /* fd 上感兴趣的事件(需要等待的事件).  */

                short int revents;          /* fd 上实际发生的事件. 也就是poll返回时 的状态*/

  }; 

  参数 nfds:fds的个数,也就是要监视的文件描述符的个数。

  参数 timeout : 超时的毫秒数,如果为-1则表示永远等待。

  函数返回值 返回0表示超时 返回值小于0函数出错,大于0 实际可用的资源数

  下面是示例代码:  

void * Test_poll( void*  arg )

{

        struct pollfd Events[2] ;

        int ret;

        int fd ,fd2;

        fd = open(DEV_FILENAME, O_RDWR  );

        if(fd<0) 

        {

                printf("open device error\r\n");

                return NULL ;

        }

        fd2 = open(DEV_FILENAME2, O_RDWR  );

        if(fd<0) 

        {

                printf("open device error\r\n");

                return NULL ;

        }

        Events[0].fd = fd;

        Events[0].events = POLLIN | POLLERR;     /*关心读取和出错事件*/

        

        Events[1].fd = fd2;

        Events[1].events = POLLIN | POLLERR;     /*关心读取和出错事件*/

        while(exitFlag==0)

        {

                ret = poll ((struct pollfd *)&Events[0], 2, -1);//如果要设置超时可以把-1改为要设置的超时的毫秒数

                if(ret==0)

                {

                        printf("poll timeout\r\n");

                        continue;

                }

                else if(ret<0)

                {

                        printf("poll error\r\n");

                        exitFlag= 1;

                }

                else

                {

                        if (Events[0].revents & POLLERR) 

                        {

                                printf ("device error!\n");              

                        }

                        if (Events[1].revents & POLLERR) 

                        {

                                printf ("device error!\n");              

                        }

                                 

                        if (Events[0].revents & POLLIN) {

                                //read data;              

                        } 

                    

                        if (Events[1].revents & POLLIN) {

                                //read data;              

                        } 

                }

        }

        close(fd );

        close(fd2 );

        return NULL;

}

说明:poll函数的实现方法和select类似,所以等待的文件描述符越多,越消耗资源。

3 epoll实现说明

  epoll实现很简单,就三个函数就解决了,下面是函数申明:

  int epoll_create(int size);

  这个函数的功能创建一个epoll的句柄,当创建好epoll句柄后,它就是会占用一个fd值所以使用完epoll后一定要close掉,避免占用资源。

  参数size:用来告诉内核这个监听的数目一共有多少。 自从Linux 2.6.8开始,size参数被忽略,但是依然要大于0。

  返回值:成功返回epoll的句柄,失败返回-1。

  int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

  参数 epfd: epoll的句柄,是epoll_create()的返回值

  参数 op:操作码。用三个宏来表示的

           EPOLL_CTL_ADD:注册新的fd到epfd中;

           EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

           EPOLL_CTL_DEL:从epfd中删除一个fd;

  参数 fd:要监视的文件描述符。

  参数event :要监视的事件的描述。

  struct epoll_event的结构说明如下:

  struct epoll_event {

          __uint32_t events; /* 需要监视的事件,如EPOLLIN、EPOLLOUT等等 */

          epoll_data_t data; /* 用户数据*/

  }; 

  typedef union epoll_data

  {

          void        *ptr;

          int          fd;

          __uint32_t   u32;

          __uint64_t   u64;

  } epoll_data_t;

  返回值:0成功,小于0 失败

  int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

  该函数等待事件产生。

  参数epfd: epoll的句柄

  参数events:从内核得到的事件集合

  参数maxevents:events的大小。events的大小取决于可能同时产生的事件的数量。例如我们虽然同时监视了fd1 fd2 fd3 三个文件描述符,但是这3个之间是不可能同时可读或者可写的,那么将maxevents设置为1也没有问题,但是如果三个事件可能会同时产生,那么最好就设置为3。如果设置为1,3个事件同时产生,epoll_wait第一次返回的时候就会只返回第一事件了。(不过在此调用这个函数的时候,会马上返回。例如当fd1 fd2 fd3同时可读,但是maxevents为1时,那么第一次调用epoll_wait返回值为1,报告fd1的时间,第二次调用,马上返回1,调用fd2的事件,第三次调用马上返回1,报告fd3的事件.而如果maxevents为3的话,当fd1
fd2 fd3同时可读,epoll_wait返回值为3,同时报告fd1 fd2 fd3的事件)。

  参数timeout:超时的毫秒数,如果为-1表示一直等待直到事件产生。

  返回值:超时返回0 ,返回小于0 函数执行失败,大于0,返回值为实际的事件数目。

下面是示例代码:

static int epoll_register( int  epoll_fd, int  fd )

{

        struct epoll_event  ev;

        int                 ret, flags;

        /*****************************************/ 

        flags = fcntl(fd, F_GETFL);

        fcntl(fd, F_SETFL, flags | O_NONBLOCK);

        //资料上说需要设置为非阻塞的方式,我试了下,似乎不设置为非阻塞的方式也不会出错

        /**********************************************/

        ev.events  = EPOLLIN;

        ev.data.fd = fd; 

        ret = epoll_ctl( epoll_fd, EPOLL_CTL_ADD, fd, &ev );  

        return ret;

}

void * Test_epoll( void*  arg )

{

        int   epoll_fd  ,ret ;

        int count =0;

        struct epoll_event   events[2];

        int                  ne, nevents; 

        int fd ,fd2;

        fd = open(DEV_FILENAME, O_RDWR  );

        if(fd<0) 

        {

                printf("open device error\r\n");           

                return NULL ;

        }

        fd2 = open(DEV_FILENAME2, O_RDWR  );

        if(fd<0) 

        {

                printf("open device error\r\n");              

                return NULL ;

        } 

        epoll_fd = epoll_create(1);

        epoll_register( epoll_fd, fd );

        epoll_register( epoll_fd, fd2 );

        while(exitFlag==0)

        {

                nevents = epoll_wait( epoll_fd, events, 2, -1 );

                if (nevents < 0) 

                {

                        exitFlag =1;

                        printf("epoll_wait() unexpected error: %s", strerror(errno));

                }

                for (ne = 0; ne < nevents; ne++) 

                {

                        if ((events[ne].events & (EPOLLERR|EPOLLHUP)) != 0) 

                        {

                             

                        }

                        if ((events[ne].events & EPOLLIN) != 0) 

                        {

                               if( fd == events[ne].data.fd)

                               {

                                       //read data;

                               }

                               else if( fd2 == events[ne].data.fd)

                               {

                                       //read data;

                               }

                        }

                }

        }

        epoll_ctl( epoll_fd, EPOLL_CTL_DEL, fd, NULL ); 

        epoll_ctl( epoll_fd, EPOLL_CTL_DEL, fd2, NULL ); 

        ret=close(epoll_fd);

         

        return NULL;

}

最后总结:

(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内部定义的等待队列)。这也能节省不少的开销。

所以最好尽量使用epoll。他的速度更快,系统开销更小。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: