select(poll)实现代码实现分析(基于kernel 3.10.0)

来源:互联网 发布:迅龙数据恢复软件安卓 编辑:程序博客网 时间:2024/06/05 06:51
分析转自网上。针对kernel3.10.0的代码重新注释,对其中的错误进行了修正


上层要能使用select()poll()系统调用来监测某个设备文件描述符,那么就必须实现这个设备驱动程序中struct file_operation结构体的poll函数,为什么?因为这两个系统调用最终都会调用驱动程序中的poll函数来初始化一个等待队列项然后将其加入到驱动程序中的等待队列头,这样就可以在硬件可读写的时候wake up这个等待队列头,然后等待(可以是多个)同一个硬件设备可读写事件的进程都将被唤醒。(这个等待队列头可以包含多个等待队列项,这些不同的等待队列项是由不同的应用程序调用select或者poll来监测同一个硬件设备的时候调用file_operationpoll函数初始化填充的)


       下面就以select系统调用分析具体实现,源码路径:fs/select.c

      

一、          select()系统调用代码走读

调用顺序如下:sys_select() à core_sys_select() à do_select() à fop->poll()

 


/*
* sys_select函数
*/
SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp, fd_set __user *, exp, struct timeval __user *, tvp)
{
     /*
     * 如果超时值非NULL
     * 从用户空间取数据到内核空间, 并且转换为timespec格式的未来超时时间
     */
     if (tvp) {
          if (copy_from_user(&tv, tvp, sizeof(tv)))
               return -EFAULT;

          to = &end_time;
          if (poll_select_set_timeout(to,  tv.tv_sec + (tv.tv_usec / USEC_PER_SEC), (tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC))
               return -EINVAL;
     }
     
     ret = core_sys_select(n, inp, outp, exp, to);//关键函数
     ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);  //如果有超时值, 并拷贝离超时时刻还剩的时间到用户空间的timeval中

     /*返回就绪的文件描述符的个数*/
     return ret;
}

 

==================================================================

       core_sys_select()函数解析


int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timespec *end_time)
{
     /**
          typedef struct {
                      unsigned long *in, *out, *ex;
                      unsigned long *res_in, *res_out, *res_ex;
          } fd_set_bits;
     * 这个结构体中定义的全是指针,这些指针都是用来指向描述符集合的。
     **/
     fd_set_bits fds;
     void *bits;
     int ret, max_fds;
     unsigned int size;
     struct fdtable *fdt;
     /* Allocate small arguments on the stack to save memory and be faster */
     long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];  // 256/32 = 8, stack中分配的空间, 用来记录文件描述符的bitmap, fd_set_bits中的描述符集合指针就指向这里

     ret = -EINVAL;
     if (n < 0)
          goto out_nofds;

     /* max_fds can increase, so grab it once to avoid race */
     rcu_read_lock();
     fdt = files_fdtable(current->files); //RCU ref, 获取当前进程的文件描述符表
     max_fds = fdt->max_fds;
     rcu_read_unlock();
     if (n > max_fds) // 如果传入的n大于当前进程最大的文件描述符,给予修正
          n = max_fds;

     /*
     * We need 6 bitmaps (in/out/ex for both incoming and outgoing),
     * since we used fdset we need to allocate memory in units of
     * long-words. 
     */
     size = FDS_BYTES(n); // 以一个文件描述符占一bit来计算,传递进来的这些fd_set需要用掉多少个字
     bits = stack_fds;
     if (size > sizeof(stack_fds) / 6) {  // 除6,为什么?因为每个文件描述符需要6个bitmaps: in,out,ex,res_in,res_out,res_ex
          /* Not enough space in on-stack array; must use kmalloc */
          ret = -ENOMEM;
          bits = kmalloc(6 * size, GFP_KERNEL); // stack中分配的太小,直接kmalloc
          if (!bits)
               goto out_nofds;
     }

     //这里就可以明显看出struct fd_set_bits结构体的用处了。
     fds.in      = bits;
     fds.out     = bits +   size;
     fds.ex      = bits + 2*size;
     fds.res_in  = bits + 3*size;
     fds.res_out = bits + 4*size;
     fds.res_ex  = bits + 5*size;

     //get_fd_set仅仅调用copy_from_user从用户空间拷贝了fd_set
     if ((ret = get_fd_set(n, inp, fds.in)) ||
         (ret = get_fd_set(n, outp, fds.out)) ||
         (ret = get_fd_set(n, exp, fds.ex)))
          goto out;

     // 对这些存放返回状态的字段清0
     zero_fd_set(n, fds.res_in);
     zero_fd_set(n, fds.res_out);
     zero_fd_set(n, fds.res_ex);

     // 关键函数,完成主要的工作
     ret = do_select(n, &fds, end_time);

     if (ret < 0) // 有错误
          goto out;
     if (!ret) { // 超时返回,无设备就绪
          ret = -ERESTARTNOHAND;
          if (signal_pending(current))
               goto out;
          ret = 0;
     }

     // 把结果集,拷贝回用户空间
     if (set_fd_set(n, inp, fds.res_in) ||
         set_fd_set(n, outp, fds.res_out) ||
         set_fd_set(n, exp, fds.res_ex))
          ret = -EFAULT;

