1.poll 和 select
Linux系统提供了多路IO复用机制,通过select、poll、epoll接口实现,前提是该驱动支持poll。这样同一个进程可以监听多个文件描述符,并及时对资源可用的文件描述符处理。支持任何一个这些调用都需要来自设备驱动的支持. 这个支持(对所有 3 个调用)由驱动的 poll 方法调用. 这个方法由下列的原型:
#include <linux/poll.h>
unsigned int (*poll) (struct file *filp, poll_table *wait);
这个驱动方法被调用, 无论何时用户空间程序进行一个 poll, select, 或者 epoll 系统调用, 涉及一个和驱动相关的文件描述符. 这个设备方法负责这 2 步:
1.1 调用 poll_wait
在一个或多个可指示查询状态变化的等待队列上调用 poll_wait。 如果没有文件描述符可用作 I/O, 内核使这个进程在等待队列上等待所有的传递给系统调用的文件描述符。
poll 方法的第 2 个参数poll_table 结构,在内核中用来实现 poll, select, 和 epoll 调用。它被传递给驱动以便驱动可用每个能唤醒进程的等待队列来加载它, 并且可改变 poll 操作状态. 驱动增加一个等待队列到 poll_table 结构通过调用函数 poll_wait:
void poll_wait (struct file *, wait_queue_head_t *, poll_table *);
1.2 返回位掩码
poll 方法的第 2 个任务是返回位掩码, 它描述哪个操作可马上被实现。例如, 如果设备有数据可用, 对应的读操作进程会从等待队列中唤醒。常用掩码如下:
POLLIN //如果设备可被不阻塞地读, 这个位必须设置.
POLLRDNORM //这个位必须设置, 如果"正常"数据可用来读. 一个可读的设备返回( POLLIN|POLLRDNORM ).
POLLRDBAND //这个位指示带外数据可用来从设备中读取. 当前只用在 Linux 内核的一个地方( DECnet 代码 )并且通常对设备驱动不可用.
POLLPRI //高优先级数据(带外)可不阻塞地读取. 这个位使 select 报告在文件上遇到一个异常情况, 因为 selct 报告带外数据作为一个异常情况.
POLLHUP //当读这个设备的进程见到文件尾, 驱动必须设置 POLLUP(hang-up). 一个调用 select 的进程被告知设备是可读的, 如同 selcet 功能所规定的.
POLLERR //一个错误情况已在设备上发生. 当调用 poll, 设备被报告位可读可写, 因为读写都返回一个错误码而不阻塞.
POLLOUT //这个位在返回值中设置, 如果设备可被写入而不阻塞.
POLLWRNORM //这个位和 POLLOUT 有相同的含义, 并且有时它确实是相同的数. 一个可写的设备返回( POLLOUT|POLLWRNORM).
POLLWRBAND //如同 POLLRDBAND , 这个位意思是带有零优先级的数据可写入设备. 只有 poll 的数据报实现使用这个位, 因为一个数据报看传送带外数据.
应当重复一下 POLLRDBAND 和 POLLWRBAND 仅仅对关联到 socket 的文件描述符有意义: 通常设备驱动不使用这些标志.
2.驱动实现
本驱动是在 Linux设备驱动——阻塞型I/O基础上开发的,read和write方法已经实现不再这里列出,下面列出poll方法:
static unsigned int my_fifo_poll(struct file *filp, poll_table *wait)
{
struct fifo_dev *dev = filp->private_data;
unsigned int mask = 0;
/*
* The buffer is circular; it is considered full
* if "wp" is right behind "rp" and empty if the
* two are equal.
*/
down(&dev->sem);
poll_wait(filp, &dev->rd_qeue, wait);
poll_wait(filp, &dev->wr_qeue, wait);
if (dev->rd != dev->wt)
mask |= POLLIN | POLLRDNORM; /* readable */
if (get_fifo_remain_space(dev))
mask |= POLLOUT | POLLWRNORM; /* writable */
up(&dev->sem);
return mask;
}
将poll方法加入到struct file_operations中:
static struct file_operations char_driver_fops = {
......
.poll = my_fifo_poll,
};
3.应用层实现
头文件
#include <poll.h>
完整实现:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
int main(int argc, char *argv[])
{
int fd;
int ret;
char buf[128] = {0};
struct pollfd p_fds[5]; /* 最大监听5个句柄 */
int max_fd; /* 监听文件描述符中最大的文件号 */
fd = open("/dev/poll_select_dev", O_RDWR);
if (0 > fd) {
perror("can not open poll_select_dev");
return -1;
}
printf("open \"poll_select_dev\" success\n");
/* poll 监听参数 */
for (ret = 0; ret < 5; ret++) {
p_fds[ret].fd = -1;
}
p_fds[0].fd = fd;
p_fds[0].events = POLLIN | POLLRDNORM;
p_fds[0].revents = 0;
max_fd = 1;
while (1) {
ret = poll(p_fds,max_fd+1, -1); /* -1表示阻塞,不超时 */
if (ret < 0) {
perror("poll error");
close(fd);
}
if ((p_fds[0].revents & POLLIN) == POLLIN || (p_fds[0].revents & POLLRDNORM) == POLLRDNORM) {
read(fd, &ret, sizeof(int));
printf("read:%d\n", ret);
}
usleep(10000);
}
close(fd);
return 0;
}
完整代码路径:
https://github.com/zhaoxd298/Linux_drivers/tree/master/poll_select