01
1. poll机制适用场景
应用层采用超时机制访问驱动设备。即如果第一次访问可以使用直接返回,若不能访问,则先将应用层休眠,在到了设定的时间,再访问一次,此时可以访问则返回成功标志,若不能访问则返回失败。
02
2.poll机制实现流程
Poll机制会判断fds中的文件是否可读,如果可读则会立即返回,返回的值就是可读文件描述符fd的数量,如果不可读,那么进程就会休眠timeout这么长的时间,然后再来判断是否有文件可读,如果有,返回fd的数量,如果没有,则返回0。Poll机制的实现需要等待队列的支撑。
03
3.代码应用
在驱动里若要使用poll机制,只需要初始化一个等待队列,实例化file_operations中.poll成员即可。
驱动程序:
static DECLARE_WAIT_QUEUE_HEAD(queue_wait);
……
static unsigned int drv_poll(struct file *fp, poll_table * wait)
{
Unsigned int ret = 0;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
poll_wait(fp, &queue_wait, wait);
if(atomic_read(&dev->releasekey)) { /* 按键按下 */
ret = POLLIN | POLLRDNORM; /* 返回 PLLIN */
}
return ret;
}
……
/* 定义自己的file_operations结构体 */
static struct file_operations gpio_key_drv = {
.owner = THIS_MODULE,
.read = gpio_key_drv_read,
.poll = gpio_key_drv_poll,
};
……
应用层实例:
……
while (1)
{
/* 3. 读文件 */
ret = poll(fds, 1, timeout_ms);
if ((ret == 1) && (fds[0].revents & POLLIN))
{
read(fd, &val, 4);
printf("get button : 0x%x\n", val);
}
else
{
printf("timeout\n");
}
}
……
04
4.原理分析
从应用层一直到驱动,来分析poll机制实现与运行原理。
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(head, &table, end_time);
|
_ for ( walk = head; walk; walk = walk->next )
{
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等待队列下
|
_ if (p && p->_qproc && wait_address)
p->_qproc(filp, wait_address, p); > 将线程放入等待队列
|
_ add_wait_queue()
|
— mask赋值 ; return mask; /* 返回事件类型 */
pollfd->revents = mask; /* 将实际事件类型返回 */
count++;
pt->_qproc = NULL; //这个地方清空,下次调用驱动poll,就不会将进程挂入等待队列
}
}
if (count || timed_out) /* 如果有事件发生,或者超时,则跳出poll */
break;
if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) /* 如果没有事件发生,那么陷入休眠状态 */
timed_out = 1;
}
基本上,知道大概流程就可以了。其流程的实现大多依靠函数指针为基础,通过向指针函数传参,以及相关的调用配合编程思想实现相应的功能。在浏览时,不必纠结过多的细节,这也是最初笔者容易忽视的。往往纠结于某个细节而耽搁过多时间,将时间浪费在不必要的地方,虽然时间在当下看是一直富裕的,实则也是一种代价,只有在会用以后,才能够更快的去了解其中的原理。当然,另一个角度来讲,linux的实现很伟大,每一个细节都值得学习,所以当时间足够富裕时,建议多看看每一个流程的细节的衔接。
参考资料: https://www.cnblogs.com/amanlikethis/p/6915485.html 《嵌入式linux应用开发完全手册V2.3_韦东山》