linux内核中的poll机制实现原理

下面是函数调用情况:

SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,
        long, timeout_msecs)
        
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINEx(x, sname, ...)                \
    __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
#define __SYSCALL_DEFINEx(x, name, ...)                    \
    asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))
    
    
宏解析得到函数:
asmlinkage long sys_poll(struct pollfd __user * ufds, unsigned int nfds,
        long timeout_msecs)
上面这个函数位于fs/select.c中
poll(struct pollfd *fds, nfds_t nfds, int timeout)   对超时参数进行一些处理后直接调用下面函数
    ret = do_sys_poll(ufds, nfds, to)  ‘
        poll_initwait(&table)
            init_poll_funcptr(table->pt, __pollwait)   -->   table->pt->qproc = __pollwait;
        fdcount = do_poll(nfds, head, &table, end_time)
            for (;;) {
                struct poll_list *walk;

                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++) {
                
                        if (do_pollfd(pfd, pt)) {
                            count++;
                            pt = NULL;
                        }
                    }
                }
        
                pt = NULL;
                if (!count) {
                    count = wait->error;
                    if (signal_pending(current))
                        count = -EINTR;
                }
                if (count || timed_out)
                    break;
    
                if (end_time && !to) {
                    expire = timespec_to_ktime(*end_time);
                    to = &expire;
                }

                if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
                    timed_out = 1;
            }
            return count;
            20行开始是一个死循环,死循环退出的条件有两大点:
            a、由第43行可知,count非零(及30行中的do_pollfd返回值非零或者40行中有信号量等待或者39行有错误发生)
            b、超时
            第51行表示让本进程休眠一段时间,注意:应用程序执行poll调用后,如果a、b的条件不满足,进程就会进入休眠。
            那么,谁唤醒呢?除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒──记住这点,这就是为什么驱动的poll
            里要调用poll_wait的原因,后面分析。
            
            下面主要看do_pollfd(pfd, pt)函数
                mask = file->f_op->poll(file, pwait)  调用驱动程序里面的poll函数
                mask &= pollfd->events | POLLERR | POLLHUP;
                return mask;
                由此可见,主要让驱动程序中的poll函数返回适当的mask即可使得do_pollfd函数返回值非零从而使得用户空间的poll函                   数返回非零值
                如果超时,用户空间的poll函数也会返回,但返回值为0,由上面的return count可以得出。
                
                

下面我们就明白驱动程序中的poll函数应该怎样规划了。
上面的init_poll_funcptr函数中给poll_table结构体注册了一个__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函数中要间接的调用这个函数,刚好poll_wait函数会调用上面这个函数,这样我们的驱动程序的poll函数中调用什么函数,
返回什么样的值就都清楚了,这就可以指导我们进行驱动中的poll函数编程了。


现在来总结一下poll机制:
执行到驱动程序的poll_wait函数时,进程并没有休眠,我们的驱动程序里实现的poll函数是不会引起休眠的。让进程进入休眠,是前面分析的do_sys_poll函数的30行“__timeout = schedule_timeout(__timeout)”。
poll_wait只是把本进程挂入某个队列,应用程序调用poll > sys_poll > do_sys_poll > poll_initwait,do_poll > do_pollfd > 我们自己写的poll函数后,再调用schedule_timeout进入休眠。如果我们的驱动程序
发现情况就绪,可以把这个队列上挂着的进程唤醒,在驱动程序中一定要有唤醒程序。可见,poll_wait的作用,只是为了让驱动程序能找到要唤醒的进程。即使不用poll_wait,我们的程序也有机会被唤醒:chedule_timeout(__timeout),只是要休眠__time_out这段时间。

1. poll > sys_poll > do_sys_poll > poll_initwait,poll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。

2. 接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数
   它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的;
   它还判断一下设备是否就绪。

3. 如果设备未就绪,do_sys_poll里会让进程休眠一定时间

4. 进程被唤醒的条件有2:一是上面说的“一定时间”到了,二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。

猜你喜欢

转载自blog.csdn.net/Wenlong_L/article/details/81566025