/* AUTHOR: Pinus
* Creat on : 2018-10-11
* KERNEL : linux-4.4.145
* BOARD : JZ2440(arm9 s3c2440)
* REFS : 韦东山视频教程第二期
*/
概述
啥子事poll机制呢?直白来说,当你在应用程序中使用poll,程序就会在给定时间内进入沉睡状态等待某项资源,
只回在两种情况下返回结束,1.设定的等待时间到了;2.所等待的资源到了。
应用价值:还是说我们的按键程序,上一小节,我们虽然使用了中断的方式,极大的节省了资源,但我们的测试程序一旦启动,就会在while中循环等待中断,永远永远,程序一直在运行,这显然不好。所以这节就是引入poll机制,让测试程序等待指定时间,如果一直没有中断产生,就会自动结束程序。
实验
目标:为(3.1)一个按键所能涉及的:按键中断 添加poll机制处理方式
驱动程序的实现并不复杂,Linux作为一个成熟的系统,已经为我们做好了体系,我们往往只要实现具体的功能就可以了。
poll作为应用中常用的功能,被定义在了file_operation结构体中(这个结构体是驱动的基础啊)
下文只列出在(3.1)一个按键所能涉及的:按键中断 添加poll机制处理方式 基础上修改的部分
1.在file_operation结构体中添加poll函数
static const struct file_operations jz2440_buttons_fops = {
.owner = THIS_MODULE,
.read = buttons_drv_read,
.open = buttons_drv_open,
.release = buttons_drv_close,
.poll = buttons_drv_poll, /* poll的具体函数 */
};
2. 既然我们采用poll的方式休眠,自然不用再read的时候休眠了
static ssize_t buttons_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if (1 != size)
return -EINVAL;
/*如果没有按键发生, 休眠
* 如果有,返回
*/
//wait_event_interruptible(button_waitq, ev_press); /* 注释掉这行 */
ev_press = 0;
copy_to_user(buf, &key_val, 1);
return 1;
}
3. 实现poll函数
unsigned int buttons_drv_poll (struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); //不会立即休眠,只是将程序挂在队列上
if(ev_press)
mask |= POLLIN | POLLRDNORM;
return mask;
}
看来关键的就是poll_wait(file, &button_waitq, wait); 函数,传进三个参数
file:是监测的文件
button_waitq:是定义的睡眠队列
wait:追加到设备驱动上的 poll_table结构体指针参数
4. 唤醒方式不变仍然在中断处理函数中唤醒程序
ev_press = 1;
wake_up_interruptible(&button_waitq); /*唤醒*/
5. 测试程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
/*
* poll 机制
*/
int main(int argc, char **argv)
{
int fd;
char key_val;
struct pollfd fds[1];
int ret;
fd = open("/dev/buttons", O_RDWR);
if (fd < 0){
printf("can't open dev nod !\n");
return -1;
}
fds[0].fd = fd;
fds[0].events = POLLIN;
while(1){
ret = poll(fds, 1, 5000); // 等待5s,关键!!
if(ret == 1){
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
else{
printf("time out \n");
}
}
return 0;
}
原理浅析
驱动中poll原型:unsigned poll(struct file *file, poll_table *wait)
应用中poll原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
其中
struct pollfd {
int fd;
short events;
short revents;
};
在linux中执行man poll可以看到events为期待发生的事件,revent为真实发生的事件
nfds为*fds中pollfd的个数
timeout为睡眠时间,毫秒单位。
1. poll作为file_operation的一部分,在调用时与open等大致相同
对于系统调用poll或select,经过系统调用层unistd.h
#define __NR_poll 1068
__SYSCALL(__NR_poll, sys_poll)
linux内核通过宏定义调用, 函数有三个参数对应为fs/select.c中
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, int, timeout_msecs)
{
...
ret = do_sys_poll(ufds, nfds, to);
...
}
do_sys_poll函数也位于位于fs/select.c文件中,我们忽略其他不重要的代码:
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{
……
poll_initwait(&table);
fdcount = do_poll(nfds, head, &table, timeout);
poll_freewait(&table);
……
}
2. 先来看poll_initwait(&table)
poll_initwait函数非常简单,它初始化一个poll_wqueues变量table:
poll_initwait >> init_poll_funcptr(&pwq->pt, __pollwait) >> pt->qproc = qproc;
即table->pt->qproc = __pollwait,__pollwait将在驱动的poll函数里用到。在poll_initwait()中注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait()时,真正被调用的函数。
void poll_initwait(struct poll_wqueues *pwq)
{
init_poll_funcptr(&pwq->pt, __pollwait);
pwq->polling_task = current;
pwq->triggered = 0;
pwq->error = 0;
pwq->table = NULL;
pwq->inline_index = 0;
}
static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
{
pt->_qproc = qproc;
pt->_key = ~0UL; /* all events enabled */
}
3. 再来do_poll函数位于fs/select.c文件中,代码如下:
static int do_poll(unsigned int nfds, struct poll_list *list,
struct poll_wqueues *wait, struct timespec *end_time)
{
01 ...
02 for (;;)
{
03 struct poll_list *walk;
04 bool can_busy_loop = false;
05 for (walk = list; walk != NULL; walk = walk->next)
{
06 struct pollfd * pfd, * pfd_end;
07 pfd = walk->entries;
08 pfd_end = pfd + walk->len;
//查询多个驱动程序
09 for (; pfd != pfd_end; pfd++)
{
/*
* Fish for events. If we found one, record it
* and kill poll_table->_qproc, so we don't
* needlessly register any other waiters after
* this. They'll get immediately deregistered
* when we break out and return.
*/
//do_pollfd函数相当于调用驱动里面的forth_drv_poll函数,下面另外再进行分析,返回值mask非零,count++,记录等待事件发生的进程数
10 if (do_pollfd(pfd, pt, &can_busy_loop, busy_flag))
{
11 count++;
12 pt->_qproc = NULL;
/* found something, stop busy polling */
13 busy_flag = 0;
14 can_busy_loop = false;
}
}
}
/*
* All waiters have already been registered, so don't provide
* a poll_table->_qproc to them on the next loop iteration.
*/
15 pt->_qproc = NULL;
16 if (!count)
{
17 count = wait->error;
18 if (signal_pending(current))
19 count = -EINTR;
}
20 if (count || timed_out) /* 若count不为0(有等待的事件发生了)或者timed_out不为0(有信号发生或超时),则退出休眠 */
break;
/* only if found POLL_BUSY_LOOP sockets && not out of time */
21 if (can_busy_loop && !need_resched())
{
22 if (!busy_end)
{
23 busy_end = busy_loop_end_time();
24 continue;
}
25 if (!busy_loop_timeout(busy_end))
26 continue;
}
27 busy_flag = 0;
/*
* If this is the first loop and we have a timeout
* given, then we convert to ktime_t and set the to
* pointer to the expiry value.
*/
28 if (end_time && !to)
{
29 expire = timespec_to_ktime(*end_time);
30 to = &expire;
}
//上述条件不满足下面开始进入休眠,若有等待的事件发生了,超时或收到信号则唤醒
31 if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
32 timed_out = 1;
}
33 return count;
}
分析其中的代码,可以发现,它的作用如下:
① 从<02行>可以知道,这是个循环,它退出的条件为:<20行>的2个条件之一( count非0,超时 )
count非0表示<10行>的do_pollfd至少有一个成功 or <17行>有错误产生 or <18行>有信号等待处理.
② 重点在do_pollfd函数,后面再分析
③ 第<31行>,让本进程休眠一段时间,注意:应用程序执行poll调用后,如果①②的条件不满足,进程就会进入休眠。那么,谁唤醒呢?除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒──记住这点,这就是为什么驱动的poll里要调用poll_wait的原因,后面分析。
4. do_pollfd函数位于fs/select.c文件中,代码如下:
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
{
...
if (file->f_op && file->f_op->poll) { /* 当fops存在且poll被实际定义时 */
...
mask = file->f_op->poll(file, pwait);
...
}
/* Mask out unneeded events. */
mask &= pollfd->events | POLLERR | POLLHUP; //期望events和实际事件相与;相同有值,不同为零
pollfd->revents = mask; /* 实际事件 */
return mask;
}
可见,它就是调用我们的驱动程序里注册的poll函数。再把驱动中的poll拷贝过来看一下。
unsigned int buttons_drv_poll (struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); //不会立即休眠,只是将程序挂在队列上
if(ev_press)
mask |= POLLIN | POLLRDNORM;
return mask;
}
可以预见
1. 当按键没有被按下,也就是ev_press==0时,返回的mask=0;do_pollfd返回值为0,count为0,不满足条件,循环继续执行。
2. 按键按下,在中断处理函数中,ev_press=1,则在poll函数中返回mask |= POLLIN | POLLRDNORM;,当和预期events(测试程序中)fds[0].events = POLLIN;相与后do_pollfd返回真,count++,退出循环,结束poll函数的等待,应用程序继续运行。
5. 驱动程序里与poll相关的地方有两处:一是构造file_operation结构时,要定义自己的poll函数。二是通过poll_wait来调用上面说到的__pollwait函数,pollwait的代码如下:
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
if (p && wait_address)
p->qproc(filp, wait_address, p);
}
p->qproc就是__pollwait函数,从它的代码可知,它只是把当前进程挂入我们驱动程序里定义的一个队列里而已。它的代码如下:
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p)
{
struct poll_table_entry *entry = poll_get_entry(p);
if (!entry)
return;
get_file(filp);
entry->filp = filp;
entry->wait_address = wait_address;
init_waitqueue_entry(&entry->wait, current);
add_wait_queue(wait_address, &entry->wait);
}
可以看出,执行到驱动程序的poll_wait函数时,进程并没有休眠,只是把进程挂在了一个队列里。让进程进入休眠,是前面分析的do_poll函数的<31行>poll_schedule_timeout()【思考一:这个函数具体是如何实现睡眠延时的?】函数。
poll_wait只是把本进程挂入某个队列,应用程序调用poll > sys_poll > do_sys_poll > poll_initwait,do_poll > do_pollfd > fops.poll,再调用poll_schedule_timeout进入休眠。如果我们的驱动程序发现情况就绪,可以把这个队列上挂着的进程唤醒。可见,poll_wait的作用,只是为了让驱动程序能找到要唤醒的进程。即使不用poll_wait,当超时,time_out=1,下次循环式程序也会退出。
现在来总结一下poll机制:
1. poll > sys_poll > do_poll > poll_initwait,poll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。
2. 接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数。 它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的,这个队列的意义是为了能让其他程序知道去哪里唤醒它。
3. 进程被唤醒的条件有2:一是上面说的“一定时间”到了,二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。
4. 如果驱动程序没有去唤醒进程,那么poll_schedule_timeout超时后,time_out=1,下次循环式程序也会退出。
应用程序调用poll,经过一系列调用,进入do_poll,首先判断一下是否有资源,有就直接结束,没有等待唤醒或者超时,再次判断是否有资源,有就退出。