在前面的章节中,我们讲述了read和write方法。当数据不可用时,用户调用read,或者用户使用写入数据,但输出缓冲区已满,驱动程序该如何相应呢?在这种情况下,驱动程序应该(默认)阻塞该进程,将其置入休眠状态直到请求可继续。
1.休眠(sleep)
当一个进程被置入休眠时,它会被标记为一种特殊状态并从调度器的运行队列中移走,休眠中的进程会被搁置在一边,等待将来的某个事件发生,直到某些情况下修改了这个状态,进程才会在任意CPU上被调度。
为将进程以安全的方式进入休眠,必须遵守两条规则:
1.永远不要在原子上下文(在执行多个步骤时,不能有任何的并发访问)中进入休眠。
a.对休眠来说,驱动程序不能在拥有锁是休眠。
b.禁止中断时不能休眠。
c.拥有信号量时可以休眠,但要保证休眠的代码很短,并且还要确保拥有信号量不会阻塞最终唤醒自己的那个进程。
2.必须检查确保休眠等待的条件为真。
我们无法知道休眠期间发生了什么事,无法知道是否还有其他进程在同一事件上休眠,这个进程可能在驱动被唤醒之前拿走该进程等待的资源。
关于休眠的另一个问题,我们需要确定是否有其他进程会唤醒被休眠的进程,并且清楚地知道对每个休眠而言,哪些事件会结束休眠。能够找到休眠的进程意味着,需要维护一个称为等待队列的数据结构,等待队列就是一个进程链表,其中包含了等待某个特定事件的所有进程。
在Linux中,一个等待队列通过一个“等待队列头”(wait queue head)来管理,等待队列头是一个类型为wait_queue_head_t,在<linux/wait.h>中可用过静态定义并初始化一个等待队列头:
DECLARE_WAIT_QUEUE_HEAD(name);
使用动态方法:
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue)
2.简单休眠
当一个休眠进程被唤醒时,它必须再次检查它所等待的条件的确为真,Linux内核中最简单的休眠方式是wait_event
#define wait_event(wq, condition) \
do { \
if (condition) \
break; \
__wait_event(wq, condition); \
} while (0)
#define __wait_event(wq, condition) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
if (condition) \
break; \
schedule(); \
} \
finish_wait(&wq, &__wait); \
} while (0)
@wq是等待队列头,注意它是通过“值传递”
@condition是一个C表达式,在事件为真之前,进程保持休眠
最好选择wait_event_interruptible(),它可被信号中断
#define wait_event_interruptible(wq, condition) \
({ \
int __ret = 0; \
if (!(condition)) \
__wait_event_interruptible(wq, condition, __ret); \
__ret; \
})
#define __wait_event_interruptible_timeout(wq, condition, ret) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \
if (condition) \
break; \
if (!signal_pending(current)) { \
ret = schedule_timeout(ret); \
if (!ret) \
break; \
continue; \
} \
ret = -ERESTARTSYS; \
break; \
} \
if (!ret && (condition)) \
ret = 1; \
finish_wait(&wq, &__wait); \
} while (0)
用来唤醒休眠进程的基本函数是wake_up
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
void __wake_up(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, void *key)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr_exclusive, 0, key);
spin_unlock_irqrestore(&q->lock, flags);
}
@q是等待队列
@mode 哪一个线程
@多少个线程需要被唤醒
@key 可设置为NULL
wake_up_interruptible只会唤醒那些执行可中断休眠的进程
#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_exclusive, void *key)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr_exclusive, 0, key);
spin_unlock_irqrestore(&q->lock, flags);
}
在使用中,约定wait_event时使用wait_up,在使用wait_event_interruptible时使用wake_up_interruptible。
3.进程如何休眠
在<linux/wait.h>文件中,wait_queue_head_t类型的数据结构,它由一个自旋锁和一个链表组成。链表中保存的是一个等待队列入口,该入口声明为wait_queue_t类型
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
将进程置于休眠
step1:通常是分配并初始化一个wait_queue_t结构,然后将其加入到对应的等待队列。
step2:设置进程的状态,将其标记为休眠,在<linux/sched.h>中定义了多个任务状态
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
TASK_RUNNING表示进程可运行。有两个状态表明进程处于休眠状态:TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE。