应用程序中的轮询编程(多路复用IO)
应用层提供的轮询(多路复用IO)的接口有三个,分别是poll、select、epoll,这三个函数对应了驱动file_operations结构体中的函数成员:
__poll_t (*poll) (struct file *, struct poll_table_struct *);
poll函数的典型模板;
static unsigned int xxx_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
struct xxx_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
函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
-
nfds - 所要监视的这三类文件描述集合中,最大文件描述符加 1。
-
readfds - 监视的读文件描述符集合
-
writefds - 监视的写文件描述集合
-
exceptfds - 异常处理的文件描述符集合
-
timeout - 超时时间,在超时时间结束时,即使监视的文件描述没有反应,进程也会被唤醒。
readfds 可以设置为NULL,表示不关心任何文件的读变化,另外两个同理。
超时时间的结构体原型:
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微妙 */
};
系统提供的操作fd_set类型变量的函数:
void FD_ZERO(fd_set *set) //清除文件描述符集合
void FD_SET(int fd, fd_set *set) //将一个文件描述符加入文件描述符集合中
void FD_CLR(int fd, fd_set *set) //将一个文件描述符从文件描述符集合中清除
int FD_ISSET(int fd, fd_set *set) //判断文件描述符是否被置位
void main(void)
{
int ret, fd; /* 要监视的文件描述符 */
fd_set readfds; /* 读操作文件描述符集 */
struct timeval timeout; /* 超时结构体 */
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) {
case 0: /* 超时 */
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没有这个限制。
函数原型:
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
-
fds - 要监视的文件描述符以及要监视的事件,fd后面加s表示一个数组。
结构体原型:
struct pollfd { 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>
int main(int argc, char **argv)
{
struct pollfd fds;
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");
return 0;
}
3、epoll
当多路复用IO文件数量庞大,IO流量频繁的时候,而select和poll函数都是会遍历所监视的所有文件描述符,select和poll性能都会下降,而epoll的性能不会随着fd的增长而降低效率。
函数接口:
int epoll_create(int size);
//创建epoll句柄,创建完本身会占据一个fd,所以适用完后必须调用close关闭
//size - 从 Linux2.6.8 开始此参数已经没有意义了,随便填写一个大于 0 的值就可以。
//返回值: -1表示创建失败
int epoll_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()函数原型
unsigned int(*poll)(struct file * filp, struct poll_table* wait);
2、poll_wait()
该函数不会引起阻塞,作用将参数queue对应的等待队列上休眠的进程唤醒
void poll_wait(struct file *filp, wait_queue_heat_t *queue, poll_table * wait);
3、poll()函数模板
static unsigned int xxx_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
struct xxx_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,普通数据可读