poll_wait阻塞/唤醒 --------------Good

come from : https://blog.csdn.net/zhangxuechao_/article/details/50726529

1. 应用阻塞 
应用程序使用 select() 或 poll() 调用设备驱动程序的 poll() 函数,该函数把输入输出复用处理的等待队列追加到由内核管理的进程的 poll_table()上

#include <linux/poll.h>
static inline void poll_wait (struct file *filp, wait_queue_head_t *wait_address, poll_table *P);
  • filp:设备文件信息的 struct file 结构体的指针参数 struct file *filp 
    P:追加到设备驱动上的 poll_table结构体指针参数

2. 内核等待事件

/* 
*    conditon:必须满足,否则阻塞
*    timeout和conditon相比,有更高优先级
*/
wait_event(wq, condition);
wait_event_timeout(wq, condition, timeout);
wait_event_interruptible(wq, condition) ;
wait_event_interruptible_timeout(wq, condition, timeout) ;

3. 唤醒等待队列

//可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)

//只能唤醒处于TASK_INTERRUPTIBLE状态的进程
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

4. schedule_timeout 
表示的进程睡眠直到时间超时,函数就会立即返回

5. 信号 
TASK_INTERRUPTIBLE是可以被信号和wake_up()唤醒的,当信号到来时,进程会被设置为可运行 
TASK_UNINTERRUPTIBLE只能被wake_up()唤醒

信号来源: 
硬件来源:(比如我们按下了键盘或者其它硬件故障) 
软件来源:最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作

二、驱动实现方法

复制代码
/* 定义一个等待队列,这个等待队列实际上是由中断驱动的,当中断发生时,会令挂接到这个等待队列的休眠进程唤醒 */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

static unsigned drivers_poll(struct file *file, poll_table *wait)
{
    unsigned int mask = 0;
    poll_wait(file, &button_waitq, wait); /* 将进程挂接到button_waitq等待队列下 */

    /* 根据实际情况,标记事件类型 */
    if (ev_press)
        mask |= POLLIN | POLLRDNORM;

    /* 如果mask为0,那么证明没有请求事件发生;如果非零说明有时间发生 */
    return mask;
}
复制代码
      上述代码展示了一个poll()函数功能,具体对应的底层驱动实现细节。利用这样的框架,我们可以写出类似驱动的poll功能。但是,这个框架很难理解,不知道为什么这样编写?为此,我们需要了解linux系统poll功能实现的机制。

三、linux内核poll实现机制

     从应用程序调用poll()函数开始,一直到调用drivers_poll函数,期间的过程很复杂,捡主要的内容列出来:

复制代码
app: poll
      |
drv:sys_poll
      |
      — do_sys_poll(struct pollfd __user * ufds, unsigned int nfds, struct timespec * end_time)
        |   
        - poll_initwait(&table);  >  实际效果:令函数指针 table.pt.qproc = __pollwait,这个函数指针最终会传递给poll_wait函数调用中的wait->qproc
        |
        - do_poll(nfds, head, &table, end_time);
        |
        _ for ( ; ; )
          {
                for (; pfd != pfd_end; pfd++) {    /* 可以监测多个驱动设备所产生的事件 */
                    if (do_pollfd(pfd, pt)) {  
                      |
                      _  mask = file->f_op->poll(file, pwait); > 实际效果:执行我们写的drivers_poll(file,pwait)
                                |
                                 _  poll_wait(file, &button_waitq, wait); > 实际效果:执行__pollwait(file, &button_waitq, wait),也就是将
                                                                            进程挂接到button_waitq等待队列下
                                |
                                —  mask赋值 ; return mask; /* 返回事件类型 */
                         pollfd->revents = mask;    /* 将实际事件类型返回 */
                         count++; pt = NULL; 
                     } 
                 } 
               if (count || timed_out) /* 如果有事件发生,或者超时,则跳出poll */ 
                  break; 
               if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) /* 如果没有事件发生,那么陷入休眠状态 */ 
                  timed_out = 1; 
          }
复制代码
      由此可见,我们的drivers_poll()函数,是系统在执行sys_poll()过程中的一个调用,调用的目的是“将进程挂接到等待队列下”和“返回事件类型mask”。当已经发生了请求事件,那么通过标记mask非0,if (do_pollfd(pfd, pt))判断为真,令count++,从而可以直接令poll()函数成功返回。如果还没有发生请求的事件,那么mask被标记为0,进程将通过函数poll_schedule_timeout()陷入休眠状态。一旦发生了请求的事件,因为之前已经将进程挂接到等待队列下,所以进程将被唤醒,重新执行drivers_poll(),而显然此时能够成功返回。

备注:分析的源码版本为linux-2.6.30.4。

猜你喜欢

转载自blog.csdn.net/zmjames2000/article/details/88104464