四、poll()、select()和epoll()

在用户程序中,poll()和select()系统调用用于对设备进行无阻塞访问。poll()和select()最终会调用设备驱动中的poll()函数,在我所使用的Linux内核中,还有扩展的poll()函数epoll()

 

 

一、poll()函数

应用程序中的poll()函数原型为:

#include <poll.h>

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

poll()函数中参数fds用于描述监听的文件描述符集,nfds表示fds的数量,timeout表示监听超时时间,示例代码如下:

 1 struct pollfd fdsa[1];
 2 
 3 fdsa[0].fd        = fd;        /* 监听fd */
 4 fdsa[0].events    = POLLIN;    /* 监听输入事件,除 */
 5 
 6 while(1) {
 7     /* 5000ms内若有输入,返回大于0的数;否则返回0 */
 8     ret = poll(&fdsa[0], 1, 5000);
 9     if (!ret)
10         printf("time out\n");
11     else {
12         read(fd, buf, 1);
13         printf("buf = %d\n", buf[0]);
14     }
15 }

现在,我们来看看poll()函数的调用过程:

SYSCALL_DEFINE5(select, ...)
  -> core_sys_select(n, inp, outp, exp, to);
    -> do_select(n, &fds, end_time);    // 
      -> poll_initwait(&table);    // 初始化等待队列
      -> mask = (*f_op->poll)(f.file, wait);    // 执行驱动程序的poll()函数

二、select()函数

应用程序中的select()函数原型为:

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

select()函数中参数nfs表示监听所有的fd的最大值 + 1;readfds、writefds和exceptfds分别是被监听的读、写和异常的文件描述符集;timeout表示监听超时时间,结构体如下:

struct timeval {
    __kernel_time_t        tv_sec;        /**/
    __kernel_suseconds_t    tv_usec;    /* 微秒 */
};

FD_SET()、FD_ZERO()、FD_CLR()、FD_ISSET()分别用于加入fd、清除fd集合、清除fd、判断fd是否被加入集合中

示例代码如下:

 1 fd_set rfds;
 2 
 3 FD_ZERO(&rfds);
 4 FD_SET(fd, &rfds);
 5 
 6 tv.tv_sec = 5;
 7 tv.tv_usec = 0;    // 设置等待时间5s
 8 
 9 ret = select(fd + 1, &rfds, NULL, NULL, &tv);
10 
11 if (ret > 0) {
12     if(FD_ISSET(fd, &rfds))    /* 测试是否有数据 */ {
13         read(fd, buf, 1);
14         printf("buf = %d\n", buf[0]);
15     }
16 }

select()函数的调用过程:

SYSCALL_DEFINE3(poll, ...)
  -> do_sys_poll(ufds, nfds, to);
    -> poll_initwait(&table);    // 初始化等待队列
    -> do_poll(nfds, head, &table, end_time);
      -> do_pollfd(pfd, pt, &can_busy_loop, busy_flag);    // 处理进程的每一个fd的poll操作
        -> f.file->f_op->poll(f.file, pwait);    // 执行驱动程序的poll()函数

当poll()和select()的文件数量庞大、I/O流量频繁时,poll()和select()的性能表现较差,我们宜使用epoll(),epoll()不会随着fd的数目增长而降低效率

三、epoll()函数

epoll()函数原型为:

#include <sys/epoll.h>

/* 创建epoll文件描述符 */
int epoll_create(int size);

/* 添加、修改或删除需要监听的文件描述符及其事件 */
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

/* 等待被监听的描述符的I/O事件 */
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

代码中maxevents表示每次能处理的事件数

代码中的struct epoll_event声明为:

struct epoll_event {  
    uint32_t events;    /* epoll事件 */  
    epoll_data_t data;    /* epoll数据 */
};

typedef union epoll_data {  
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

示例代码如下:

 1 int efd, nfds, i;
 2 struct epoll_event event;
 3 
 4 efd = epoll_create(256);
 5 if (efd == -1) {
 6     return -1;
 7 }
 8 
 9 event.data.fd = fd;
10 /* 
11  * EPOLLIN: 表示对应的文件描述符可以读
12  * EPOLLOUT: 表示对应的文件描述符可以写
13  * EPOLLET: 表示对应的文件描述符有事件发生
14  */
15 event.events  = EPOLLIN | EPOLLET;
16 
17 /* 除EPOLL_CTL_ADD之外还有EPOLL_CTL_DEL(删除)和EPOLL_CTL_MOD(修改) */
18 s = epoll_ctl(efd, EPOLL_CTL_ADD, fd, &event);
19 
20 while (1) {
21     nfds = epoll_wait(epfd, event, 20, 500);
22     
23     for (i = 0; i < nfds; ++i) {
24         if (event[i].events & EPOLLIN) /* 有数据可读 */ {
25             read(event[i].data.fd, buf, 1);
26             printf("buf = %d\n", buf[0]);
27         }
28     }
29 }

epoll()系列函数的调用过程:

/* epoll_create() */
SYSCALL_DEFINE1(epoll_create, int, size)
  -> sys_epoll_create1(0);
    -> evetpoll_init();

/* epoll_ctl() */
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
        struct epoll_event __user *, event)
  -> case EPOLL_CTL_ADD:
  -> ep_insert(ep, &epds, tfile, fd);
    -> tfile->f_op->poll(tfile, &epq.pt);    /* 调用驱动的poll()函数 */

/* epoll_wait() */
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
        int, maxevents, int, timeout)
  -> ep_poll(ep, events, maxevents, timeout);
    -> 判断timeout

四、poll()、select()和epoll()的区别

五、驱动程序的poll()函数

poll()函数需要#include <linux/poll.h>

为了更方便演示poll()函数,我在代码中加入了一个全局变量ev_press,如果有按键按下置1;然后重新置0

static struct file_operations key_fops = {
    .owner    = THIS_MODULE,
    .read    = key_read,
    .poll    = key_poll,        /* 加入poll()函数 */
    .open        = key_open,
    .release    = key_release,
};

poll()函数示例如下:

static unsigned int key_poll(struct file *filp, struct poll_table_struct *table)
{
    struct key_device *dev = filp->private_data;

    unsigned int mask = 0;

    poll_wait(filp, &dev->r_head, table);

    if (ev_press)
        mask |= POLLIN | POLLRDNORM;

    return mask;
}

代码中的poll_wait()并不会像wait_event()系列函数一样阻塞地等待事件发生,poll_wait()并不会引起阻塞。它只是把当前进程加入到poll_table_struct等待列表

点击查看:源代码

猜你喜欢

转载自www.cnblogs.com/Lioker/p/10849612.html