Chinaunix首页 | 论坛 | 博客
  • 博客访问: 113296
  • 博文数量: 23
  • 博客积分: 471
  • 博客等级: 一等列兵
  • 技术积分: 251
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-21 15:21
文章分类
文章存档

2017年(1)

2013年(2)

2011年(20)

分类: LINUX

2011-08-19 10:19:02

  试想如果在驱动方法中的read/write中,当数据不可用时,用户可能调用read,当输出缓冲区满时,设备并未准备好接受数据,这种情况下驱动程序可以阻塞该进程,并且置入休眠状态直到满足条件。
  下面就介绍高级字符驱动操作中的阻塞型I/O的实现
 
2.阻塞型I/O
 
   2.1 休眠
  
   休眠对进程意味着什么呢?当一个进程休眠时,它会被标记为一种特殊状态并从调度器的运行队列中移走,直到某些情况修改了这个状态,进程才会在任意cpu上调度,即运行该进程。
   在linux下,为了让进程安全的进入休眠状态,有两条规则需要牢记:
 
   R1:永远不要再原子上下文中休眠。原子上下文:在执行多个步骤时,不能有任何并发访问。不能再拥有自旋锁,seqlock,rcu锁时休眠,进程1在拥有信号量时休眠是合法的,但是要确保进程1拥有信号量不会阻塞唤醒我们的那个进程2。
 
   R2:对唤醒之后的状态不能做任何假定,必须检查以确保我们等待的条件为真。
———————————————————————————————————————————————
  
   2.1.1 简单休眠
   
   等待队列:一个进程链表,包含了等待某个特定事件的所有进程。
   在linux中,一个等待队列通过一个"wait queue head"来管理,类型为 wait_queue_head_t,在中定义。
   初始化一个等待队列有两种方法:
 
   1.静态
     DECLARE_WAIT_QUEUE_HEAD(my_queue);
   2.动态
     wait_queue_head_t my_queue;
     init_waitqueue_head(&my_queue);
 
   休眠宏: 
  1. wait_event(queue,condition);
  2. wait_event_interruptible(queue,condition);
  3. wait_event_timeout(queue,condition,timeout);
  4. wait_event_interruptible_timeout(queue,condition,timeout);

上述宏的调用如果condition条件不满足将引起调用进程的阻塞,并且宏会在休眠前后对condition求值,从名字可以看出带interruptible的宏用户可以中断休眠,返回非0值表示被中断,此时驱动程序要返回      -ERESTARTSYS.而带timeout的宏表示在给定的时间到期时,宏都会返回0.

   唤醒函数:

  1. void wake_up(wait_queue_head_t *queue);
  2. void wake_up_interruptible(wait_queue_head_t *queue);

wake_up会唤醒在queue上的所有进程,而wake_up_interruptible只唤醒那些可中断的进程。

   2.1.2 高级休眠

   为了加深对linux等待队列机制的理解,我们讨论一些底层的技术细节。

   进程如何休眠?

   step1:分配初始化一个wait_queue_t 结构,然后加入到对应的等待队列。

   step2:通过函数set_current_state(int new_state)设置进程状态,驱动程序关心的进程状态主要是TASK_RUNNING(可运行),TASK_INTERRUPTIBLE(可中断休眠),TASK_UNINTERRPUTIBLE(不可中断休眠)。

   step3:调用schedule(),记住在调用之前,必须进行阻塞条件的检查如下所示:

  1. if(!condition)
  2.   schedule();

   等待队列上的独占等待进程

   独占等待进程与普通休眠进程的亮点不同:

   1.等待队列入口设置了WQ_FLAG_EXCLUSIVE标志,并且添加到等待队列的尾部,没有这个标志的进程被添加到等待队列的头部。

   2.在某个等待队列上调用wake_up时,它会唤醒第一个具有WQ_FLAG_EXCLUSIVE标志的进程之后再唤醒其他进程。

   什么时候采用独占等待进程呢?满足以下两个条件可以考虑:

   1.对某个资源存在严重竞争。

   2.唤醒单个进程就能完整消耗该资源。

   2.2 阻塞和非阻塞操作

   2.2.1设置非阻塞I/O

   显式的非阻塞I/O由filp->f_flags中的O_NONBLOCK标志决定,为了保持和System V代码兼容,标志O_NDELAY和O_NONBLOCK是一个意思,非阻塞io的read和write会返回-EAGAIN.

   应用程序有两种方式制定非阻塞IO:

   1.在open的时候指定,用于在open调用可能会阻塞很长的时间。

   2.调用fcntl函数。具体使用方法可在网上查哦这里就不列出了。

   2.2.3阻塞操作的标准语义

   如果一个进程调用了read但是没有数据可读,进程阻塞,数据到达时进程被唤醒,并把数据返回给调用者,即使数据少于count;如果一个进程调用了write但是缓冲区满,该进程必须阻塞,休眠在与read进程不同的等待队列上,当缓冲区有空闲时,进程被唤醒,写入数据成功,即使写入了小于count的字节数。

   2.2.4一个阻塞IO的例子scullp

经过上面的一些理论阐述,相信代码应该很好理解了吧:

  1. static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count,
  2.                 loff_t *f_pos)
  3. {
  4.         struct scull_pipe *dev = filp->private_data;

  5.         if (down_interruptible(&dev->sem))
  6.                 return -ERESTARTSYS;

  7.         while (dev->rp == dev->wp) { /* nothing to read */
  8.                 up(&dev->sem); /* release the lock */
  9.                 if (filp->f_flags & O_NONBLOCK)
  10.                         return -EAGAIN;
  11.                 PDEBUG("\"%s\" reading: going to sleep\n", current->comm);
  12.                 if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
  13.                         return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
  14.                 /* otherwise loop, but first reacquire the lock */
  15.                 if (down_interruptible(&dev->sem))
  16.                         return -ERESTARTSYS;
  17.         }
  18.         /* ok, data is there, return something */
  19.         if (dev->wp > dev->rp)
  20.                 count = min(count, (size_t)(dev->wp - dev->rp));
  21.         else /* the write pointer has wrapped, return data up to dev->end */
  22.                 count = min(count, (size_t)(dev->end - dev->rp));
  23.         if (copy_to_user(buf, dev->rp, count)) {
  24.                 up (&dev->sem);
  25.                 return -EFAULT;
  26.         }
  27.         dev->rp += count;
  28.         if (dev->rp == dev->end)
  29.                 dev->rp = dev->buffer; /* wrapped */
  30.         up (&dev->sem);

  31.         /* finally, awake any writers and return */
  32.         wake_up_interruptible(&dev->outq);
  33.         PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count);
  34.         return count;
  35. }
阅读(1336) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~