POLL 机制的适用场景
在前面引入中断时,我们曾经举过一个例子: 妈妈怎么知道卧室里小孩醒了?
POLL方式:妈妈要干很多活,但是可以陪小孩睡一会,定个闹钟要浪费点时间,但是可以继续干活。妈妈要么是被小孩吵醒,要么是被闹钟吵醒。
使用休眠-唤醒的方式等待某个事件发生时,有一个缺点: 等待的时间可能很久。我们可以加上一个超时时间,这时就可以使用 poll 机制。
步骤
- APP 不知道驱动程序中是否有数据,可以先调用
poll
函数查询一下,poll
函数可以传入超时时间; - APP 进入内核态,调用到驱动程序的
poll
函数,如果有数据则立刻返回; - 如果发现没有数据就休眠一段时间;
- 当有数据时,比如按下按键时,驱动程序的中断服务程序被调用,他会记录数据、唤醒APP;
- 当超时时间到之后,内核也会唤醒APP;
- 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 函数读数据;
注意几点:
drv_poll
要把线程挂入队列wq
,但是并不是在drv_poll
中进入休眠,而是在调用drv_poll
之后休眠。drv_poll
要返回数据状态- APP 调用一次 poll,有可能会导致 drv_poll 被调用 2 次
- 线程被唤醒的原因有 2:中断发生了去队列 wq 中把它唤醒 和 超时时间到了内核把它唤醒
- 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 可以调用 poll
或 select
函数,这 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 函数时,要指明:
- 你要监测哪一个文件:哪一个 fd
- 你想监测这个文件的哪种事件:是 POLLIN、还是 POLLOUT
- 最后,在 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