linux poll 和 等待队列休眠的关系(基于kernel 3.10.0)

来源:互联网 发布:淘宝女包品牌排行 编辑:程序博客网 时间:2024/06/04 17:49
原理分析来自网上,代码由本人梳理分析

阅读vhost的时候,发现使用了大量的等待队列和poll,这里温故而知新一下。
注:wait_queue_t是等待在wait_queue_head_t队列中的等待元素

1.poll机制和等待队列

应用层通过系统调用poll函数进入内核,内核执行相应的sys_poll函数。在sys_poll函数中调用do_sys_poll函数。do_sys_poll函数通过调用poll_initwait函数初始化poll_wqueues变量table,并且将__pollwait函数赋值给table。这里的__pollwait函数将在驱动的poll方法中通过调用poll_wait函数来执行。poll_wait会执行p->_qproc,也就是__pollwait,将当前task加入等待队列后阻塞住

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的操作中寻找是否有已满足条件的操作,有则跳出循环

没有则继续向下执行。

linux poll 和 等待队列休眠的关系(基于kernel 3.10.0) - 六六哥 - 六六哥的博客


函数调用流程:
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, int, timeout_msecs) //也就是sys_poll
-> do_sys_poll
  -> poll_initwait //初始化poll_wqueues,设置poll_table的处理函数qproc为__pollwait,这里的__pollwait函数将在驱动的poll方法中通过调用poll_wait函数来执行。,poll_wait会执行p->_qproc,也就是__pollwait,将当前task加入等待队列后阻塞住
  -> do_poll
    -> do_pollfd(死循环等待,直到条件满足发生退出,返回sys_poll后,返回用户态)


上图中的唤醒操作是通过驱动中调用wake_up函数实现的。LDD3上的例子并不完整,可以通过其他博文来学习驱动中poll的使用。

总结下poll的机制:

1.poll->sys_poll->do_sys_poll->poll_initwait

/*
* 初始化poll_wqueues,设置poll_table的处理函数qproc为__pollwait,
* 这里的__pollwait函数将在驱动的poll方法中通过调用poll_wait函数来执行。
* poll_wait会执行p->_qproc,也就是__pollwait,将当前task加入等待队列后阻塞住
*/
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结构中的函数指针_proc。


后面的驱动中将通过调用poll_wait函数来调用次函数。

/*
* 通过调用poll_table注册的_qproc函数,来实现将任务加入等待队列,select中为__pollwait
*/
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
     if (p && p->_qproc && wait_address)
          p->_qproc(filp, wait_address, p);
}

此处p->qproc操作指向的就是__pollwait函数


2.do_sys_poll->do_poll


static int do_poll(unsigned int nfds,  struct poll_list *list, struct poll_wqueues *wait, struct timespec *end_time)
{
    ....
     /*
     * 在for死循环中,首先轮询整条poll_list。在所有等待poll的操作中寻找是否有已满足条件的操作,有则跳出循环
     */
     for (;;) {
          for (walk = list; walk != NULL; walk = walk->next) {
               for (; pfd != pfd_end; pfd++) {
                    /*
                     * 在do_pollfd函数中通过mask = file->f_op->poll来调用驱动中的poll方法,并且获得驱动中操作poll后的mask值。
                     */
                    if (do_pollfd(pfd, pt, &can_busy_loop, busy_flag)) {
                         count++;
                         pt->_qproc = NULL;
                         /* found something, stop busy polling */
                         busy_flag = 0;
                         can_busy_loop = false;
                    }
               }
          }

          if (count || timed_out)
               break;
          
          if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
               timed_out = 1;
     }
     return count;
}


在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, bool *can_busy_poll, unsigned int busy_flag)
{
     if (fd >= 0) {
          if (f.file) {
                    /*
                    * 即调用驱动中的poll函数(在写驱动程序时编写poll函数)
                    */
                    mask = f.file->f_op->poll(f.file, pwait);
          }
     }

     pollfd->revents = mask;

     return mask;
}


通过file->f_op->poll来调用驱动中的poll方法,而驱动中的poll方法会调用poll_wait函数

poll_wait函数调用poll_initwait函数注册的__pollwait函数(驱动函数的poll的实现方法中,一般都会调用这个poll_wait,如dvb_audio_poll(), bttv_poll()

/* Add a new entry */
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;
     entry->filp = get_file(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可以将函数置入休眠,看下具体操作

#define wait_event(wq, condition)                          \
do {                                             \
     if (condition)                                   \
          break;                                   \
     __wait_event(wq, condition);                         \
} while (0)


/*
* 通过__wait_event中的for(;;)循环后,队列进入休眠。休眠后等待wake_up
* 每次通过schedule()进行休眠
*/
#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

0 0
原创粉丝点击