out:
     // 如果有申请空间,那么释放fds对应的空间
     if (bits != stack_fds)
          kfree(bits);
out_nofds:
     // 返回就绪的文件描述符的个数
     return ret;
}

 

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

do_select()函数解析


int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
{
     ktime_t expire, *to = NULL;
     struct poll_wqueues table;
     poll_table *wait;
     int retval, i, timed_out = 0;
     unsigned long slack = 0;
     unsigned int busy_flag = net_busy_loop_on() ? POLL_BUSY_LOOP : 0;
     unsigned long busy_end = 0;

     rcu_read_lock();
     /*
     * 根据已经设置好的fd位图检查用户打开的fd, 要求对应fd必须打开, 并且返回最大的fd。
     */
     retval = max_select_fd(n, fds);
     rcu_read_unlock();

     if (retval < 0)
          return retval;
     n = retval;

     /*
     * 一些重要的初始化:
     * poll_wqueues.poll_table.qproc函数指针初始化,该函数是驱动程序中poll函数实
     * 现中必须要调用的poll_wait()中使用的函数。
     */
     poll_initwait(&table);
     wait = &table.pt;
     if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
          wait->_qproc = NULL;
          timed_out = 1;  // 如果系统调用带进来的超时时间为0,那么设置. timed_out = 1,表示不阻塞,直接返回。
     }

     if (end_time && !timed_out)
          slack = select_estimate_accuracy(end_time); // 超时时间转换,转换为从现在开始到超时为止的纳秒数

     retval = 0;
     for (;;) {
          unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
          bool can_busy_loop = false;

          inp = fds->in; outp = fds->out; exp = fds->ex;
          rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;

          //所有n个fd的循环, i就是文件描述号
          for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
               unsigned long in, out, ex, all_bits, bit = 1, mask, j;
               unsigned long res_in = 0, res_out = 0, res_ex = 0;

               //先取出当前循环周期中的32个文件描述符对应的bitmaps,一次就检查32个文件
               in = *inp++; out = *outp++; ex = *exp++;
               all_bits = in | out | ex;  // 组合一下,有的fd可能只监测读,或者写,或者err,或者同时都监测
               if (all_bits == 0) { // 这32个描述符没有任何状态被监测,就跳入下一个32个fd的循环中
                    i += BITS_PER_LONG;  //每32个文件描述符一个循环,正好一个long型数
                    continue;
               }

               //本次32个fd的循环中有需要监测的状态存在,初始bit = 1
               for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit <<= 1) {
                    struct fd f;
                    if (i >= n) // i用来检测是否超出了最大待监测的fd
                         break;
                    
                    if (!(bit & all_bits))
                         continue; // bit每次循环后左移一位的作用在这里,用来跳过没有状态监测的fd
                         
                    f = fdget(i); // 得到file结构指针,并增加引用计数字段f_count,i就是文件描述号
                    if (f.file) {  // 如果file存在
                         const struct file_operations *f_op;
                         f_op = f.file->f_op;
                         mask = DEFAULT_POLLMASK;
                         if (f_op && f_op->poll) {
                              wait_key_set(wait, in, out,
                                        bit, busy_flag);  // 设置当前fd待监测的事件掩码
                              /*
                              * 核心实现1
                              * 调用驱动程序中的poll函数,以evdev驱动中的evdev_poll()为例
                              * 该函数会调用函数poll_wait(file, &evdev->wait, wait),
                              * poll_wait继续调用__pollwait()(前面将__pollwait注册为了qproc)回调来分配一个poll_table_entry结构体,
                              * 该结构体有一个内嵌的等待队列项,设置好wake时调用的回调函数后将其添加到驱动程序中的等待队列头中。
                              */
                              mask = (*f_op->poll)(f.file, wait);
                         }                         
                         fdput(f); // 释放file结构指针,实际就是减小他的一个引用计数字段f_count。

                         // mask是每一个fop->poll()程序返回的设备状态掩码。
                         if ((mask & POLLIN_SET) && (in & bit)) {
                              res_in |= bit;  // fd对应的设备可读
                              retval++;
                              wait->_qproc = NULL;  // 后续有用,避免重复执行__pollwait(),这里可以设置为NULL是因为_qproc已经在fop->poll中使用过了
                         }
                         if ((mask & POLLOUT_SET) && (out & bit)) {
                              res_out |= bit;  // fd对应的设备可写
                              retval++;
                              wait->_qproc = NULL;
                         }
                         if ((mask & POLLEX_SET) && (ex & bit)) {
                              res_ex |= bit;
                              retval++;
                              wait->_qproc = NULL;
                         }
                         /* got something, stop busy polling */
                         if (retval) {
                              can_busy_loop = false;
                              busy_flag = 0;

                         /*
                         * only remember a returned
                         * POLL_BUSY_LOOP if we asked for it
                         */
                         } else if (busy_flag & mask)
                              can_busy_loop = true;

                    }
               }

               // 根据poll的结果写回到输出位图里,返回给上级函数
               // TODO: 这里就所有都返回?
               if (res_in)
                    *rinp = res_in;
               if (res_out)
                    *routp = res_out;
               if (res_ex)
                    *rexp = res_ex;

               /*
               * 这里的目的纯粹是为了增加一个抢占点。
               * 在支持抢占式调度的内核中(定义了CONFIG_PREEMPT),cond_resched是空操作。
               */
               cond_resched();
          }
          
          wait->_qproc = NULL;  // 后续有用,避免重复执行__pollwait()


          /*
          * 跳出这个大循环的条件有: 
          *      有设备就绪或有异常(retval!=0), 
          *     超时(timed_out= 1), 或者有中止信号出现
          */
          
          if (retval || timed_out || signal_pending(current))
               break;
          if (table.error) {
               retval = table.error;
               break;
          }

          /* only if found POLL_BUSY_LOOP sockets && not out of time */
          if (can_busy_loop && !need_resched()) {
               if (!busy_end) {
                    busy_end = busy_loop_end_time();
                    continue;
               }
               if (!busy_loop_timeout(busy_end))
                    continue;
          }
          busy_flag = 0;

          /*
          * If this is the first loop and we have a timeout
          * given, then we convert to ktime_t and set the to
          * pointer to the expiry value.
          */
          if (end_time && !to) {
               expire = timespec_to_ktime(*end_time);
               to = &expire;
          }

          /* 
          * 第一次循环中,当前用户进程从这里进入休眠,
          * 上面传下来的超时时间只是为了用在睡眠超时这里而已
          * 超时,poll_schedule_timeout()返回0;被唤醒时返回-EINTR 
          */
          if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
                            to, slack))
               timed_out = 1; /* 超时后,将其设置成1,方便后面退出循环返回到上层 */
     }

     /*
     * 清理各个驱动程序的等待队列头,同时释放掉所有空出来
     * 的page页(poll_table_entry)
     */
     poll_freewait(&table);

     return retval;  // 返回就绪的文件描述符的个数
}

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


