驱动开发之始,(四)阻塞型I/O,进程休眠

在前面的章节中,我们讲述了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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值