设备驱动程序学习笔记(5)-休眠与唤醒
来源:互联网 发布:张怡宁 知乎 编辑:程序博客网 时间:2024/05/22 14:56
Date: 2009-6-3
Email: intrepyd@gmail.com
Homepage:http://blog.csdn.net/intrepyd
Copyright:该文章版权由潘云登所有。可在非商业目的下任意传播和复制。
对于商业目的下对本文的任何行为需经作者同意。
写在前面
1. 本文内容对应《linux设备驱动程序》第六章。
2. 参考wait.h和wait.c两个文件。
3. 希望本文对您有所帮助,也欢迎您给我提意见和建议。
4. 本文包含以下内容:
² 手工休眠步骤
² 休眠的背后
² 独占等待
² 唤醒细节
² 简单睡眠
² 需要牢记的几条规则
手工休眠步骤
1. 首先,建立并初始化一个等待队列入口,即wait_queue_t结构。
/*静态声明*/
DEFINE_WAIT(my_wait);
/*运行时初始化*/
wait_queue_t my_wait;
init_wait(&my_wait);
2. 然后,将等待队列入口添加到等待队列中,即wait_queue_head_t结构,并设置进程的状态。
void prepare_to_wait(wait_queue_head_t *queue,
wait_queue_t *wait,
int state);
其中, queue为等待队列,state是进程的新状态,为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE。
3. 检查休眠等待的条件,确定有必要休眠后,调用schedule,让出CPU。
if (!condition)
schedule( );
4. 当schedule返回后,修改进程状态为TASK_RUNNING,将等待队列入口从等待队列中移出,这些都通过finish_wait完成。
void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);
这时候,不能保证休眠等待的条件已经成立,必须再次判断是否需要重新等待。因此,一个典型的手工休眠模型如下所示:
wait_queue_head_t queue;
init_waitqueue_head(&queue);
while (!condition) {
/* 步骤1 */
DEFINE_WAIT(wait);
/* 如果是非阻塞操作,无需休眠 */
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
/* 步骤2 */
prepare_to_wait(&queue, &wait, TASK_INTERRUPTIBLE);
/* 步骤3 */
if (!condition)
schedule( );
/* 步骤4 */
finish_wait(&dev->outq, &wait);
}
休眠的背后
休眠机制与信号量机制有些类似:当一个人走到房间前,发现房间被其他人占用(休眠等待条件不成立);于是,他在访客名单上留下个人信息(加入等待队列);然后去做其它事情,而不是在门外守着(让出CPU);当房里的人出来后,便呼唤名单上的访客(唤醒)。不同之处在于:访客在名单上留下个人信息后,会敲一下门,以确认房间内的人没有离开;另外,当访客被呼唤回来后,会再次敲门,确认是否有其他访客在他之前进入了房间。
在这个机制中,等待队列和等待队列入口分别充当了名单和个人信息的角色。先来看一下它们的结构:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
typedef struct __wait_queue wait_queue_t;
struct __wait_queue {
unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
其中,除了保证互斥访问的自旋锁和连接结构的链表头外,最重要的信息就是访客的姓名private和联系方式func。flags就像一张VIP卡,用来标识独占等待。有趣的是,每个访客的个人信息看起来都一样,即当前进程current和回调函数autoremove_wake_function,它们在初始化时设置。
#define DEFINE_WAIT(name) /
wait_queue_t name = { /
.private = current, /
.func = autoremove_wake_function, /
.task_list = LIST_HEAD_INIT((name).task_list),/
}
这样,prepare_to_wait只需要修改进程状态,并利用list_add方法把等待队列入口加入到等待队列的头部。然后,由finish_wait进行相反的操作。可以在<kernel/wait.c>中找到它们。
独占等待
通常,所有等待队列上的进程都会在唤醒时被置为可运行状态。有时候,只有一个被唤醒的进程可以获得期望的资源,而其它的进程会再次休眠。因此,当对某个资源存在严重竞争,并且唤醒单个进程就能完整消耗该资源的时候,可以使用独占等待提高系统性能。
要使用独占等待,可以使用prepare_to_wait_exclusive函数替换prepare_to_wait。它设置等待队列入口的独占标志flags,就是前面的VIP卡J,并将进程添加到等待队列的尾部。为什么不像prepare_to_wait那样加到队列头部呢?等你看完下面的唤醒细节,也许就明白了。
void fastcall
prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
……
wait->flags |= WQ_FLAG_EXCLUSIVE;
……
__add_wait_queue_tail(q, wait);
……
}
唤醒细节
当房间里的人走出来后,他会查看名单上的访客信息,然后通过他们留下的联系方式挨个联系。这通过wake_up宏或其变种完成,而真正调用的都是__wake_up方法。
#define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_nr(x, nr) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_all(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 0, NULL)
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_interruptible_nr(x, nr)__wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
void fastcall __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);
}
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, int sync, void *key)
{
struct list_head *tmp, *next;
list_for_each_safe(tmp, next, &q->task_list) {
wait_queue_t *curr;
unsigned flags;
curr = list_entry(tmp, wait_queue_t, task_list);
flags = curr->flags;
if (curr->func(curr, mode, sync, key) &&
(flags & WQ_FLAG_EXCLUSIVE) &&
!--nr_exclusive)
break;
}
}
在__wake_up中,确切地说,应该是在__wake_up_common中,从头部开始遍历等待队列,并调用等待队列入口项初始化时指定的func回调函数,将进程设置为可运行状态,并且如果该进程具有更高的优先级,则会执行一次上下文切换以便切换到该进程。
这时候,如果有VIP访客在,则遍历会在唤醒nr_exclusive个独占等待的进程后停止。nr_exclusive根据不同的宏调用进行设置。常用的wake_up和wake_up_interruptible宏,会在唤醒队列中第一个具有WQ_FLAG_EXCLUSIVE标志的进程后停止遍历。由于独占等待入口项总是被加入到队列的尾部,因此最先加入等待队列的VIP进程会先被唤醒。此外,处于等待队列头部的非独占等待进程,在每一次wake_up的时候,都将被唤醒。
简单睡眠
随着社会的不断进步,生产工具也得到了不断的改进。现在你不再需要手工休眠,只要调用wait_event系列宏之一,便可轻松入眠。唯一的缺憾是,无法设置独占等待。
wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
#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)
如果使用wait_event,进程被置于非中断休眠。最好的选择是使用wait_event_interruptible,它可以被信号中断。这个版本可返回一个整数值,非零值表示休眠被某个信号中断,这时驱动程序要返回-ERESTARTSYS。wait_event_timeout和wait_event_interruptible_timeout会等待限定的时间,当给定的时间到期时,它们都会返回0值,而不管condition如何求值。
在实践中,约定的做法是在使用wait_event时使用wake_up,它会唤醒所有非独占等待进程;而在使用wait_event_interruptible时使用wake_up_interruptible,它只会唤醒那些执行可中断休眠的进程。
需要牢记的几条规则
Ø 永远不要在原子上下文中进入休眠,即不能在拥有自旋锁、seqlock或者RCU锁时休眠。
Ø 当进程被唤醒时,永远无法知道休眠了多长时间,或者休眠期间发生了什么事情。因此,对休眠后的状态不能做任何假定,必须检查以确保等待的条件真正为真。
Ø 除非知道有其他人会在其他地方唤醒我们,否则进程不能休眠。
- 设备驱动程序学习笔记(5)-休眠与唤醒
- s3c6410 休眠与唤醒笔记
- 字符设备中添加系统休眠与唤醒接口
- android 休眠与唤醒
- S3C6410 休眠与唤醒
- Android 休眠与唤醒
- linux 休眠与唤醒
- linux 休眠与唤醒
- android 休眠与唤醒
- Android休眠与唤醒
- Android休眠与唤醒
- linux 休眠与唤醒
- S3C6410 休眠与唤醒
- linux 休眠与唤醒
- linux 休眠与唤醒
- Linux休眠与唤醒
- linux 休眠与唤醒
- Linux设备驱动程序--学习笔记(5)
- c#类的初始化顺序
- Nginx模块开发指南(Emiller) -
- C# 中的常用正则表达式总结 (转)
- Android 常用代码---SQL
- 设计模式之策略模式
- 设备驱动程序学习笔记(5)-休眠与唤醒
- Linux下使用system()函数一定要谨慎
- IDEA Inspector对serialVersionUID不自动生成
- ArcGIS API for Silverlight调用GP生成等值线输入要素集FeatureSet
- POJ 3680 - intervals 一类分配任务,有重叠限制的模型..最大费用最大流..
- Android 常用代码---WEB
- 第一次笔试题
- ubuntu(linux) jdk 1.7配置
- Struts 2 动态方法调用