二、重要结构体之间关系

       比较重要的结构体由四个:struct poll_wqueuesstruct poll_table_pagestruct poll_table_entrystruct poll_table_struct,这小节重点讨论前三个,后面一个留到后面小节。

      

2.1、结构体关系

每一个调用select()系统调用的应用进程都会存在一个struct poll_weueues结构体,用来统一辅佐实现这个进程中所有待监测的fd的轮询工作,后面所有的工作和都这个结构体有关,所以它非常重要。

struct poll_wqueues {

       poll_table pt;

       struct poll_table_page *table;

       struct task_struct *polling_task; //保存当前调用select的用户进程struct task_struct结构体

       int triggered;         // 当前用户进程被唤醒后置成1,以免该进程接着进睡眠

       int error;               // 错误码

       int inline_index;   // 数组inline_entries的引用下标

       struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];

};

实际上结构体poll_wqueues内嵌的poll_table_entry数组inline_entries[] 的大小是有限:

#define MAX_STACK_ALLOC 832

#define FRONTEND_STACK_ALLOC     256

#define WQUEUES_STACK_ALLOC     (MAX_STACK_ALLOC - FRONTEND_STACK_ALLOC)

#define N_INLINE_POLL_ENTRIES      (WQUEUES_STACK_ALLOC / sizeof(struct poll_table_entry))

如果空间不够用,后续会动态申请物理内存页以链表的形式挂载poll_wqueues.table上统一管理。接下来的两个结构体就和这项内容密切相关:

struct poll_table_page { // 申请的物理页都会将起始地址强制转换成该结构体指针

       struct poll_table_page * next;     // 指向下一个申请的物理页

       struct poll_table_entry * entry; // 指向entries[]中首个待分配(空的) poll_table_entry地址

       struct poll_table_entry entries[0]; // page页后面剩余的空间都是待分配的 poll_table_entry结构体

};

对每一个fd调用fop->poll()  à  poll_wait()  à  __pollwait()都会先从poll_wqueues. inline_entries[]中分配一个poll_table_entry结构体,直到该数组用完才会分配物理页挂在链表指针poll_wqueues.table上,然后才会分配一个poll_table_entry结构体。具体用来做什么?这里先简单说说,__pollwait()函数调用时需要3个参数,第一个是特定fd对应的file结构体指针,第二个就是特定fd对应的硬件驱动程序中的等待队列头指针,第3个是调用select()的应用进程中poll_wqueues结构体的poll_table(该进程监测的所有fd调用fop->poll函数都用这一个poll_table结构体)

struct poll_table_entry {

       struct file *filp;            // 指向特定fd对应的file结构体;

       unsigned long key;              // 等待特定fd对应硬件设备的事件掩码,如POLLIN POLLOUTPOLLERR;

       wait_queue_t wait;             // 代表调用select()的应用进程,等待在fd对应设备的特定事件 (读或者写)的等待队列头上,的等待队列项;

       wait_queue_head_t *wait_address; // 设备驱动程序中特定事件的等待队列头;

};

       总结一下几点:

1.               特定的硬件设备驱动程序的事件等待队列头是有限个数的,通常是有读事件和写事件的等待队列头

2.               一个调用了select()的应用进程只存在一个poll_wqueues结构体

3.               该应用程序可以有多个fd在进行同时监测其各自的事件发生,但该应用进程中每一个fd有多少个poll_table_entry存在,那就取决于fd对应的驱动程序中有几个事件等待队列头了,也就是说,通常驱动程序的poll函数中需要对每一个事件的等待队列头调用poll_wait()函数。比如,如果有读写两个等待队列头,那么就在这个应用进程中存在两个poll_table_entry结构体,在这两个事件的等待队列头中分别将两个等待队列项加入;

4.               如果有多个应用进程使用selcet()方式同时在访问同一个硬件设备,此时硬件驱动程序中加入等待队列头中的等待队列项对每一个应用程序来说都是相同数量的(一个事件等待队列头一个,数量取决于事件等待队列头的个数)

 

2.2、注意项

对于第3点中,如果驱动程序中有多个事件等待队列头,那么在这种情况下,写设备驱动程序时就要特别小心了,特别是设备有事件就绪然后唤醒等待队列头中所有应用进程的时候需要使用另外的宏,唤醒使用的宏和函数源码见include/linux/wait.h:

在这之前看一看__pollwait()函数中填充poll_table_entry结构体时注册的唤醒回调函数pollwake()

static int pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)

{

       struct poll_table_entry *entry;

 

       entry = container_of(wait, struct poll_table_entry, wait);

       // 取得poll_table_entry结构体指针

       if (key && !((unsigned long)key & entry->key))

       /*这里的条件判断至关重要,避免应用进程被误唤醒,什么意思?*/

              return 0;

       return __pollwake(wait, mode, sync, key);

}

到底什么情况下会出现误唤醒呢?当然是有先决条件的。

驱动程序中存在多个事件的等待队列头,并且应用程序中只监测了该硬件的某几项事件,比如,驱动中有读写等待队里头,但应用程序中只有在监测读事件的发生。这种情况下,写驱动程序时候,如果唤醒函数用法不当,就会引起误唤醒的情况。

先来看一看我们熟知的一些唤醒函数吧!

