poll, select &&nbs…

原文链接:  http://blog.csdn.net/zacklin/article/details/7236419        

     阻塞操作是指,在执行设备操作时,若不能获得资源,则进程挂起直到满足可操作的条件再进行操作。非阻塞操作的进程在不能进行设备操作时,并不挂起。被挂起的进程进入sleep状态,被从调度器的运行队列移走,直到等待的条件被满足。 

   在Linux驱动程序中,我们可以使用等待队列(wait queue)来实现阻塞操作。waitqueue很早就作为一个基本的功能单位出现在Linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。等待队列可以用来同步对系统资源的访问,上节中所讲述Linux信号量在内核中也是由等待队列来实现的。 

下面我们以"globalfifo"为例,来说明linux下poll/select及阻塞与非阻塞操作的实现过程."globalfifo"可以被多个进程打开,但是每次只有当一个进程写入了一个数据之后本进程或其它进程才可以读取该数据,否则一直阻塞。

select和 poll的本质一样,前者在BSD Unix中引入,后者在SystemV中引入。poll和select用于查询设备的状态,以便用户程序获知是否能对设备进行非阻塞的访问,它们都需要设备驱动程序中的poll函数支持。

驱动程序中poll函数中最主要用到的一个API是poll_wait,其原型如下:

void poll_wait(struct file *filp, wait_queue_heat_t*queue, poll_table * wait);

poll_wait函数所做的工作是把当前进程添加到wait参数指定的等待列表(poll_table)中。工作过程如下图所示:

poll, <wbr>select <wbr>& <wbr>poll鈥攚ait底层数据结构

下面我们给出globalfifo的poll函数:

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

{

  unsigned int mask = 0;

  struct globalfifo_dev *dev =filp->private_data;

  

 down(&dev->sem);


  //添加等待队列

dev->r_wait/dev->w_wait到poll_tablewait所在的结构体的poll_table_entry中,然后添加等待队列项(指向当前进程)到等待队列头dev->r_wait/dev->w_wait中

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

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


 

  if (dev->current_len !=0)

  {

       mask|= POLLIN | POLLRDNORM;

  }

 

  if (dev->current_len !=GLOBALFIFO_SIZE)

  {

       mask |= POLLOUT | POLLWRNORM;

  }

     

  up(&dev->sem);

  return mask;

}

poll_wait内核实现代码如下:
staticvoid __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;    //添加wait_queue_head_t到entry
   init_waitqueue_entry(&entry->wait, current);
    add_wait_queue(wait_address,&entry->wait);    //添加等待队列项到等待队列头
}


poll_wait函数并不阻塞,真正的阻塞动作是上层的select/poll(这里指上层的poll)函数中完成的(见:select.c文件中的do_select)。select/poll会在一个循环中对每个需要监听的设备调用它们自己的poll支持函数以使得当前进程被加入各个设备的等待列表当前没有任何被监听的设备就绪,内核进行调度(调用schedule)让出cpu进入阻塞状态,schedule返回时将再次循环检测是否有操作可以进行,如此反复;否则,若有任意一个设备就绪,select/poll都立即返回以select(2.6.28内核)为例代码如下:

SYSCALL_DEFINE5(select,int, n, fd_set __user *, inp, fd_set __user *, outp,
       fd_set__user *, exp, struct timeval __user *, tvp)
{
   struct timespec end_time, *to = NULL;
   struct timeval tv;
   int ret;

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

   return ret;
}
poll/select内核调用过程如下:select()->core_sys_select()->do_select():do_select的源代码如下: 
(在 do_select 中创建poll_table并调用file_operations中的poll函数 
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)
       returnretval;
    n= retval;

    poll_initwait(&table);  //初始化结构体,主要是初始化poll_wait的回调函数为__pollwait
   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(;;) {
       unsignedlong *rinp, *routp, *rexp, *inp, *outp, *exp;

       set_current_state(TASK_INTERRUPTIBLE);     //设置当前进程状态为TASK_INTERRUPTIBLE

       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) {
          unsignedlong in, out, ex, all_bits, bit = 1, mask, j;
          unsignedlong res_in = 0, res_out = 0, res_ex = 0;
          const structfile_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) {
             intfput_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)
                   mask= (*f_op->poll)(file, retval ? NULL : wait);  //调用poll_wait处理过程,即把驱动中等待队列头增加到poll_wqueues中的entry中,并把指向当前里程的等待队列项增加到等待队列头中。每一个等待队列头占用一个entry
               fput_light(file, fput_needed);
                if ((mask& POLLIN_SET) && (in & bit)) {
                   res_in |=bit;
                  retval++;
                }
                if ((mask& POLLOUT_SET) && (out & bit)) {
                   res_out |=bit;
                  retval++;
                }
                if ((mask& POLLEX_SET) && (ex & bit)) {
                   res_ex |=bit;
                  retval++;
                }
             }
          }
          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 (end_time&& !to) {
          expire =timespec_to_ktime(*end_time);
          to =&expire;
      }

       if(!schedule_hrtimeout_range(to, slack,HRTIMER_MODE_ABS))
          timed_out =1;
   }
   __set_current_state(TASK_RUNNING);

    poll_freewait(&table);    //从等待队列头中删除poll_wait中添加的等待队列,并释放资源

    returnretval;
}
从以上代码可以看出,当一个想从设备读数据的进程调用select,则内核调用poll_wait,把含当前进程的等待队列项添加到设备的等待列队上,然后简单的把当前里程设置为睡眠。直到有其它进程写设备,数据可获取,调用wakeup,唤醒当前进程。当前进程继续执行,再次调用poll后,有数据可读返回。
下面再看globalfifo的write过程,源代码如下:

static ssize_t globalfifo_write(struct file*filp, const char __user *buf,
  size_t count, loff_t*ppos)
{
  struct globalfifo_dev *dev=filp->private_data;    //获得设备结构体指针
  int ret;
  DECLARE_WAITQUEUE(wait,current);    //定义等待队列

 down(&dev->sem);    //获取信号量
 add_wait_queue(&dev->w_wait, &wait);    //进入写等待队列头

 
  if (dev->current_len ==GLOBALFIFO_SIZE)
  {
    if(filp->f_flags &O_NONBLOCK)
   //如果是非阻塞访问
   {
     ret =  - EAGAIN;
     goto out;
   
   __set_current_state(TASK_INTERRUPTIBLE);    //改变进程状态为睡眠
   up(&dev->sem);

   schedule(); //调度其他进程执行
    if(signal_pending(current))
   //如果是因为信号唤醒
   {
     ret =  - ERESTARTSYS;
     goto out2;
   }

   down(&dev->sem); //获得信号量
  }

 
  if (count >GLOBALFIFO_SIZE - dev->current_len)
   count = GLOBALFIFO_SIZE - dev->current_len;

  if(copy_from_user(dev->mem + dev->current_len, buf,count))
  {
   ret =  - EFAULT;
   goto out;
  }
  else
  {
   dev->current_len += count;
   printk(KERN_INFO "written %d bytes(s),current_len:%d\n", count,dev
     ->current_len);

    wake_up_interruptible(&dev->r_wait);//唤醒读等待队列,睡眠在读队列上的里程被唤醒。
    
   ret = count;
  }

  out: up(&dev->sem);//释放信号量
 out2:remove_wait_queue(&dev->w_wait, &wait);//从附属的等待队列头移除
 set_current_state(TASK_RUNNING);
  return ret;
}
读函数的阻塞机制同上,这里就不再详述。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值