linux poll机制分析(二)

一、回顾

linux poll机制使用(一)写了个实现poll机制的简单例子。在驱动模块中需要实现struct file_operations.poll成员。在驱动模块中xxx_poll函数的的作用是将当前进程添加到等待队列中;然后判断事件是否发生,发生则返回POLLIN | POLLRDNORM,否则返回0(可以看看上一章的例子);接下来分析一下 linux 内核中 poll 机制的实现。

二、poll机制分析

1、系统调用

当应用层调用poll函数时,linux发生系统调用(系统调用入口CALL(sys_poll)),程序从应用空间切换到内核空间,然后执行sys_poll函数(sys_poll函数在fs\select.c文件中)。sys_poll 函数的作用是 根据时间计算滴答频率数,然后调用do_sys_poll函数。代码块如下:

/* ufds:应用层传递过来的struct pollfd结构体数组
 * nfds:应用层传递过来的struct pollfd结构体个数
 * timeout_msecs:超时时间*/
asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,long timeout_msecs)
{
    s64 timeout_jiffies;
    if (timeout_msecs > 0) {
        /* 大于0,根据时间计算滴答频率数 */
        timeout_jiffies = msecs_to_jiffies(timeout_msecs);
    } else {
        /* Infinite (< 0) or no (0) timeout */
        timeout_jiffies = timeout_msecs;
    }
    /* 然后调用do_sys_poll函数*/
    return do_sys_poll(ufds, nfds, &timeout_jiffies);
}

2、do_sys_poll函数

do_sys_poll函数在fs\select.c文件中;do_sys_poll函数的主要作用初始化table(table->pt->qproc = __pollwait),然后初始化poll列表(poll列表的作用是存放用户空间的struct pollfd)接着调用do_poll函数。下面的代码块省略了部分源代码。代码块如下:

int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{
    struct poll_wqueues table;
    struct poll_list *head;
    struct poll_list *walk;
    .....//省略了部分源代码
    
    if (nfds > current->signal->rlim[RLIMIT_NOFILE].rlim_cur)
       return -EINVAL;


    /* 初始化 table */
    /* table->pt->qproc =  __pollwait
     * __pollwait 主要将当前进程挂到等待队列中,这里只是做初始化而已。
     * 在驱动程序里调用的 poll_wait(file, &button_waitq, wait); 函数里面最终
     * 调用的就是table->pt->qproc()函数,也就是调用__pollwait()函数。
     */
    poll_initwait(&table);
    

    .....//省略部分源代码(省略的源代码其实就是分配空间和初始化head列表)
    


    /* do_poll函数的参数说明:
	 *
     * nfds: 应用层传递过来的struct pollfd结构体个数
     * head: poll列表头,列表里面包含应用层传进来的struct pollfd数组的信息
     * table: table->pt->qproc指向__pollwait函数(主要将当前进程挂到等待队列中)
     * timeout: 等待超时
     */
    /* 调用do_poll函数 */
    fdcount = do_poll(nfds, head, &table, timeout);
    
    .....//省略部分源代码(这部分的源代码是将revents(返回的事件) copy 到应用层)
    return err;
}

3、do_poll函数

do_poll函数在fs\select.c文件中。do_poll 函数的作用主要是设置当前进程的任务状态,然后遍历poll列表、调用do_pollfd函数,然后根据实际情况是否将当前进程休眠或者唤醒当前进程,代码块如下:

