进程休眠的方法

休眠的步骤
将一个进程置于休眠状态,一般步骤如下:
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)

休眠规则
不要在原子上下文中休眠。
禁止中断时,也不能休眠。
要确保有进程能唤醒自己。
休眠被唤醒之后仍要检查等待的条件是否为真,否则重新继续休眠。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/iczyh/archive/2008/11/09/3260260.aspx

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值