如何让进程休眠(转)
时间:2010-10-20 来源:wwwzyf
休眠的步骤
将一个进程置于休眠状态,一般步骤如下:
0. 定义并初始化(如果还没有的话)一个等待队列头(wait_queue_head_t),这个等待队列头应该是能被要休眠的进程和负责唤醒的进程都能访问 到。
1. 对进程的每次休眠,定义并初始化一个等待队列(wait_queue_t)
2. 把等待队列加入到相应的等待队列头中。
3. 把进程状态置为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE
4. 再次检查休眠条件是否为真,否则跳过第5步
5. 执行 schedule()
6. 清理:将进程状态改为 TASK_RUNNING(通常已经是,除非是从第4步跳过来的),把等待队列从等待队列头中删除(防止多次唤醒)
7. 如果是可中断休眠的话(TASK_INTERRUPTIBLE),检查是否是因信号而被唤醒。如果是的话,一般直接 return -ERESTARTSYS 让上层处理。
8. 检查需要等待的条件是否满足,如果不是的话,再回到第1步重新开始。
注意,第4步 的检查条件非常重要。因为到第3步之前,条件可能已被满足,而如果我们再把进程状态设为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE ,直接执行 schedule() 的话就可能再也无法被调度到。但如果条件是在第4步到第5步之间满足的话,那不用担心,因为既然条件能满足,就应该有唤醒,而我们的进程状态应该又被设为 了 TASK_RUNNING,所以 schedule() 会调度到我们。
另外,以上只是一个一般的步骤,具体休眠时可根据个人的理解 和实际的需要灵活调整。
内核提供的操作方法
根据上面的步骤,内核提供了至少两种方法来帮助实现,我把他们称作是半 自动DIY式的和全自动傻瓜式。
半自动DIY式
wait_queue_head_t wq; init_waitqueue_head(&wq); --对应第0步
DEFINE_WAIT(wait); --对应第1步
prepare_to_wait(&wq, &wait, TASK_INTERRUPTIBLE); --对应第2,3步
if (condition) schedule(); --对应第4,5步
finish_wait(&wq, &wait); --对应第6步
if (signal_pending(current)) return -ERESTARTSYS; --对应第7步
全 自动傻瓜式
wait_queue_head_t wq; init_waitqueue_head(&wq); --对应第0步
int ret = wait_event_interruptible(wq, condition); --对应第1,2,3,4,5,6步和第7步前半步
if (ret != 0) return -ERESTARTSYS; --对应第7步后半步
如 果看内核代码的话,就可以发现wait_event系列其实就是对第1,2,3,4,5,6,7步的一个宏包装
唤醒
唤 醒的操作相对简单,就用 wake_up 系列函数。比如 wake_up_interruptible(&wq),它会把等待队列头(wq)上的所有进程都去唤醒。
实现细节
关 于休眠的内核实现细节,其实半自动DIY式的每一步所做的事都对应相应的休眠步骤中所描述的,只有第1步中初始化一个等待队列 (wait_queue_t)的概念比较模糊。
先看一下一个等待队列(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;
};
typedef struct __wait_queue wait_queue_t;
再看一下 DEFINE_WAIT 的宏定义:
#define DEFINE_WAIT(name) \
wait_queue_t name = { \
.private = current, \
.func = autoremove_wake_function, \
.task_list = LIST_HEAD_INIT((name).task_list), \
}
由此可见,private 字段被指向了当前进程,func字段是一个函数指针,当内核要唤醒一个等待队列时,内核就会调用 func 所指向的函数来唤醒进程,而 autoremove_wake_function() 就是内核提供的唤醒等待队列中进程的方法,它其实主要做两件事:1. 调用 default_wake_function() 来唤醒进程;2. 把等待队列从等待队列头中删除。
关于唤醒,内核最主要会调用到 __wake_up_common() 这个函数,下面是它的代码实现:
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, int sync, void *key)
{
wait_queue_t *curr, *next;
list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
unsigned flags = curr->flags;
if (curr->func(curr, mode, sync, key) && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}
其他变种
关于休眠和唤醒,内核还提供了一些其他变种的 方法,这里就不一一说明了,下面只列出了一些常用的宏和函数名称。
wait_event(queue, condition);
wait_event_interruptible(queue, condition);
wait_event_timeout(queue, condition, timeout);
wait_event_interruptible_timeout(queue, condition, timeout);
void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
void prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)
wake_up(queue)
wake_up_nr(queue, nr)
wake_up_interruptible(queue)
wake_up_interruptible_nr(queue, nr)
休眠规则
不要在原子上下文中休眠。
禁止中断时,也不能休眠。
要确保有进程 能唤醒自己。
休眠被唤醒之后仍要检查等待的条件是否为真,否则重新继续休眠。
将一个进程置于休眠状态,一般步骤如下:
0. 定义并初始化(如果还没有的话)一个等待队列头(wait_queue_head_t),这个等待队列头应该是能被要休眠的进程和负责唤醒的进程都能访问 到。
1. 对进程的每次休眠,定义并初始化一个等待队列(wait_queue_t)
2. 把等待队列加入到相应的等待队列头中。
3. 把进程状态置为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE
4. 再次检查休眠条件是否为真,否则跳过第5步
5. 执行 schedule()
6. 清理:将进程状态改为 TASK_RUNNING(通常已经是,除非是从第4步跳过来的),把等待队列从等待队列头中删除(防止多次唤醒)
7. 如果是可中断休眠的话(TASK_INTERRUPTIBLE),检查是否是因信号而被唤醒。如果是的话,一般直接 return -ERESTARTSYS 让上层处理。
8. 检查需要等待的条件是否满足,如果不是的话,再回到第1步重新开始。
注意,第4步 的检查条件非常重要。因为到第3步之前,条件可能已被满足,而如果我们再把进程状态设为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE ,直接执行 schedule() 的话就可能再也无法被调度到。但如果条件是在第4步到第5步之间满足的话,那不用担心,因为既然条件能满足,就应该有唤醒,而我们的进程状态应该又被设为 了 TASK_RUNNING,所以 schedule() 会调度到我们。
另外,以上只是一个一般的步骤,具体休眠时可根据个人的理解 和实际的需要灵活调整。
内核提供的操作方法
根据上面的步骤,内核提供了至少两种方法来帮助实现,我把他们称作是半 自动DIY式的和全自动傻瓜式。
半自动DIY式
wait_queue_head_t wq; init_waitqueue_head(&wq); --对应第0步
DEFINE_WAIT(wait); --对应第1步
prepare_to_wait(&wq, &wait, TASK_INTERRUPTIBLE); --对应第2,3步
if (condition) schedule(); --对应第4,5步
finish_wait(&wq, &wait); --对应第6步
if (signal_pending(current)) return -ERESTARTSYS; --对应第7步
全 自动傻瓜式
wait_queue_head_t wq; init_waitqueue_head(&wq); --对应第0步
int ret = wait_event_interruptible(wq, condition); --对应第1,2,3,4,5,6步和第7步前半步
if (ret != 0) return -ERESTARTSYS; --对应第7步后半步
如 果看内核代码的话,就可以发现wait_event系列其实就是对第1,2,3,4,5,6,7步的一个宏包装
唤醒
唤 醒的操作相对简单,就用 wake_up 系列函数。比如 wake_up_interruptible(&wq),它会把等待队列头(wq)上的所有进程都去唤醒。
实现细节
关 于休眠的内核实现细节,其实半自动DIY式的每一步所做的事都对应相应的休眠步骤中所描述的,只有第1步中初始化一个等待队列 (wait_queue_t)的概念比较模糊。
先看一下一个等待队列(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;
};
typedef struct __wait_queue wait_queue_t;
再看一下 DEFINE_WAIT 的宏定义:
#define DEFINE_WAIT(name) \
wait_queue_t name = { \
.private = current, \
.func = autoremove_wake_function, \
.task_list = LIST_HEAD_INIT((name).task_list), \
}
由此可见,private 字段被指向了当前进程,func字段是一个函数指针,当内核要唤醒一个等待队列时,内核就会调用 func 所指向的函数来唤醒进程,而 autoremove_wake_function() 就是内核提供的唤醒等待队列中进程的方法,它其实主要做两件事:1. 调用 default_wake_function() 来唤醒进程;2. 把等待队列从等待队列头中删除。
关于唤醒,内核最主要会调用到 __wake_up_common() 这个函数,下面是它的代码实现:
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, int sync, void *key)
{
wait_queue_t *curr, *next;
list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
unsigned flags = curr->flags;
if (curr->func(curr, mode, sync, key) && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}
其他变种
关于休眠和唤醒,内核还提供了一些其他变种的 方法,这里就不一一说明了,下面只列出了一些常用的宏和函数名称。
wait_event(queue, condition);
wait_event_interruptible(queue, condition);
wait_event_timeout(queue, condition, timeout);
wait_event_interruptible_timeout(queue, condition, timeout);
void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
void prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)
wake_up(queue)
wake_up_nr(queue, nr)
wake_up_interruptible(queue)
wake_up_interruptible_nr(queue, nr)
休眠规则
不要在原子上下文中休眠。
禁止中断时,也不能休眠。
要确保有进程 能唤醒自己。
休眠被唤醒之后仍要检查等待的条件是否为真,否则重新继续休眠。
相关阅读 更多 +