字符驱动-poll机制
什么是poll
- poll机制基于等待队列
wait_queue
- poll机制:如果没有发生需要的事件,那么进程进入休眠。如果在限定的时间内得到需要的事件,那么成功返回,如果没有则返回超时错误信息
#include <poll.h>
int poll(struct pollfd fd[], nfds_t nfds, int timeout);
struct pollfd{
int fd; //文件描述符
short events; //请求的事件
short revents; //返回的事件
};
nfds:要监视的描述符的数目
timeout:超时时间
INFTIM 永远
0 立即返回
x>0 x毫秒
事件 | 意义 |
---|---|
POLLIN | 普通或优先级带数据可读 |
POLLRDNORM | 普通数据可读 |
POLLRDBAND | 优先级带数据可读 |
POLLPRI | 高优先级数据可读 |
POLLOUT | 普通数据可写 |
POLLWRNORM | 普通数据可写 |
POLLWRBAND | 优先级带数据可写 |
POLLERR | 发生错误 |
POLLHUP | 发生挂起 |
POLLNVAL | 描述字不是一个打开的文件 |
要想深入理解poll机制,就必须先理解wait_queue
什么是等待队列
- 在 Linux 驱动程序设计中,可以使用等待队列来实现进程的阻塞.
- 等待队列实质由一个等待队列头加上N个等待队列项组成的循环双向链表
- 常用函数如下:
函数 | 意义 |
---|---|
wait_queue_head_t wq; | 分配等待队列头 |
init_waitqueue_head(&wq); | 初始化等待队列头 |
wait_event | 加入等待队列项 |
wait_evnet_timeout | |
wait_event_interruptible | |
wait_event_interruptible_timeout | |
wake_up | 唤醒等待队列 |
wake_up_interruptib |
- 使用等待队列前通常先定义一个等待队列头
static wait_queue_head_t wq
- 然后调用
wait_event_*
函数将等待某条件condition
的当前进程插入到等待队列wq
中并睡眠 - 一直等到
condition
条件满足后,内核再将睡眠在等待队列wq
上的某一进程或所有进程唤醒
在wait_event_*
中,先创建等待队列项(包含了当前进程的参数) -> 再将等待队列项加入到等待队列头中 -> 设置休眠状态 -> 调用schedule
切换任务
在 wake_up*
中当收到唤醒信号后,signal_pending
判断唤醒信号 -> 设置运行态 -> 移除等待队列项 -> __wake_up_common
循环遍历等待队列内的所有元素,分别执行其对应的唤醒函数。
poll驱动框架
以上可以清楚的了解到poll机制的原理:利用等待队列实现休眠查询事件
poll驱动框架如下
static unsigned int Mypoll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
/* 该函数,只是将进程挂在button_waitq队列上,而不是立即休眠 */
poll_wait(file, &button_waitq, wait);
if(ev_press)
{
mask |= POLLIN | POLLRDNORM; /* 表示有数据可读 */
}
/* 如果ev_press事件发生,mask |= POLLIN | POLLRDNORM,否则mask = 0 */
return mask;
}
- 其中,当事件发生时,设置事件状态 -> 调用
wait_event_*
函数唤醒等待队列,回到poll_wait
休眠处顺序执行
可以看到主要是调用了poll_wait
函数,其调用流程如下
app: poll
|
drv:sys_poll
|
— do_sys_poll(struct pollfd __user * ufds, unsigned int nfds, struct timespec * end_time)
|
- poll_initwait(&table); > 实际效果:令函数指针 table.pt.qproc = __pollwait,这个函数指针最终会传递给poll_wait函数调用中的wait->qproc
|
- do_poll(nfds, head, &table, end_time);
|
_ for ( ; ; )
{
for (; pfd != pfd_end; pfd++)
{ /* 可以监测多个驱动设备所产生的事件 */
if (do_pollfd(pfd, pt))
{
mask = file->f_op->poll(file, pwait); > 实际效果:执行我们写的drivers_poll(file,pwait)
|
_ poll_wait(file, &button_waitq, wait); > 实际效果:执行__pollwait(file, &button_waitq, wait),也就是将
进程挂接到button_waitq等待队列下
|
— mask赋值 ; return mask; /* 返回事件类型 */
pollfd->revents = mask; /* 将实际事件类型返回 */
count++; pt = NULL;
}
}
if (count || timed_out) /* 如果有事件发生,或者超时,则跳出poll */
break;
if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) /* 如果没有事件发生,那么陷入休眠状态 */
timed_out = 1;
}