在用户程序中,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等待列表
点击查看:源代码