等待队列

见《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;

    }