poll/select轮询机制让等待队列wait_queue有了超时机制,如果阻塞一定时间后会直接返回。例如当我们实现一个中断方式的按键驱动时,虽然中断式的驱动,效率是蛮高的,但是大家有没有发现,应用程序的死循环里的读函数是一直在读的;在实际的应用场所里,有没有那么一种情况,偶尔有数据、偶尔没有数据,答案当然是有的。我们期望要做到的就是,当有数据的时候,我们才去读它,没数据的时候我们就不去读它,这样就避免了做无用功。
所以我们在中断的基础上添加poll机制来实现有数据的时候就去读,没数据的时候,自己规定一个时间,如果还没有数据,就表示超时时间。

所以Poll就是监控文件是否可读的一种轮询机制,作用与select一样。

应用程序的调用函数如下:

int poll(struct pollfd *fds,nfds_t nfds, int timeout);

Poll机制会判断fds中的文件是否可读,如果可读则会立即返回,返回的值就是可读fd的数量,如果不可读,那么进程就会休眠timeout这么长的时间,然后再来判断是否有文件可读,如果有,返回fd的数量,如果没有,则返回0。

poll机制总结(韦东山老师的总结):

  1. poll > sys_poll > do_sys_poll >poll_initwait,poll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。

  2. 接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数

    它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的;

    它还判断一下设备是否就绪。

  3. 如果设备未就绪,do_sys_poll里会让进程休眠一定时间

  4. 进程被唤醒的条件有2:一是上面说的“一定时间”到了,二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。

  5. 如果驱动程序没有去唤醒进程,那么chedule_timeout(__timeou)超时后,会重复2、3动作,直到应用程序的poll调用传入的时间到达。

poll机制中几个重要的函数:

设备驱动中

file_operations中要添加对应的.poll

头文件

#include <linux/poll.h>

添加到等待队列头

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

poll_wait() 函数的名称非常容易让人产生误会,以为它和 wait_event() 等一样,会阻塞地等待某事件的发生,其实这个函数并不会引起阻塞。poll_wait() 函数所做的工作是把当前进程添加到 wait 参数指定的等待列表(poll_table)中,实际作用是让唤醒参数 queue 对应的等待队列可以唤醒因 poll/select() 而睡眠的进程。

应用程序中
头文件

#include <poll.h>

poll文件集合

 struct pollfd fds[1];
/*
struct pollfd {
int fd;           //文件
short events;     //事件
short revents;
};events:
#define POLLIN      0x0001  //可以无阻塞的读
#define POLLPRI     0x0002
#define POLLOUT     0x0004  //可以无阻塞的写
#define POLLERR     0x0008
#define POLLHUP     0x0010
#define POLLNVAL    0x0020
*/

poll函数

int poll(struct pollfd fd[], nfds_t nfds, int timeout);
/*
nfds:  pollfd的个数
timeout: 超时时间
*/

