POLL机制

POLL 机制的适用场景

在前面引入中断时,我们曾经举过一个例子: 妈妈怎么知道卧室里小孩醒了?

POLL方式:妈妈要干很多活,但是可以陪小孩睡一会,定个闹钟要浪费点时间,但是可以继续干活。妈妈要么是被小孩吵醒,要么是被闹钟吵醒。

使用休眠-唤醒的方式等待某个事件发生时,有一个缺点: 等待的时间可能很久。我们可以加上一个超时时间,这时就可以使用 poll 机制。

步骤

  1. APP 不知道驱动程序中是否有数据,可以先调用 poll 函数查询一下,poll 函数可以传入超时时间;
  2. APP 进入内核态,调用到驱动程序的 poll 函数,如果有数据则立刻返回;
  3. 如果发现没有数据就休眠一段时间;
  4. 当有数据时,比如按下按键时,驱动程序的中断服务程序被调用,他会记录数据、唤醒APP;
  5. 当超时时间到之后,内核也会唤醒APP;
  6. APP 根据POLL 函数的返回值就可以知道是否有数据,如果有数据就调用 read 得到数据。

使用流程

妈妈进入房间时,会先看小孩醒没醒,闹钟响起之后走出房间之前又会再看小孩醒没醒;

注意: 这里看了小孩两次

POLL 机制也是类似的,流程如下:
在这里插入图片描述
函数执行流程如上图 1 - 8 所示,重点从 3 开始看。假设一开始无按键数据:
③ APP调用 poll 之后,进入内核态;
④ 驱动程序 drv_poll 被调用;
注意: drv_poll要把自己这个线程挂入等待队列wq中;假设不放入队列里,那以后发生中断时,中断服务程序去哪里找到你嘛? drv_poll 还会判断一下:有没有数据啊?返回这个状态
⑤ 当前没有数据,则会休眠一会;
⑥ 过程中,按下了按键,发生了中断

  • 在中断服务程序里记录了按键值,并且从 wq 中把线程唤醒。
    ⑦ 从休眠中被唤醒,继续执行 for 循环,再次调用 drv_poll
  • drv_poll 返回数据状态
    ⑧ 有数据,那从内核态返回到应用态;
    ⑨ APP 调用 read 函数读取数据。

如果一直没有数据,调用流程也是类似的,重点从③开始看,如下:
③ APP调用poll之后,进入内核态;
④ 导致驱动程序的drv_poll被调用:
注意: drv_poll 要把自己这个线程挂入等待队列 wq 中;假设不放入队列里,那以后发生中断时,中断服务程序去哪里找到你嘛?drv_poll 还会判断一下:有没有数据啊?返回这个状态。
⑤ 假设当前没有数据,则休眠一会;
⑥ 在休眠过程中,一直没有按下了按键,超时时间到:内核把这个线程唤醒;
⑦ 线程从休眠中被唤醒,继续执行 for 循环,再次调用 drv_poll: drv_poll 返回数据状态;
⑧ 如果还是没有数据,但是超时时间到了,那从内核态返回到应用态;
⑨ APP 不能调用 read 函数读数据;

注意几点:

  1. drv_poll 要把线程挂入队列 wq,但是并不是在 drv_poll 中进入休眠,而是在调用 drv_poll 之后休眠。
  2. drv_poll 要返回数据状态
  3. APP 调用一次 poll,有可能会导致 drv_poll 被调用 2 次
  4. 线程被唤醒的原因有 2:中断发生了去队列 wq 中把它唤醒 和 超时时间到了内核把它唤醒
  5. APP 要判断 poll 返回的原因:有数据,还是超时。有数据时再去调用 read函数

驱动编程

使用 poll 机制时,驱动程序的核心就是提供对应的 drv_poll 函数。在drv_poll 函数中要做 2 件事:

第一件事

把当前线程挂入队列 wq:poll_wait

  • APP 调用一次 poll,可能导致 drv_poll 被调用 2 次,但是我们并不需要把当前线程挂入队列 2 次。
  • 可以使用内核的函数 poll_wait 把线程挂入队列,如果线程已经在队列里了,它就不会再次挂入。

第二件事

返回设备状态:
APP 调用 poll 函数时,有可能是查询“有没有数据可以读”: POLLIN,也有可能是查询“你有没有空间给我写数据”: POLLOUT。所以 drv_poll 要返回自己的当前状态: (POLLIN | POLLRDNORM) 或 (POLLOUT | POLLWRNORM)。

  • POLLRDNORM 等同于 POLLIN,为了兼容某些 APP 把它们一起返回。
  • POLLWRNORM 等同于 POLLOUT ,为了兼容某些 APP 把它们一起返回。

APP 调用 poll 后,很有可能会休眠。对应的,在按键驱动的中断服务程序,也要唤醒操作

驱动程序中 poll 的代码如下。(在上一节环形的基础上)

static unsigned int gpio_key_drv_poll(struct file *fp, poll_table * wait)
{
   
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	poll_wait(fp, &gpio_key_wait, wait);
	return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}

应用编程

注意: APP 可以调用 pollselect 函数,这 2 个函数的作用是一样的。
poll / select 函数可以监测多个文件,可以监测多种事件:

事件类型 说明
POLLIN 有数据可读
POLLRDNORM 等同于 POLLIN
POLLRDBAND Priority band data can be read,有优先级较较高的“ band data”可读, Linux 系统中很少使用这个事件
POLLPRI 高优先级数据可读
POLLOUT 可以写数据
POLLWRNORM 等同于 POLLOUT
POLLWRBAND Priority data may be written
POLLERR 发生了错误
POLLHUP 挂起
POLLNVAL 无效的请求,一般是 fd 未 open

在调用 poll 函数时,要指明:

  1. 你要监测哪一个文件:哪一个 fd
  2. 你想监测这个文件的哪种事件:是 POLLIN、还是 POLLOUT
  3. 最后,在 poll 函数返回时,要判断状态

应用程序代码如下:

struct pollfd fds[1];
int timeout_ms = 5000;
int ret;
fds[0].fd = fd;
fds[0].events = POLLIN;
ret = poll(fds, 1, timeout_ms);
if ((ret == 1) && (fds[0].revents & POLLIN))
{
   
    read(fd, &val, 4);
    printf("get button : 0x%x\n", val);
}
完整代码

button_test.c


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>

/*
 * ./button_test /dev/100ask_gpio_key
 *
 */
int main(int argc, char **argv)
{
   
	int fd;
	int val;
	struct pollfd fds
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值