等待队列
见《Linux设备驱动开发详解》=> 8.1.1 等待队列
poll_wait
poll_wait()退出循环的条件
(1)count非0,超时、有信号等待处理
(2)发生错误
(3)我们的驱动程序里注册的poll函数返回值非0
应用程序执行poll调用后,如果(1)(2)(3)的条件不满足,进程就会进入休眠。那么,谁唤醒呢?除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒,这就是驱动的poll里要调用poll_wait的原因,后面分析。
poll_wait()作用
把当前进程(app)挂入我们驱动程序里定义的一个队列里(为了让驱动程序能找到要唤醒的进程)。
一般poll_wait()函数在驱动的.poll中调用。如果应用程序没用到poll,可以不调用poll_wait。例:驱动的.read函数里调用wait_event_interruptible,中断里wake_up。
poll (app调用) => sys_poll => do_sys_poll => poll_initwait(&table) => do_poll
=> do_pollfd => .poll //驱动中自己写的poll函数; schedule_timeout //进入休眠。
如果我们的驱动程序发现情况就绪,可以把这个队列上挂着的进程唤醒。
wait_event_interruptible()
成功地唤醒一个被wait_event_interruptible()的进程,需要满足:
(1)condition为真的前提下 (2) 调用wake_up()。
condition一般声明为static volatile int类型
wait_event_interruptible的返回值
根据 wait_event_interruptible 的宏定义知:
1) 条件condition为真时调用这个函数将直接返回0,而当前进程不会
被 wait_event_interruptible和从runqueue队列中删除。
2) 如果要被wait_event_interruptible的当前进程有nonblocked pending
signals, 那么会直接返回-ERESTARTSYS(i.e. -512),当前进程不会
被wait_event_interruptible 和从runqueue队列中删除。
3) 其他情况下,当前进程会被正常的wait_event_interruptible,并从
runqueue队列中删除,进入TASK_INTERRUPTIBLE状态退出运行调度,
直到再次被唤醒加入runqueue队列中后而参与调度,将正常返回0。
wait_event_interruptible
#define wait_event_interruptible(wq, condition) \
({ \
int __ret = 0; \
if (!(condition)) \
__wait_event_interruptible(wq, condition, __ret); \
__ret; \
})
注: C语言中{a,b, ..., x}的的值等于最后一项,即x,因此上述
宏的值是 __ret。
#define __wait_event_interruptible(wq, condition, ret) \
do { \
DEFINE_WAIT(__wait); \
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \
if (condition) \
break; \
if (!signal_pending(current)) { \
schedule(); \
continue; \
} \
ret = -ERESTARTSYS; \
break; \
} \
finish_wait(&wq, &__wait); \
} while (0)
总结一下poll机制
1. poll(应用程序) > sys_poll > do_sys_poll > poll_initwait,poll_initwait函数注册一下回调函数
__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。
2. 接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数
它会调用poll_wait把当前进程(app)挂入我们的驱动自己定义的队列;
它还判断一下设备是否就绪。(即驱动的poll的返回值是不是0,若 不是0,则设备就绪)
3. 如果设备未就绪,do_sys_poll里会让进程休眠应用中指定的时间
4. 进程被唤醒的条件有2:一是上面说的“一定时间”到了,二是被驱动程序唤醒。驱动程序发现条件就绪时,
就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。
5. 如果驱动程序没有去唤醒进程,那么chedule_timeout(__timeou)超时后,会重复2、3动作,
直到应用程序的 poll调用传入的时间到达。
代码分析
poll系统调用
//驱动中定义
static DECLARE_WAIT_QUEUE_HEAD(read_wq);
(fs/select.c)
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, int, timeout_msecs)
//设置超时时间
poll_select_set_timeout
do_sys_poll(ufds, nfds, to);
struct poll_wqueues table;
poll_initwait(&table);
init_poll_funcptr(&pwq->pt, __pollwait);
//此函数会在驱动中调用poll_wait时调用
pwq->p->_qproc = __pollwait;
do_poll(nfds, head, &table, end_time);
for(;;){
//系统调用poll时,会调用一次驱动中定义的poll函数,若返回值不是0,则count++,会退出循环
if (do_pollfd(pfd, table->pt)) { table->pt的类型是poll_table
{fd = pollfd->fd;
//调用驱动中的poll函数
mask = file->f_op->poll(file, table->pt);
//将do_sys_poll中定义的table插入到驱动定义的队列中
poll_wait(file, &read_wq, wait)
__pollwait(file, &read_wq, wait)
return mask;}
count++;
pt->_qproc = NULL;
}
if (count || timed_out)
break;
if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
{set_current_state(TASK_INTERRUPTIBLE)
if (!pwq->triggered)
rc = schedule_hrtimeout_range(expires, slack, HRTIMER_MODE_ABS);
//sleep until timeout
schedule_hrtimeout_range_clock(expires, delta, mode,
CLOCK_MONOTONIC);
__set_current_state(TASK_RUNNING);}
timed_out = 1;
}
wait_event_interruptible(wq, condition)
wait_event_interruptible(read_queue_head, ev_write); //include/linux/wait.h
int __ret = 0;
if (!(ev_write))
__wait_event_interruptible(wq, condition, __ret); //include/linux/wait.h
DEFINE_WAIT(__wait); //include/linux/wait.h
DEFINE_WAIT_FUNC(__wait, autoremove_wake_function)
//autoremove_wake_function:唤醒等待队列wait中的thread,成功唤醒则清空等待队列wait
wait_queue_t __wait = {
.private = current,
.func = function,
}
for (;;) {
//把wait.h中定义的__wait插入到驱动定义的等待队列read_wq中
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);
if (list_empty(&__wait->task_list))
__add_wait_queue(&wq, __wait);
//如果事件发生了,则跳出循环
if (condition)
break;
//如果不是信号唤醒的,则进入
if (!signal_pending(current)) {
ret = schedule_timeout(ret);
expire = timeout + jiffies;
__mod_timer(&timer, expire, false, TIMER_NOT_PINNED);
schedule();
timeout = expire - jiffies;
if (!ret)
break;
continue;
}
ret = -ERESTARTSYS;
break;
}
finish_wait(&wq, &__wait);
源码分析:
wait_event_interruptible()分析:
读一下wait_event_interruptible()的源码,不难发现这个函数先将 当前进程的状态设置成TASK_INTERRUPTIBLE,如果condition为假,调用schedule(), 而schedule()会将位于TASK_INTERRUPTIBLE状态的当前进程从runqueue 队列中删除。从runqueue队列中删除的结果是,当前这个进程将不再参 与调度,除非通过其他函数将这个进程重新放入这个runqueue队列中, 这就是wake_up()的作用了。
由于这一段代码位于一个由condition控制的for(;;)循环中,所以当由 shedule()返回时(当然是被wake_up之后,通过其他进程的schedule()而再次调度本进程),如果条件condition不满足,本进程将自动再次被设 置为TASK_INTERRUPTIBLE状态,接下来执行schedule()的结果是再次被 从runqueue队列中删除。这时候就需要再次通过wake_up重新添加到 runqueue队列中。
如此反复,直到condition为真的时候被wake_up.
wake_up_interruptible(&read_wq);
include/linux/wait.h
wake_up_interruptible(&read_queue_head); //include/linux/wait.h
__wake_up(&read_queue_head, TASK_INTERRUPTIBLE, 1, NULL) //sched/wait.c
//kernel/sched/core.c
__wake_up_common(&read_queue_head, TASK_INTERRUPTIBLE, nr_exclusive, 0, NULL); //sched/wait.c
list_for_each_entry_safe(curr, next, &read_queue_head->task_list, task_list) {
if (curr->func(curr, TASK_INTERRUPTIBLE, wake_flags, key) &&
(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
//此函数在wait_event_interruptible中指定的
//kernel/wait.c中实现
autoremove_wake_function(curr, TASK_INTERRUPTIBLE, wake_flags, NULL)
//kernel/wait.c
ret = default_wake_function(wait, TASK_INTERRUPTIBLE, sync, NULL)
//kernel/sched/core.c
try_to_wake_up(curr->private, TASK_INTERRUPTIBLE, wake_flags);
//curr->private = current
//wait_event_interruptible中指定的
//对于单cpu来说,此处返回0
cpu = task_cpu(curr->private);
ttwu_queue(curr->private, cpu);
struct rq *rq = cpu_rq(cpu);
ttwu_do_activate(rq, curr->private, 0);
if (ret)
list_del_init(&wait->task_list);
break;
}