第8章 Linux设备驱动中的阻塞与非阻塞I/O-轮询操作
8.2 轮询操作
8.2.1 轮询的概念与作用
在用户程序中,select()和poll()是与设备阻塞与非阻塞访问息息相关的论题。使用非阻塞I/O的应用程序通常会使用select()和poll()系统调用查询是否可对设备进行无阻塞的访问。select()和poll()系统调用使设备驱动中的poll()函数被执行。
8.2.2 应用程序中的轮询编程
一、
应用程序中最广泛用到的是BSD UNIX中引入的select()系统调用,其原型为:
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
其中readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集合,numfds的值是需要检查的号码最高的fd加1。
readfds文件集中的任何一个文件变得可读,select()返回;同理,writefds文件集中的任何一个文件变得可写,select也返回。
如图8.3所示,第一次对n个文件进行select()的时候,若任何一个文件满足要求,select()就直接返回;第2次再进行select()的时候,没有文件满足读写要求,select()的进程阻塞且睡眠。由于调用select()的时候,每个驱动的poll()接口都会被调用到,实际上执行select()的进程被挂到了每个驱动的等待队列上,可以被任何一个驱动唤醒。如果FDn变得可读写,select()返回。
图8.3 多路复用select()
timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout时间后若仍然没有文件描述符准备好则超时返回。struct timeval数据结构的定义如代码清单8.7所示。
代码清单8.7 timeval结构体定义
struct timeval {
int tv_sec; /* 秒 */
int tv_usec; /* 微秒 */
};
下列操作用来设置、清除、判断文件描述符集合:
1、
FD_ZERO(fd_set *set)
清除一个文件描述符集合;
2、
FD_SET(int fd,fd_set *set)
将一个文件描述符加入文件描述符集合中;
3、
FD_CLR(int fd,fd_set *set)
将一个文件描述符从文件描述符集合中清除;
4、
FD_ISSET(int fd,fd_set *set)
判断文件描述符是否被置位。
二、
poll()的功能和实现原理与select()相似,其函数原型为:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
当多路复用的文件数量庞大、I/O流量频繁的时候,一般不太适合使用select()和poll(),这种情况下,select()和poll()的性能表现较差,宜使用epoll。epoll的最大好处是不会随着fd的数目增长而降低效率,select()则会随着fd的数量增大性能下降明显。
8.2.3 设备驱动中的轮询编程
#include <linux/fs.h>
设备驱动中poll()函数的原型是:
unsigned int(*poll)(struct file * filp, struct poll_table* wait);
第1个参数为file结构体指针,第2个参数为轮询表指针。这个函数应该进行两项工作。
1)对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列头部添加到poll_table中。
2)返回表示是否能对设备进行无阻塞读、写访问的掩码。
#include <linux/poll.h>
向poll_table注册等待队列的关键poll_wait()函数的原型如下:
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p);
poll_wait()函数并不会引起阻塞。poll_wait()函数所做的工作是把当前进程添加到p参数指定的等待列表(poll_table)中,实际作用是让唤醒参数wait_address对应的等待队列可以唤醒因select()而睡眠的进程。
驱动程序poll()函数应该返回设备资源的可获取状态,即POLLIN、POLLOUT、POLLPRI、POLLERR、POLLNVAL等宏的位“或”结果。每个宏的含义都表明设备的一种状态,如POLLIN(定义为0x0001)意味着设备可以无阻塞地读,POLLOUT(定义为0x0004)意味着设备可以无阻塞地写。
poll()函数典型模板
1 static unsigned int xxx_poll(struct file *filp, poll_table *wait)
2 {
3 unsigned int mask = 0;
4 struct xxx_dev *dev = filp->private_data; /* 获得设备结构体指针*/
5
6 ...
7 poll_wait(filp, &dev->r_wait, wait); /* 加入读等待队列 */
8 poll_wait(filp, &dev->w_wait, wait); /* 加入写等待队列 */
9
10 if (...) /* 可读 */
11 mask |= POLLIN | POLLRDNORM; /* 标示数据可获得(对用户可读)*/
12
13 if (...) /* 可写 */
14 mask |= POLLOUT | POLLWRNORM; /* 标示数据可写入*/
15 ...
16 return mask;
17}