设备驱动程序学习笔记(5)-休眠与唤醒

本文详细介绍了Linux中的休眠机制,包括手工休眠的步骤、独占等待的概念、唤醒的细节及简单睡眠的方法,并强调了几条重要的规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

By:             潘云登

Date:          2009-6-3

Email:         intrepyd@gmail.com

Homepage: http://blog.youkuaiyun.com/intrepyd

Copyright: 该文章版权由潘云登所有。可在非商业目的下任意传播和复制。

对于商业目的下对本文的任何行为需经作者同意。


写在前面

1.          本文内容对应《linux设备驱动程序》第六章。

2.          参考wait.hwait.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_INTERRUPTIBLETASK_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和联系方式funcflags就像一张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,就是前面的VIPJ,并将进程添加到等待队列的尾部。为什么不像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_upwake_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,它可以被信号中断。这个版本可返回一个整数值,非零值表示休眠被某个信号中断,这时驱动程序要返回-ERESTARTSYSwait_event_timeoutwait_event_interruptible_timeout会等待限定的时间,当给定的时间到期时,它们都会返回0值,而不管condition如何求值。

在实践中,约定的做法是在使用wait_event时使用wake_up,它会唤醒所有非独占等待进程;而在使用wait_event_interruptible时使用wake_up_interruptible,它只会唤醒那些执行可中断休眠的进程。


需要牢记的几条规则

Ø         永远不要在原子上下文中进入休眠,即不能在拥有自旋锁、seqlock或者RCU锁时休眠。

Ø         当进程被唤醒时,永远无法知道休眠了多长时间,或者休眠期间发生了什么事情。因此,对休眠后的状态不能做任何假定,必须检查以确保等待的条件真正为真。

Ø         除非知道有其他人会在其他地方唤醒我们,否则进程不能休眠。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值