下面是一个驱动实例:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/time.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/sched.h>#include <linux/poll.h> //要添加的头文件//gpio pin可以根据不同平台自行定义
#define IMX_GPIO_NR(bank, nr)               (((bank) - 1) * 32 + (nr))
#define CYNO_GPIO_BEEP_NUM                  IMX_GPIO_NR(6,10)int char_poll_major = 0;
dev_t char_devno;struct char_poll_dev{
struct cdev cdev;
};//引脚描述结构体
static struct pin_desc{
int irq;
unsigned char *name;
unsigned int pin;
};static struct pin_desc beep_desc = {
0,
"beep_num",
CYNO_GPIO_BEEP_NUM
};struct char_poll_dev *char_poll_devp;
struct class *char_poll_class;static DECLARE_WAIT_QUEUE_HEAD(wq); //定义一个等待队列头
static int condition = 0; //等待队列条件,中断事件标识,中断服务程序beep_interrupt_handler将它置为1,char_poll_read将它清零//按键中断执行程序,设置等待条件condition为真,并且唤醒挂起的进程
static irqreturn_t beep_interrupt_handler(int irq, void *dev_id)
{
//printk("%s\n", __func__);
condition = 1; //设置等待条件condition为真
wake_up_interruptible(&wq);    //唤醒挂起的进程
return IRQ_HANDLED;
}static int char_poll_open (struct inode *inode, struct file *filp)
{
printk(KERN_INFO "%s\n", __func__);
return 0;
}//用户执行read,阻塞,有按键按下时唤醒
static ssize_t char_poll_read (struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
char *data = "button falling\n\n\n";//printk(KERN_INFO "%s\n", __func__);
/* 如果没有按键动作, 休眠 */
wait_event_interruptible(wq,condition);  //如果没有按键动作,休眠,让出CPU
condition = 0;
/* 如果有按键动作, 返回键值 */
//printk(KERN_INFO "%s : %s\n", __func__, data);
copy_to_user(buf,data,strlen(data));
return strlen(data);
}static ssize_t char_poll_write (struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
char data[30];
//printk(KERN_INFO "%s\n", __func__);
memset(data, 0, 30);
copy_from_user(data, buf, count);
printk(KERN_INFO "%s : data is %s\n", __func__, data);
return strlen(data);
}//poll机制,底层设备驱动实现,加入等待队列头wq中,如果有按键按下,返回成功事件
unsigned int char_poll_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;/* 当没有按键按下时,即不会进入按键中断处理函数,此时condition = 0 * 当按键按下时,就会进入按键中断处理函数,此时condition被设置为1*/
poll_wait(filp, &wq , wait); //添加一个等待队列,该函数,只是将进程挂在wq队列上,而不是立即休眠
if(condition){mask |= POLLIN | POLLRDNORM;      //根据返回的位掩码,标识该设备可以读取了
}
return mask; //返回设备文件现在的状态}static struct file_operations char_poll_fops = {
.owner = THIS_MODULE,
.open  = char_poll_open,
.read  = char_poll_read,
.write = char_poll_write,
.poll  = char_poll_poll,     //添加poll实现};static int char_poll_init(void)
{
int err, ret = -ENODEV;
struct device *dev_temp;printk(KERN_INFO "%s\n", __func__);ret = alloc_chrdev_region(&char_devno, 0, 1, "char_poll");
char_poll_major = MAJOR(char_devno);if(ret){printk(KERN_ERR "%s : chrdev_region fail\n", __func__);goto chrdev_region_fail;
}char_poll_devp = kmalloc(sizeof(struct char_poll_dev), GFP_KERNEL);
if(char_poll_devp == NULL){printk(KERN_ERR "%s : kmalloc is fail\n", __func__);goto kmalloc_fail;
}memset(char_poll_devp, 0, sizeof(struct char_poll_dev));cdev_init(&char_poll_devp->cdev, &char_poll_fops);
char_poll_devp->cdev.owner = THIS_MODULE;
char_poll_devp->cdev.ops = &char_poll_fops;
err = cdev_add(&char_poll_devp->cdev, char_devno, 1);
if(err){printk(KERN_ERR "%s : cdev_add fail\n", __func__);goto cdev_add_fail;
}//start create class point
char_poll_class = class_create(THIS_MODULE, "char_poll");
if(IS_ERR(char_poll_class)){printk(KERN_ERR "%s : class_create fail\n", __func__);goto class_create_fail;
}dev_temp = device_create(char_poll_class, NULL, char_devno, NULL, "char_poll");
if(IS_ERR(dev_temp)){printk(KERN_ERR "%s : device_create fail\n", __func__);goto device_create_fail;
}//interrupt init
if(gpio_request(beep_desc.pin ,beep_desc.name)){ //申请GPIO,获取一个GPIO并声明标签printk(KERN_ERR "%s : request gpio %d error\n", __func__, beep_desc.pin);goto err_gpio_request;
}
gpio_direction_input(beep_desc.pin); //设置GPIO的方向,这里设置为GPIO输入
beep_desc.irq = gpio_to_irq(beep_desc.pin); //将GPIO映射为IRQ中断,得到中断号
printk(KERN_INFO "%s : the irq num is %d\n", __func__, beep_desc.irq);
ret = request_threaded_irq(beep_desc.irq, NULL, beep_interrupt_handler , IRQF_ONESHOT | IRQF_TRIGGER_FALLING, beep_desc.name , &beep_desc); //注册中断
if(ret){printk(KERN_ERR "%s : request_irq is error\n", __func__);goto err_request_irq;
}printk(KERN_INFO "%s : init end\n", __func__);return 0;err_request_irq:
free_irq(beep_desc.irq, &beep_desc);err_gpio_request:
gpio_free(beep_desc.pin);device_create_fail:
class_destroy(char_poll_class);class_create_fail:
cdev_del(&char_poll_devp->cdev);cdev_add_fail:
kfree(char_poll_devp);kmalloc_fail:chrdev_region_fail:
unregister_chrdev_region(char_devno,1);
return -1;}static void char_poll_exit(void)
{
printk(KERN_INFO "%s\n", __func__);
free_irq(beep_desc.irq, &beep_desc);
gpio_free(beep_desc.pin);
device_destroy(char_poll_class, char_devno);
class_destroy(char_poll_class);
cdev_del(&char_poll_devp->cdev);
kfree(char_poll_devp);
unregister_chrdev_region(char_devno,1);
}module_init(char_poll_init);
module_exit(char_poll_exit);MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("linux char driver base");
MODULE_AUTHOR("CZD");

