应用程序中的轮询编程(多路复用IO)
应用层提供的轮询(多路复用IO)的接口有三个,分别是poll、select、epoll,这三个函数对应了驱动file_operations结构体中的函数成员:
__poll_t (*poll) (struct file *, struct poll_table_struct *);
poll函数的典型模板;
staticunsignedintxxx_poll(struct file *filp, poll_table *wait)
{
unsignedint mask = 0;
structxxx_dev *dev = filp->private_data; /* 获得设备结构体指针 */
...
poll_wait(filp, &dev->r_wait, wait); /* 加入读等待队列 */
poll_wait(filp, &dev->w_wait, wait); /* 加入写等待队列 */
if (...) /* 可读 */
mask |= POLLIN | POLLRDNORM; /* 标示数据可获得(对用户可读) */
if (...) /* 可写 */
mask |= POLLOUT | POLLWRNORM; /* 标示数据可写入 */
...
return mask;
}
1、select
函数原型:
intselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
readfds 可以设置为NULL,表示不关心任何文件的读变化,另外两个同理。
超时时间的结构体原型:
structtimeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微妙 */
};
系统提供的操作fd_set类型变量的函数:
voidFD_ZERO(fd_set *set)//清除文件描述符集合
voidFD_SET(int fd, fd_set *set)//将一个文件描述符加入文件描述符集合中
voidFD_CLR(int fd, fd_set *set)//将一个文件描述符从文件描述符集合中清除
intFD_ISSET(int fd, fd_set *set)//判断文件描述符是否被置位
voidmain(void)
{
int ret, fd; /* 要监视的文件描述符 */
fd_set readfds; /* 读操作文件描述符集 */
structtimevaltimeout;/* 超时结构体 */
fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
FD_ZERO(&readfds); /* 清除 readfds */
FD_SET(fd, &readfds); /* 将 fd 添加到 readfds 里面 */
/* 构造超时时间 */
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500ms */
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
switch (ret) {
case0: /* 超时 */
printf("timeout!\r\n");
break;
case-1: /* 错误 */
printf("error!\r\n");
break;
default: /* 可以读取数据 */
if(FD_ISSET(fd, &readfds)) { /* 判断是否为 fd 文件描述符 */
/* 使用 read 函数读取数据 */
}
break;
}
}
2、poll
与select功能类似。不同地方在于select在单个线程中一般能够监视的最大文件描述符数量为1024,而poll没有这个限制。
函数原型:
intpoll(struct pollfd *fds, nfds_t nfds, int timeout)
fds - 要监视的文件描述符以及要监视的事件,fd后面加s表示一个数组。
结构体原型:
structpollfd {
int fd; /* 文件描述符 */
short events; /* 请求的事件 */
short revents; /* 返回的事件 */
};
//events可以是以下事件类型:
// POLLIN 有数据可以读取。
// POLLPRI 有紧急的数据需要读取。
// POLLOUT 可以写数据。
// POLLERR 指定的文件描述符发生错误。
// POLLHUP 指定的文件描述符挂起。
// POLLNVAL 无效的请求。
// POLLRDNORM 等同于 POLLIN
nfds - 监视的文件描述符的数量,也就是fds数组的大小。
timeout - 超时时间
#include<poll.h>
#include<stdio.h>
#include<unistd.h>
intmain(int argc, char **argv)
{
structpollfdfds;
int timeout_ms = 5000;
int ret;
char buf[20];
fds.fd = 0; //监视标准输入 (0文件描述符代表标准输入)
fds.events = POLLIN;
ret = poll(&fds, 1, timeout_ms);
if (ret == 1 && (fds.events & POLLIN))
{
read(0, buf, sizeof(buf));
printf("get : %s\n", buf);
}
printf("AABBB\r\n");
return0;
}
3、epoll
当多路复用IO文件数量庞大,IO流量频繁的时候,而select和poll函数都是会遍历所监视的所有文件描述符,select和poll性能都会下降,而epoll的性能不会随着fd的增长而降低效率。
函数接口:
intepoll_create(int size);
//创建epoll句柄,创建完本身会占据一个fd,所以适用完后必须调用close关闭
//size - 从 Linux2.6.8 开始此参数已经没有意义了,随便填写一个大于 0 的值就可以。
//返回值: -1表示创建失败
intepoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//添加要监视的文件描述符和事件
//epfd - epoll句柄
//op - 对句柄的操作,可以是以下值:
// EPOLL_CTL_ADD 向 epfd 添加文件参数 fd 表示的描述符。
// EPOLL_CTL_MOD 修改参数 fd 的 event 事件。
// EPOLL_CTL_DEL 从 epfd 中删除 fd 描述符。
//fd - 要监视的文件描述符
//event - 监视的事件,可选以下值:
// EPOLLIN 有数据可以读取。
// EPOLLOUT 可以写数据。
// EPOLLPRI 有紧急的数据需要读取。
// EPOLLERR 指定的文件描述符发生错误。
// EPOLLHUP 指定的文件描述符挂起。
// EPOLLET 设置 epoll 为边沿触发,默认触发模式为水平触发。
// EPOLLONESHOT 一次性的监视,当监视完成以后还需要再次监视某个 fd,那么就需要将fd 重新添加到 epoll 里面。
驱动中的轮询编程
1、poll()函数原型
unsignedint(*poll)(struct file * filp, struct poll_table* wait);
2、poll_wait()
该函数不会引起阻塞,作用将参数queue对应的等待队列上休眠的进程唤醒
voidpoll_wait(struct file *filp, wait_queue_heat_t *queue, poll_table * wait);
3、poll()函数模板
staticunsignedintxxx_poll(struct file *filp, poll_table *wait)
{
unsignedint mask = 0;
structxxx_dev *dev = filp->private_data; /* 获得设备结构体指针 */
...
poll_wait(filp, &dev->r_wait, wait); /* 加入读等待队列 */
poll_wait(filp, &dev->w_wait, wait); /* 加入写等待队列 */
if (...) /* 可读 */
mask |= POLLIN | POLLRDNORM; /* 标示数据可获得(对用户可读) */
if (...) /* 可写 */
mask |= POLLOUT | POLLWRNORM; /* 标示数据可写入 */
...
return mask;
}
POLLIN 有数据可以读取。
POLLPRI 有紧急的数据需要读取。
POLLOUT 可以写数据。
POLLERR 指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起。
POLLNVAL 无效的请求。
POLLRDNORM 等同于 POLLIN,普通数据可读