#define wake_up(x)                    __wake_up(x, TASK_NORMAL, 1, NULL)

#define wake_up_interruptible(x)      __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr, void *key);

注意到这个key了吗?通常我们调用唤醒函数时keyNULL,很容易看出,如果我们在这种情况下,使用上面两种唤醒函数,那么上面红色字体的判断条件一直都会是假,那么也就是说,只要设备的几类事件之一有发生,不管应用程序中是否对其有监测,都会在这里顺利通过将应用程序唤醒,唤醒后,重新调用一遍fop->poll (注意:第一次和第二次调用该函数时少做了一件事,后面代码详解函数,得到设备事件掩码。假如恰好在这次唤醒后的一轮调用fop->poll()函数的循环中,没有其他硬件设备就绪(没有设备就绪不会返回吧),那么可想而知,从源码上看,do_select()会直接返回0

// mask是每一个fop->poll()程序返回的设备状态掩码

if ((mask & POLLIN_SET) && (in & bit)) {

       res_in |= bit;         // fd对应的设备可读

       retval++;

       wait = NULL;              // 后续有用,避免重复执行__pollwait()

}

(in & bit)这个条件就是用来确认用户程序有没有让你监测该事件的如果没有retval仍然是0,基于前面的假设,那么do_select()返回给上层的也是0。那又假如应用程序中调用select()的时候没有传入超时值,那岂不是和事实不相符合吗?没有传递超时值,那么select()函数会一直阻塞直到至少有1fd的状态就绪。

所以在这种情况下,设备驱动中唤醒函数需要用另外的一组:

#define wake_up_poll(x, m)                            /

       __wake_up(x, TASK_NORMAL, 1, (void *) (m))

#define wake_up_interruptible_poll(x, m)               /

       __wake_up(x, TASK_INTERRUPTIBLE, 1, (void *) (m))

这里的m值,应该和设备发生的事件相符合。设置poll_table_entry结构体的key项的函数是:

#define POLLIN_SET  (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR)

#define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR)

#define POLLEX_SET (POLLPRI)

static inline void wait_key_set(poll_table *wait, unsigned long in,  unsigned long out, unsigned long bit)

{

       if (wait) {

              wait->key = POLLEX_SET;

              if (in & bit)

                     wait->key |= POLLIN_SET;

              if (out & bit)

                     wait->key |= POLLOUT_SET;

       }

}

这里的m值,可以参考上面的宏来设置,注意传递的不是key的指针,而就是其值本身,只不过在wake_up_poll()pollwake()的传递过程中是将其转换成指针的。

       如果唤醒函数使用后面一组的话,再加上合理设置key值,我相信pollwake()函数中的if一定会严格把关,不让应用程序没有监测的事件唤醒应用进程,从而避免了发生误唤醒。


三、讨论几个细节

      

3.1fop->poll()

       fop->poll()函数就是file_operations结构体中的poll函数指针项,该函数相信很多人都知道怎么写,网上大把的文章介绍其模板,但是为什么要那么写,而且它做了什么具体的事情?本小节来揭开其神秘面纱,先贴一个模板上来。

      
         static unsigned int XXX_poll(struct file *filp, poll_table *wait)

{

    unsigned int mask = 0;

    struct XXX_dev *dev = filp->private_data;

    ...

    poll_wait(filp, &dev->r_wait, wait);

    poll_wait(filp ,&dev->w_wait, wait);

   

      if(...)//读就绪

    

          mask |= POLLIN | POLLRDNORM;

     

      if(...)//写就绪

    

          mask |= POLLOUT | POLLRDNORM;

     

     ..

     return mask;

}

       poll_wait()只因有wait字样,经常给人误会,以为它会停在这里等,也就是常说的阻塞。不过我们反过来想想,要是同一个应用进程同时监测多个fd,那么每一个fd调用xxx_poll的时候都阻塞在这里,那和不使用select()又有何区别呢?都会阻塞在当个硬件上而耽误了被的设备就绪事件的读取。

       其实,这个poll_wait()函数所做的工作挺简单,就是添加一个等待等待队列项到poll_wait ()函数传递进去的第二个参数,其代表的是驱动程序中的特定事件的等待队列头。

       下面以字符设备evdev为例,文件drivers/input/evdev.c

       static unsigned int evdev_poll(struct file *file, poll_table *wait)

{

       struct evdev_client *client = file->private_data;

       struct evdev *evdev = client->evdev;

 

       poll_wait(file, &evdev->wait, wait);

       return ((client->head == client->tail) ? 0 : (POLLIN | POLLRDNORM)) |

              (evdev->exist ? 0 : (POLLHUP | POLLERR));

}


       static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_addresspoll_table *p)

