Linux阻塞与非阻塞I/O之poll与select

来源:互联网 发布:网络信息采集软件 编辑:程序博客网 时间:2024/06/05 03:29

轮询操作


 轮询操作的概念与作用:
  使用非阻塞I/O的应用程序通常会使用select()和poll()系统调用查询是否可对设备进行无阻塞的
  访问。select()和poll()系统调用最终会引发设备驱动中的poll()函数被执行
  
  
  
  
  
 应用程序中的轮询编程
  应用程序中最广泛用到的BSD UNIX中引入的select()系统调用。原型为:
  int select(int numfds,fd_set *readfds,fd_set *writefds *writefds,
  fd_set *exceptfds,struct timeval *timeout);
  
  相关参数介绍
  其中readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述集。
  numfds的值是需要检查的号码最高的文件描述符1.
  timeout参数是一个指向struct timeval类型的指针,它使select()在等待timeout时间后若没有文件描述符准备好则返回
  
   
  struct timeval{
   int tv_set;/*秒*/
   int tv_usec; /*微秒*/
  };
  
  
  下面操作用来设置、清楚、判断文件描述符集合:
  FD_ZERO(fd_set *set);/*清楚一个文件描述符集合*/
  FD_SET(int fd,fd_set *set);/*将一个文件描述符加入到文件描述符集合中*/
  FD_CLR(int fd,fd_set *set);/*将一个文件描述符从文件描述符集合中清楚*/
  FD_ISSET(int fd,fd_set *set);/*判断文件描述符是否被置位*/
 
 
 
 
 
 
 
 
 设备驱动中的轮询操作:
 设备驱动中poll()的函数原型是?:
 unsigned int (*poll)(struct file *filp,struct poll_table *wait);
 filp为file结构体指针
 wait为轮询表指针
 
 这个函数应该进行两项工作:
 1.对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列头添加到poll_table
  
 2.返回表示是否能对设备进行无阻读写、写访问的掩码
 
 
 
 poll_table结构,是给poll方法的第2个参数,在内核中来实现poll、select和epoll调用,在<linux/poll.h>中声明
 它被传递给驱动方法以便驱动可用每个能唤醒进程的等待队列来加载它,并且改变poll操作的状态
 
 
 
 
 向poll_table注册等待队列的函数poll_wait()
 void poll_wait(struct file *filp,wait_queue_head_t *queue,poll_table *wait);
 这个函数不会引起阻塞,它只是把当前进程添加到wait参数指定的等待队列表中
 
   /*poll_wait函数的具体实现*/
   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);
       // 将该等待队列项添加到从驱动程序中传递过来的等待队列头中去。
   }
   
   
   //该函数的实现机制:
    首先通过contain_of宏得到结构体poll_wqueues的地址,然后调用poll_get_entry()函数来得一个
    poll_table_entry结构体,这个结构体非常关键,就是通过它来连接驱动和应用进程的,这个结构体中内嵌了
    一个等待队列项wait_queue_t和一个等待队列头wait_queue_head_t,它就是驱动程序中定义的等待队列
    头,应用进程就是在这里保存了每一个硬件设备驱动程序中的等待队列头(注意,每一个fd都有一个poll_table_entry)
    
    正因为这样,如果这个设备在别的应用程序中也有使用,又恰好别的应用程序中也是用select访问
    该硬件设备,那么在另一个应用程序的同一个地方也会调用同样的函数来初始化一个poll_table_entry结构体,然后将这个结构体
    中内嵌的等待队列项添加到同一份驱动程序的等待队列头中。此后,如果设备就绪了,那么驱动程序中将会唤醒这个对于等待队列
    头中所有的等待队列项(也就是等待在该设备上的所有应用进程,所有等待的应用进程将会得到同一份数据)。
   

  
 

 
 驱动程序poll()函数应该返回设备资源的可获取状态,即POLLIN、POLLOUT
 、POLLPRI、POLLERR、POLLNVALD等宏的位"或"结果。POLLIN意味着设备可以无阻塞的读,POLLOUT意味着设备
 可以无阻塞的写
 
 
 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 | POLLWRNORM;/*标识数据可写入*/
  ...
  return mask;
 }
 
 
 
 
 问题补充:
 我们刚才在上面讨论到,poll方法只是做一个登记,并没有发生阻塞,真正的阻塞发生在select.c中do_select函数中
 下面让我们简单分析一下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;

  rcu_read_lock();
  retval = max_select_fd(n, fds);
  rcu_read_unlock();

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

  poll_initwait(&table);
  wait = &table.pt;
  if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
   wait = NULL;
   timed_out = 1;
  }

  if (end_time && !timed_out)
   slack = estimate_accuracy(end_time);

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

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

   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;
    const struct file_operations *f_op = NULL;
    struct file *file = NULL;

    in = *inp++; out = *outp++; ex = *exp++;
    all_bits = in | out | ex;
    if (all_bits == 0) {
     i += __NFDBITS;
     continue;
    }

    for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {
     int fput_needed;
     if (i >= n)
      break;
     if (!(bit & all_bits))
      continue;
     file = fget_light(i, &fput_needed);
     if (file) {
      f_op = file->f_op;
      mask = DEFAULT_POLLMASK;
      if (f_op && f_op->poll) {
       wait_key_set(wait, in, out, bit);
       
       //调用poll函数返回掩码
       mask = (*f_op->poll)(file, wait);
      }
      fput_light(file, fput_needed);
      
      //根据传入的掩码(可读、可写、异常),与实际上可以执行的操作相符时
      //符合的文件数retval就加一
      if ((mask & POLLIN_SET) && (in & bit)) {
       res_in |= bit;
       retval++;
       wait = NULL;
      }
      if ((mask & POLLOUT_SET) && (out & bit)) {
       res_out |= bit;
       retval++;
       wait = NULL;
      }
      if ((mask & POLLEX_SET) && (ex & bit)) {
       res_ex |= bit;
       retval++;
       wait = NULL;
      }
     }
    }
    if (res_in)
     *rinp = res_in;
    if (res_out)
     *routp = res_out;
    if (res_ex)
     *rexp = res_ex;
    cond_resched();
   }
   wait = NULL;
   
   //当符合条件或者超时,或者有信号中断,就跳出循环
   if (retval || timed_out || signal_pending(current))
    break;
   if (table.error) {
    retval = table.error;
    break;
   }

   /*
    * 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;
   }
            //当不符合条件,并且也没有超时,信号中断等,执行阻塞阻塞操作
     if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
         to, slack))
    timed_out = 1;
  }

  poll_freewait(&table);

  return retval;
 }


 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  

原创粉丝点击