浅析 Linux poll 机制

在用户空间应用程序向驱动程序请求数据时,有以下几种方式:

  1. 不断查询,条件不满足的情况下就是死循环,非常耗cpu
  2. 休眠唤醒的方式,如果条件不满足,应用程序则一直睡眠下去
  3. poll机制,如果条件不满足,休眠指定的时间,休眠时间内条件满足唤醒,条件一直不满足时间到达自动唤醒
  4. 异步通知,应用程序注册信号处理函数,驱动程序发信号。类似于QT的信号与槽机制。

应用程序: poll 支持同时查询多个打开的文件

int main(int argc, char **argv)  
{  
    int fd;  
    unsigned char key_val = 0;  
      
    int ret;  
  
    struct pollfd fds[1];  
    fd = open("/dev/button", O_RDWR);  
      
    if (fd < 0)  
    {  
        printf("can't open!\n");  
    }  
      
    fds[0].fd     = fd;  
    fds[0].events = POLLIN;  
      
    while (1)  
    {  
        ret = poll(fds, 1, 5000);  
        if (ret == 0)  
        {  
            printf("time out\n");  
        }  
        else  
        {  
            read(fd, &key_val, 1);  
            printf("key_val = 0x%x\n", key_val);  
        }  
    }  
      
    return 0;  
} 

 

内核空间:sys_poll

asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,  
            long timeout_msecs)  
{  
    s64 timeout_jiffies;  
  
    if (timeout_msecs > 0) {  
#if HZ > 1000  
        /* We can only overflow if HZ > 1000 */  
        if (timeout_msecs / 1000 > (s64)0x7fffffffffffffffULL / (s64)HZ)  
            timeout_jiffies = -1;  
        else  
#endif  
            timeout_jiffies = msecs_to_jiffies(timeout_msecs);  
    } else {  
        /* Infinite (< 0) or no (0) timeout */  
        timeout_jiffies = timeout_msecs;  
    }  
        // 转换等待时间,do_sts_poll 是重点  
    return do_sys_poll(ufds, nfds, &timeout_jiffies);  
} 
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)  
{  
    struct poll_wqueues table;  
    ...  
    long stack_pps[POLL_STACK_ALLOC/sizeof(long)];  
    struct poll_list *stack_pp = NULL;  
  
    //table->pt->qproc =  __pollwait  
    poll_initwait(&table);  
/*   
void poll_initwait(struct poll_wqueues *pwq) 
{ 
    init_poll_funcptr(&pwq->pt, __pollwait); // pt->qproc = qproc; 
    pwq->error = 0; 
    pwq->table = NULL; 
    pwq->inline_index = 0; 
} 
*/  
  
    fdcount = do_poll(nfds, head, &table, timeout);  
    ...  
    poll_freewait(&table);  
    return err;  
} 
typedef struct poll_table_struct {  
    poll_queue_proc qproc;  
} poll_table;  
  
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);  

do_sys_poll 干了一件重要的事:table->pt->qproc =  __pollwait ,驱动程序中会调用这个函数

static int do_poll(unsigned int nfds,  struct poll_list *list,  
           struct poll_wqueues *wait, s64 *timeout)  
{  
    int count = 0;  
    poll_table* pt = &wait->pt;  
   