{

       if (p && wait_address)

              p->qproc(filp, wait_address, p);

}

其中wait_address是驱动程序需要提供的等待队列头,来容纳后续等待该硬件设备就绪的进程对应的等待队列项。关键结构体poll_table, 这个结构体名字也取的不好,什么table?其实其中没有table的一丁点概念,容易让人误解呀!

 

typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);

typedef struct poll_table_struct {

       poll_queue_proc qproc;

       unsigned long key;

} poll_table;

      fop->poll()函数的poll_table参数是从哪里传进来的?好生阅读过代码就可以发现,do_select()函数中存在一个结构体struct poll_wqueues,其内嵌了一个poll_table的结构体,所以在后面的大循环中依次调用各个fdfop->poll()传递的poll_table参数都是poll_wqueues.poll_table

       poll_table结构体的定义其实蛮简单,就一个函数指针,一个key值。这个函数指针在整个select过程中一直不变,而key则会根据不同的fd的监测要求而变化。

      qproc函数初始化在函数do_select()àpoll_initwait()àinit_poll_funcptr(&pwq->pt, __pollwait)中实现,回调函数就是__pollwait()

       int do_select(int n, fd_set_bits *fds, struct timespec *end_time)

{

       struct poll_wqueues table;

       …

       poll_initwait(&table);

       …

}

void poll_initwait(struct poll_wqueues *pwq)

{

       init_poll_funcptr(&pwq->pt, __pollwait);

       …

}

       static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)

{

       pt->qproc = qproc;

       pt->key   = ~0UL; /* all events enabled */

}

      

/* Add a new entry */

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;         // 保存对应的file结构体

       entry->wait_address = wait_address;  // 保存来自设备驱动程序的等待队列头

       entry->key = p->key;   // 保存对该fd关心的事件掩码

       init_waitqueue_func_entry(&entry->wait, pollwake);

       // 初始化等待队列项,pollwake是唤醒该等待队列项时候调用的函数

       entry->wait.private = pwq;

       // poll_wqueues作为该等待队列项的私有数据,后面使用

       add_wait_queue(wait_address, &entry->wait);

       // 将该等待队列项添加到从驱动程序中传递过来的等待队列头中去。

}

该函数首先通过container_of宏来得到结构体poll_wqueues的地址,然后调用poll_get_entry()函数来获得一个poll_table_entry结构体,这个结构体是用来连接驱动和应用进程的关键结构体,其实联系很简单,这个结构体中内嵌了一个等待队列项wait_queue_t,和一个等待队列头 wait_queue_head_t,它就是驱动程序中定义的等待队列头,应用进程就是在这里保存了每一个硬件设备驱动程序中的等待队列头(当然每一个fd都有一个poll_table_entry结构体)

很容易想到的是,如果这个设备在别的应用程序中也有使用,又恰好别的应用进程中也是用select()来访问该硬件设备,那么在另外一个应用进程的同一个地方也会调用同样的函数来初始化一个poll_table_entry结构体,然后将这个结构体中内嵌的等待队列项添加到同一份驱动程序的等待队列头中。此后,如果设备就绪了,那么驱动程序中将会唤醒这个对于等待队列头中所有的等待队列项(也就是等待在该设备上的所有应用进程,所有等待的应用进程将会得到同一份数据)

上面红色字体的语句保存了一个应用程序select一个fd的硬件设备时候的最全的信息,方便在设备就绪的时候容易得到对应的数据。这里的entry->key值就是为了防止第二节中描述的误唤醒而准备的。设置这个key值的地方在函数do_select()中。如下:

if (file) {

       f_op = file->f_op;

       mask = DEFAULT_POLLMASK;

       if (f_op && f_op->poll) {

                     wait_key_set(wait, in, out, bit);  // 见第二节  
                     mask = (*f_op->poll)(file, wait);

       }

}

 

fop->poll()函数的返回值都是有规定的,例如函数evdev_poll()中的返回值:

            return ((client->head == client->tail) ? 0 : (POLLIN | POLLRDNORM)) |  (evdev->exist ? 0 : (POLLHUP | POLLERR));