//参数说明
/*nfds: 应用层传递过来的struct pollfd结构体个数
 *list:poll列表头,列表里面包含应用层传进来的struct pollfd数组的信息
 *wait:wait->pt->qproc指向__pollwait函数(主要将当前进程挂到等待队列中)
 *timeout:等待超时时间
 */
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;
    
    /* Optimise the no-wait case */
    if (!(*timeout))
        pt = NULL;
     /* 执行poll的时候有一个大循环 */
    for (;;) {
        struct poll_list *walk;
        long __timeout;
        /* 设置当前的任务状态为可中断休眠 */
        set_current_state(TASK_INTERRUPTIBLE);
        /* 遍历poll列表 */
        for (walk = list; walk != NULL; walk = walk->next) {
            struct pollfd * pfd, * pfd_end;
            /* 获取头部的地址 */
            pfd = walk->entries;
            /* 获取尾部指针 */
            pfd_end = pfd + walk->len;
            /* 遍历struct pollfd结构体数组 */
            for (; pfd != pfd_end; pfd++) {
              
                /*------------------------------------------------------------------*/
                 /* do_pollfd其实就是调用开发者所写的xxx_poll函数了(里面调用的的是struct file_operations .poll函数)
                  * do_pollfd函数下面的代码块分析
                  */
				  
                 /* 执行完do_pollfd后,这个进程就被挂到等待队列里面了。
                  * 这里仅仅是挂到等待队列而已,进程的休眠是在下面
                  * 执行 schedule_timeout 函数的时候休眠的
                  */
                 
                if (do_pollfd(pfd, pt)) {
                    /* 如果执行驱动程序的poll返回的是非0值 则count++ */
                    /* 表名设备有数据要返回给应用程序 */
                    count++;
                    pt = NULL;
                }
	            /*------------------------------------------------------------------*/
            }
        }
        
        pt = NULL;
        /* 结束大循环的条件有三个:
         * count: 表示设备有数据返回给应用程序
         * timeout: 等待超时时间到
         * signal_pending(current): 当前进程接收到信号
         */
        if (count || !*timeout || signal_pending(current))
            break;
        /* 发生错误退出 */
        count = wait->error;
        if (count)
            break;
        if (*timeout < 0) {
            /* Wait indefinitely */
            __timeout = MAX_SCHEDULE_TIMEOUT;
        } else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) {
            /*
             * Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in
             * a loop
             */
            __timeout = MAX_SCHEDULE_TIMEOUT - 1;
            *timeout -= __timeout;
        } else {
            __timeout = *timeout;
            *timeout = 0;
        }
        /* 执行到这里之后 timeout 的值已经为零,下一次循环就会超时返回 */
        
        
        /* 这里让当前进程休眠一会
         * 唤醒进程的处理休眠的时间到之外,还可以由驱动程序唤醒
         */
        __timeout = schedule_timeout(__timeout);
        if (*timeout >= 0)
            *timeout += __timeout;
    }
    /* 将当前进程设置为就绪状态,等待CPU调度 */
    __set_current_state(TASK_RUNNING);
    return count;
}

4、do_pollfd

do_pollfd函数在fs\select.c文件中,函数的作用就是,根据文件描述符找到struct file结构体,然后调用驱动程序的poll函数

//参数说明
/* pollfd: struct pollfd结构体
 * pwait:pwait->qproc指向__pollwait函数(主要将当前进程挂到等待队列中)
 */
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
{
    unsigned int mask;
    int fd;
    mask = 0;
    fd = pollfd->fd;
    if (fd >= 0) {
        int fput_needed;
        struct file * file;
        /* 根据文件描述符,获取file结构
         * file结构是每打开一个文件就会有一个file
         */
        file = fget_light(fd, &fput_needed);
        mask = POLLNVAL;
        if (file != NULL) {
            mask = DEFAULT_POLLMASK;
            if (file->f_op && file->f_op->poll)
                /* 调用驱动里面的poll函数,这个函数是驱动开发者添加的poll函数 */
                /* 如果驱动有数据让应用程序读的话,就返回非0, 否侧返回0 */
                mask = file->f_op->poll(file, pwait);
            /* Mask out unneeded events. */
            /* 根据应用层传递的events,来屏蔽不需要的事件 */
            mask &= pollfd->events | POLLERR | POLLHUP;
            fput_light(file, fput_needed);
        }
    }
    pollfd->revents = mask;
    /* 设备有数据可读则返回非0 */
    /* 否则返回0 */
    return mask;
}

三、总结

  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. 进程被唤醒的条件有两个 :1、“一定时间”到了 ;2、二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。
  5. 如果驱动程序没有去唤醒进程,那么chedule_timeout(__timeou)超时后,会重复2、3动作,直到应用程序的poll调用传入的时间到达。
posted @ 2020-01-31 00:16  古澜  阅读(1488)  评论(2编辑  收藏  举报