    for (;;) {  
        struct poll_list *walk;  
        long __timeout;  
        // 设置当前进程为可中断的  
        set_current_state(TASK_INTERRUPTIBLE);  
        for (walk = list; walk != NULL; walk = walk->next) {  
            struct pollfd * pfd, * pfd_end;  
  
            pfd = walk->entries;  
            pfd_end = pfd + walk->len;  
            for (; pfd != pfd_end; pfd++) {  
                // 调用驱动程序中的 poll,返回值>0 count++ ,直接返回,如果是0,休眠   
                // mask = file->f_op->poll(file, pwait);  
                // mask &= pollfd->events | POLLERR | POLLHUP;  
                // pollfd->revents = mask;  
                if (do_pollfd(pfd, pt)) {  
                    count++;  
                    pt = NULL;  
                }  
            }  
        }  
        // count非0表示 do_pollfd 至少有一个成功  
        // 跳出条件:有成功的,时间到,有信号需要处理  
        if (count || !*timeout || signal_pending(current))  
            break;  
        count = wait->error;  
        if (count)  
            break;  
  
        ...  
        // 进程的调度,休眠,要么时间到达,再去查询一次看是否满足,要么驱动程序中条件满足,驱动唤醒。  
        __timeout = schedule_timeout(__timeout);  
        if (*timeout >= 0)  
            *timeout += __timeout;  
    }  
    // 这里设置进程状态是因为,如果第一次查询就成功了,前面讲进程状态改变了 需要更正回运行状态。  
    __set_current_state(TASK_RUNNING);  
    return count;  
}  

do_poll 函数中会调用驱动程序中的 poll 函数

驱动程序:

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);  
static unsigned poll_drv_poll(struct file *file, poll_table *wait)  
{  
    unsigned int mask = 0;  
    poll_wait(file, &button_waitq, wait); // 将当前进程挂载到休眠队列,但是不会立即休眠  
  
    if (ev_press)  
        mask |= POLLIN | POLLRDNORM;    //返回给sys_poll 判断条件是否满足 跳出 or 休眠  
  
    return mask;  
}  
返回值,合法的事件如下:  
POLLIN  
有数据可读。  
POLLRDNORM  
有普通数据可读。  
POLLRDBAND  
有优先数据可读。  
POLLPRI  
有紧迫数据可读。  
POLLOUT  
写数据不会导致阻塞。  
POLLWRNORM  
写普通数据不会导致阻塞。  
POLLWRBAND  
写优先数据不会导致阻塞。  
POLLMSG  
SIGPOLL 消息可用。  
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);//  __pollwait(filp, wait_address, p)   
}  
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,  
                poll_table *p)  
{  
    struct poll_table_entry *entry = poll_get_entry(p);  
    if (!entry)  
        return;  
    get_file(filp);  
    entry->filp = filp;  
    entry->wait_address = wait_address;  
    init_waitqueue_entry(&entry->wait, current);  
    add_wait_queue(wait_address, &entry->wait);  
}  

驱动程序中一开始定义了一个等待队列头,在__pollwait中初始化了等待队列头,并将其加入等待队列。接着就会有下面几种情况:

1.do_poll 函数第一次查询就OK,直接跳出 for 循环,将进程状态改回running状态,继续执行
2.do_poll 函数查询失败,进程调度,当前进程进入休眠状态,这个状态下又会产生以下几种情况
    1、在休眠过程中驱动程序条件得到满足,驱动程序唤醒休眠进程,万事大吉
    2、在休眠时间过程中一直不满足,休眠给定时间到达,自动唤醒,再次查询,查询ok 唤醒,查询失败继续休眠给定时间,一直循环下去。

  
在驱动程序中条件满足,唤醒进程

static irqreturn_t buttons_irq(int irq, void *dev_id)  
{  
    struct keys_desc * keys_desc = (struct keys_desc *)dev_id;  
    unsigned int keyval;  
  
    keyval = s3c2410_gpio_getpin(keys_desc->key_addr);  
  
    if (keyval)  
    {  
        /* 松开 */  
        key_val = keys_desc->key_value_up;  
    }  
    else  
    {  
        /* 按下 */  
        key_val = keys_desc->key_value_down;  
    }  
  
    ev_press = 1;                  /* 表示中断发生了 */  
    wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */  
  
      
    return IRQ_RETVAL(IRQ_HANDLED);  
} 

 

转载自:http://blog.csdn.net/lizuobin2/article/details/52703976

posted on 2017-08-30 23:19  wpjamer  阅读(333)  评论(0编辑  收藏  举报

导航