会根据驱动程序中特定的buffer队列标志,来返回设备状态。这里的判断条件是读循环buffer的头尾指针是否相等:client->head == client->tail

 

       3.2poll_wait()函数在select()睡眠前后调用的差异

       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);

}

这里有一个if条件判断,如果驱动程序中没有提供等待队列头wait_address,那么将不会往下执行p->qproc(__pollwait()),也就是不会将代表当前应用进程的等待队列项添加进驱动程序中对应的等待队列头中。也就是说,如果应用程序恰好用select来监测这个fd的这个等待队列头对应的事件时,是永远也得不到这个设备的就绪或者错误状态的。

       如果select()中调用fop->poll()时传递进来的poll_tableNULL,通常情况下,只要在应用层传递进来的超时时间结构体值不为0,哪怕这个结构体指针你传递NULL,那么在函数do_select()中第一次睡眠之前的那次所有fd的大循环中调用fop->poll()函数传递的poll_table是绝对不会为NULL的,但是第一次睡眠唤醒之后的又一次所有fd的大循环中再次调用fop->poll()函数时,此时传递的poll_tableNULL(为什么是NULL?没有看到哪里设置poll_table为NULL?),可想而知,这一次只是检查fop->poll()的返回状态值而已。最后如果从上层调用select时传递的超时值结构体赋值成0,那么do_select()函数的只会调用一次所有fd的大循环,之后不再进入睡眠,直接返回0给上层,基本上这种情况是没有得到任何有用的状态。

       为了避免应用进程被唤醒之后再次调用pollwait()的时候重复地调用函数__pollwait(),那么在传递poll_table结构体指针的时候,在睡眠之前保证poll_table->qproc为有效地址,而在唤醒之后保证传入的poll_table->qproc地址是NULL,因为在唤醒之后,再次调用fop->poll()的作用只是为了再次检查设备的事件状态而已,而不用再次加入等待队列。具体详见代码。

      

       3.3、唤醒应用进程

       第二节中已经讨论过驱动程序唤醒进程的一点注意项,但这里再次介绍睡眠唤醒的整个流程。

       睡眠是调用函数poll_schedule_timeout()来实现:

       int poll_schedule_timeout(struct poll_wqueues *pwq, int state, ktime_t *expires, unsigned long slack)

{

       int rc = -EINTR;

 

       set_current_state(state);

       if (!pwq->triggered)  // 这个triggered在什么时候被置1的呢?只要有一个fd对应的设备将当前应用进程唤醒后将会把它设置成1

              rc = schedule_hrtimeout_range(expires, slack, HRTIMER_MODE_ABS);

       __set_current_state(TASK_RUNNING);

      

       set_mb(pwq->triggered, 0);

       return rc;

}

       唤醒的话会调用函数pollwake():

       static int pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)

{

       struct poll_table_entry *entry;

 

       entry = container_of(wait, struct poll_table_entry, wait);

       if (key && !((unsigned long)key & entry->key))

              return 0;

       return __pollwake(wait, mode, sync, key);

}

       static int __pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)

{

       struct poll_wqueues *pwq = wait->private;

       DECLARE_WAITQUEUE(dummy_wait, pwq->polling_task);

 

       /*

        * Although this function is called under waitqueue lock, LOCK

        * doesn't imply write barrier and the users expect write

        * barrier semantics on wakeup functions.  The following

        * smp_wmb() is equivalent to smp_wmb() in try_to_wake_up()

        * and is paired with set_mb() in poll_schedule_timeout.

        */

       smp_wmb();

       pwq->triggered = 1;  // select()用户进程只要有被唤醒过,就不可能再次进入睡眠,因为这个标志在睡眠的时候有用

             

       return default_wake_function(&dummy_wait, mode, sync, key);  // 默认通用的唤醒函数

}

      

参考网址:

       1. http://blogold.chinaunix.net/u2/60011/showart_1334783.html

http://yuanbor.blog.163.com/blog/static/56674620201051134748647/

http://www.cnblogs.com/hanyan225/archive/2010/10/13/1850497.html

http://hi.baidu.com/operationsystem/blog/item/208eab9821da8f0e6f068cea.html

2. fs/select.c

drivers/input/evdev.c

include/linux/poll.h

include/linux/wait.h

kernel/wait.c

0 0
原创粉丝点击