当poll返回0就代表没有按键事件发生,否则就代表按了按键,并且返回了数据。下面是一个应用测试程序:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>int main(void)
{
int fd, ret;
char data[20];struct pollfd fds[1];      //poll文件集合, 这里只检测一个文件fd = open("/dev/char_poll", O_RDWR);
if (fd < 0)
{printf("can't open!\n");
}write(fd, "czd_write", strlen("czd_write"));fds[0].fd = fd;
fds[0].events = POLLIN;while(1){ret = poll(fds, 1, 5000); //poll函数的调用,当返回0时,表示5s时间到了,而这段时间里,没有事件发生"数据可读"if(ret  == 0){printf("---czd--- timeout\n");}else{ //如果没有超时,则读出按键值read(fd, data, 1);printf("---czd--- read data is %s", data);}
}close(fd);
return 0;

select与poll机制的设备驱动实现是一样的,仅仅上应用层的使用不同。

应用层的select函数会调用到内核函数do_select,do_select调用驱动的poll函数,若poll函数返回的掩码不可读写,那么do_select进入睡眠阻塞。要从睡眠中醒来并且跳出,有两种情况:a、超时跳出;b、驱动中唤醒等待队列,这时do_select再次调用poll函数,如果poll函数返回的掩码可读写,那么就跳出阻塞,否则继续睡眠。注意:上述是在select函数设成阻塞的情况,select函数可以设置成非阻塞的(将select函数的timeout参数设置成0)。

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 也返回。

struct timeval {
int tv_sec;
int tv_usec;
};
*/

清除一个文件集合

FD_ZERO(fd_set *set)

将一个文件描述符添加到文件描述符集合

FD_SET(int fd,fd_set *set)

将一个文件描述符从文件描述符集合中删除

FD_CLR(int fd,fd_set *set)

判断文件是否被置位

FD_ISSET(int fd,fd_set *set)

select应用示例代码如下

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/ioctl.h>int main()
{
int fd, i = 10;
char data_read[20];
fd_set rfds;memset(data_read, 0, sizeof(data_read));fd = open("/dev/char_poll", O_RDWR | O_NONBLOCK);
if (fd < 0)
{printf("can't open!\n");
}while(1){FD_ZERO(&rfds);       //清除一个文件描述符集合FD_SET(fd, &rfds);    //将fd加入rfds文件描述符集合select(fd + 1, &rfds, NULL, NULL, NULL);   //阻塞,直到有可读才返回if(FD_ISSET(fd, &rfds)){ //判断文件描述符是否被置位printf("select : the file can be read\n");read(fd, data_read, 1);printf("data_read = %s\n", data_read);}}
close(fd);return 0;
}

linux驱动之poll/select轮询机制实例分析(结合驱动中断使用)相关推荐

  1. (原创)Linux设备轮询机制分析

    一. 设备轮询机制的基本思想 所谓的设备轮询机制实际上就是利用网卡驱动程序提供的NAPI机制加快网卡处理数据包的速度,因为在大流量的网络环境当中,标准的网卡中断加上逐层的数据拷贝和系统调用会占用大量的 ...

  2. 中断、轮询、事件驱动、消息驱动、数据流驱动(Flow-Driven)?

    轮询.事件驱动.消息驱动.流式驱动 ---数据流驱动 Unidirectional Architecture? 中断.事件.消息这样一种机制来实现更好的在多任务系统里运行... 阻塞,非阻塞 同步,异 ...

  3. MCDF中arbiter的轮询机制

    更新的MCDF的仲裁器采用的是Round Robin轮询的一个仲裁机制,简单来说就是最先被授权的通道在下一次仲裁中优先级会变为最低,这样可以保证每个通道都能够获得授权,而不是一个通道长时间连续的进行数 ...

  4. Node中的事件轮询机制

    文章目录 2 node中的事件循环模型 2-1 一些属性 2-2 循环模型 node事件循环总共有==六个阶段== process.nextTick()函数 __实例__ 2 node中的事件循环模型 ...

  5. ajax定时轮询机制图解,通过Ajax调用的轮询机制

    我需要添加轮询机制来通过我的网页调用Web服务.为此,我尝试在javascript页面内使用ajax呼叫.我对ajax和javascript很新.我写了下面的代码.通过Ajax调用的轮询机制 func ...

  6. nodejs的事件轮询机制

    /* 面试题:nodejs的事件轮询机制*/setTimeout(function() {console.log('setTimeout()') }, 0)setImmediate(function( ...

  7. 【状态保持】Cache 基于SQL 数据库 的缓存依赖 轮询机制详解

    首先声明一下如果您还不了解什么是Cache(缓存)请您先搜一下有关信息然后再看这篇文章. 当数据库中的信息发生变化的时候,应用程序能够获取变化的通知是缓存依赖得以实现的基础.应用程序可以通过两种途径获 ...

  8. 码支付如何对接网站_第四方聚合支付特色之一的轮询机制是怎么防风控的?

    简单地说,聚合支付是融合了支付宝.微信支付.花呗.翼支付等多种支付方式的一种"包容性"支付工具.做支付系统一定不能脱离实际业务场景,更不能照搬其他公司方案. 做聚合支付的服务商越来 ...

  9. android全局轮询机制,Android轮询机制

    在消息的获取上是选择轮询还是推送得根据实际的业务需要来技术选型,例如对消息实时性比较高的需求,比如微博新通知或新闻等那就最好是用推送了.但如果只是一般的消息检测比如更新检查,可能是半个小时或一个小时一 ...

  10. 轮询机制php,JS事件轮询机制讲解

    JS是单线程语言,深入理解JS里的Event Loop,本文主要和大家分享JS事件轮询机制,希望能帮助到大家. JS的执行机制(一): 1.首先判断JS是同步还是异步,同步就进入主进程,异步就进入ev ...

最新文章

  1. 1033 旧键盘打字
  2. 5、图书类别查询功能
  3. Linux环境搭建:设置主机名颜色、设置vim颜色
  4. 有人WIFI模块使用详解
  5. 不用到处找图标了!模库承包你所有的图标素材!
  6. 常用模块——pickle模块
  7. 例14:计算某日是该年的第几天
  8. nginx http重定向https 无效_Golang设置https访问,以及http如何重定向到https
  9. cross-env跨平台设置环境变量
  10. 斯坦福与苹果基于Apple Watch检测心率异常,0.5%人群被检出,其中84%患有房颤...
  11. atitit.attilax的软件 架构 理念.docx
  12. 访问kubernetes dashboard UI报错
  13. SSM框架整合(参考尚硅谷视频和文档
  14. HT513 I2S输入2.8W单声道D类音频功放IC
  15. 台式计算机怎么换内存条,笔者教你如何更换电脑内存条
  16. 2022年帝国CMS7.5内核精仿《5288商机网》二次开发多个功能,运营级
  17. Python:Flask简单实现统计网站访问量
  18. 新书已经到手《Java程序员,上班那点事儿》正式销售纪念帖
  19. 南航与英航签署合作谅解备忘录
  20. (课程笔记)| 林轩田机器学习基石入门(一)

热门文章

  1. 学习中的一些牢骚。字符串栈/堆的址标准写法。
  2. java截取文件名.后的字符串
  3. python执行过程
  4. Obsolete属性用法
  5. 复盘人生第一次科研经历
  6. 【NLPCC 2021】 First Call For Papers
  7. python—tf.keras.backend.clear_session()
  8. 监督学习的分类:判别/生成模型,概率/非概率模型、参数/非参数模型
  9. 海量数据挖掘MMDS week7: 相似项的发现:面向高相似度的方法
  10. firefox浏览器和IE