说明:linux版本2.6.37.1
结合相关资料的概括和总结,在此做个记录,有误之处请网友指正,谢谢!
应用层通过系统调用poll函数进入内核,内核执行相应的sys_poll函数。在sys_poll函数中调用do_sys_poll函数。do_sys_poll函数通过调用poll_initwait
函数初始化poll_wqueues变量table,并且将__pollwait函数赋值给table。这里的__pollwait函数将在驱动的poll方法中通过调用poll_wait函数来执行。
poll_initwait函数执行完后,do_sys_poll函数会调用do_poll函数。
在do_poll函数中有一个for的死循环,退出条件为count或者timeout为非零。
count为非零标识do_pollfd函数返回的mask为真,timeout表示定时时间到。
在do_pollfd函数中通过mask = file->f_op->poll来调用驱动中的poll方法,并且获得驱动中操作poll后的mask值。
在for死循环中,首先轮询整条poll_list。在所有等待poll的操作中寻找是否有已满足条件的操作,有则跳出循环
没有则继续向下执行。
上图中的唤醒操作是通过驱动中调用wake_up函数实现的。LDD3上的例子并不完整,
可以通过其他博文来学习驱动中poll的使用。
总结下poll的机制:
1.poll->sys_poll->do_sys_poll->poll_initwait
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;
}
此处需注意init_poll_funcptr(&pwq->pt, __pollwait)
将函数__pollwait函数赋值给&pwq->pt指向的pooll_table结构中的函数指针。
后面的驱动中将通过调用poll_wait函数来调用次函数。
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函数
2.do_sys_poll->do_polll
static int do_poll(unsigned int nfds, struct poll_list *list,
struct poll_wqueues *wait, struct timespec *end_time)
{
for(;;){
do_pollfd(pfd, pt)
if (count || timed_out)
break;
poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)
}
}
在poll_initwait函数初始化完毕后,do_sys_poll函数会调用do_poll函数。
在do_poll函数中首先调用do_pollfd(pfd, pt)函数
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
{
mask = file->f_op->poll(file, pwait);
}
通过file->f_op->poll来调用驱动中的poll方法,而驱动中的poll方法会调用poll_wait函数
poll_wait函数调用poll_initwait函数注册的__pollwait函数
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
poll_table *p)
{
struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
struct poll_table_entry *entry = poll_get_entry(pwq);
if (!entry)
return;
get_file(filp);
entry->filp = filp;
entry->wait_address = wait_address;
entry->key = p->key;
init_waitqueue_func_entry(&entry->wait, pollwake);
entry->wait.private = pwq;
add_wait_queue(wait_address, &entry->wait);
}
可以发现此函数就是完成添加队列的工作。
然后继续向下执行poll_schedule_timeout()
执行完此函数后,进程进入休眠,直到被wake_up或者休眠时间到。
所以总结poll的操作:首先调用poll函数将__pollwait函数注册到系统,然后通过f_ops->poll
调用驱动的poll方法,通过poll->poll_wait来调用之前注册的__pollwait函数,
在__pollwait函数中添加等待队列,并调用poll_schedule_timeout()函数
将其休眠,等待其他线程唤醒。所以使用polll和使用等待队列进行简单休眠一样需要在其他地方使用
wake_up函数来通知
3.等待队列的休眠
驱动中调用wait_event可以将函数置入休眠,看下具体操作
do { \
if (condition) \
break; \
__wait_event(wq, condition); \
} while (0)
#define __wait_event(wq, condition) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
if (condition) \
break; \
schedule(); \
} \
finish_wait(&wq, &__wait); \
} while (0)
通过__wait_event中的for(;;)循环后,队列进入休眠。休眠后等待wake_up