试想如果在驱动方法中的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);
休眠宏:
- wait_event(queue,condition);
- wait_event_interruptible(queue,condition);
- wait_event_timeout(queue,condition,timeout);
- wait_event_interruptible_timeout(queue,condition,timeout);
上述宏的调用如果condition条件不满足将引起调用进程的阻塞,并且宏会在休眠前后对condition求值,从名字可以看出带interruptible的宏用户可以中断休眠,返回非0值表示被中断,此时驱动程序要返回 -ERESTARTSYS.而带timeout的宏表示在给定的时间到期时,宏都会返回0.
唤醒函数:
- void wake_up(wait_queue_head_t *queue);
- 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(),记住在调用之前,必须进行阻塞条件的检查。如下所示:
- if(!condition)
- 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
经过上面的一些理论阐述,相信代码应该很好理解了吧:
- static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count,
- loff_t *f_pos)
- {
- struct scull_pipe *dev = filp->private_data;
- if (down_interruptible(&dev->sem))
- return -ERESTARTSYS;
- while (dev->rp == dev->wp) { /* nothing to read */
- up(&dev->sem); /* release the lock */
- if (filp->f_flags & O_NONBLOCK)
- return -EAGAIN;
- PDEBUG("\"%s\" reading: going to sleep\n", current->comm);
- if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
- return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
- /* otherwise loop, but first reacquire the lock */
- if (down_interruptible(&dev->sem))
- return -ERESTARTSYS;
- }
- /* ok, data is there, return something */
- if (dev->wp > dev->rp)
- count = min(count, (size_t)(dev->wp - dev->rp));
- else /* the write pointer has wrapped, return data up to dev->end */
- count = min(count, (size_t)(dev->end - dev->rp));
- if (copy_to_user(buf, dev->rp, count)) {
- up (&dev->sem);
- return -EFAULT;
- }
- dev->rp += count;
- if (dev->rp == dev->end)
- dev->rp = dev->buffer; /* wrapped */
- up (&dev->sem);
- /* finally, awake any writers and return */
- wake_up_interruptible(&dev->outq);
- PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count);
- return count;
- }
阅读(1336) | 评论(0) | 转发(0) |