当前位置:首页>Linux>Linux设备驱动轮询操作--多路复用IO

Linux设备驱动轮询操作--多路复用IO

  • 2026-09-08 08:15:59
Linux设备驱动轮询操作--多路复用IO

应用程序中的轮询编程(多路复用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)
  • nfds  -  所要监视的这三类文件描述集合中,最大文件描述符加 1。

  • readfds - 监视的读文件描述符集合

  • writefds - 监视的写文件描述集合

  • exceptfds - 异常处理的文件描述符集合

  • 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,普通数据可读 

最新文章

随机文章