您的位置:首页 > 移动开发 > Android开发

Android深度探索:HAL与驱动开发学习笔记--等待队列

2017-09-28 09:34 381 查看
一、什么是睡眠

    对于一个进程"睡眠"意味着什么? 当一个进程被置为睡眠, 它被标识为处于一个特殊的状态并且从调度器的运行队列中去除. 直到发生某些事情改变了那个状态, 这个进程将不被在任何 CPU 上调度, 并且, 因此, 将不会运行. 一个睡着的进程已被搁置到系统的一边, 等待以后发生事件.

    LDD3说得很玄乎,睡眠是“自愿调度”,其实就是将当前进程的状态设置为 TASK_INTERRUPTIBLE 等状态,然后schedule() 让出CPU1,让调度器重新选择一个进程来执行。

    对于一个 Linux 驱动使一个进程睡眠是一个容易做的事情. 但是, 有几个规则必须记住以安全的方式编码睡眠.

这些规则的第一个是: 当你运行在原子上下文时不能睡眠.  

    一个另外的相关的点, 当然, 是你的进程不能睡眠除非确信其他人, 在某处的, 将唤醒它.做唤醒工作的代码必须也能够找到你的进程来做它的工作. 确保一个唤醒发生, 是深入考虑你的代码和对于每次睡眠, 确切知道什么系列的事件将结束那次睡眠.使你的进程可能被找到, 真正地, 通过一个称为等待队列的数据结构实现的. 一个等待队列就是它听起来的样子:一个进程列表, 都等待一个特定的事件.

二、如何睡眠

    在 Linux 中, 一个等待队列由一个"等待队列头"来管理, 一个 wait_queue_head_t 类型的结构, 定义在<linux/wait.h>中. 一个等待队列头可被定义和初始化, 使用:

    DECLARE_WAIT_QUEUE_HEAD(name); 

    或者动态地, 如下:

    wait_queue_head_t my_queue;
    init_waitqueue_head(&my_queue);

等待队列本质上是一双向链表,有等待队列头和队列节点构成,当运行的线程要获得某一个资源二暂不可得时,线程有时候需要等待,此时它可以进入睡眠状态,内核为此生成一个新的等待队列节点将睡眠的线程挂载到等待队列中。

等待队列用于使得进程等待某一特定事件的发生,无需频繁的轮询,进程在等待周期中睡眠,当时间发生后由内核自动唤醒。

  1、简单睡眠    

    Linux 内核中睡眠的最简单方式是一个宏定义, 称为 wait_event(有几个变体); 它结合了处理睡眠的细节和进程在等待的条件的检查. wait_event 的形式是:

    wait_event(queue, condition)

    wait_event_interruptible(que
4000
ue, condition)

    wait_event_timeout(queue, condition, timeout)

    wait_event_interruptible_timeout(queue, condition, timeout)

    这些东西如何使用?queue 是等待队列头,condition 是条件,如果调用 wait_event 前 condition == 0 ,则调用 wait_event 之后,当前进程就会休眠。

    那么它们四个又有什么不同?

    wait_event:将当前进程的状态设置为 TASK_UNINTERRUPTIBLE  ,然后 schedule()

    wait_event_interruptible:         TASK_INTERRUPTIBLE    ,然后 schedule()

    wait_event_timeout:               TASK_UNINTERRUPTIBLE  ,然后 schedule_timeout()

    wait_event_interruptible_timeout:  TASK_INTERRUPTIBLE    , 然后 schedule_timeout()

    TASK_INTERRUPTIBLE 与 TASK_UNINTERRUPTIBLE 区别在于,它的休眠是否会被信号打断,别的进程发来一个信号比如 kill ,TASK_INTERRUPTIBLE 就会醒来去处理。然而 TASK_UNINTERRUPTIBLE 不会。schedule(),进程调度,而schedule_timeout()进行调度之后,一定时间后自动唤醒。

    对应于不同的进程状态,使用不同的唤醒函数:

    void wake_up(wait_queue_head_t *queue);

    void wake_up_interruptible(wait_queue_head_t *queue);

    唤醒时很有意思,比如你调用 wake_up 去唤醒一个使用 wait_event 等,进入休眠的进程,唤醒之后,它会判断 condition 是否为真,如果还是假的继续睡眠。至于为什么这个样子,后面分析代码就会明白。

  

  2、手动睡眠

    DECLARE_WAITQUEUE(name, tsk)  创建一个等待队列:

        tsk一般为当前进行current. 这个宏定义并初始化一个名为name的等待队列.

    将等待队列头 加入/移除 等待队列:

        void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

        void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);

        void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

    设置进程状态:

        set_current_state(TASK_INTERRUPTIBLE) 等

    进程调度:  

        schedule() 或者 schedule_timeout()

三、内核如何实现

    以 wait_event 为例,我们看看内核都干了些什么。

[cpp] view plain copy

#define wait_event(wq, condition)                   \  

do {                                    \  

    if (condition)                          \  

        break;                          \  

    __wait_event(wq, condition);                    \  

} while (0)  

[cpp] view plain copy

#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)  

[cpp] view plain copy

#define DEFINE_WAIT(name)                       \  

    wait_queue_t name = {                       \  

        .private    = current,              \  

        .func       = autoremove_wake_function,     \  

        .task_list  = LIST_HEAD_INIT((name).task_list), \  

    }  

[cpp] view plain copy

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;  

};  

举个例子:宏展开之后

[cpp] view plain copy

__wait_event(wq, condition);  

wait_queue_t __wait = {                     \  

        .private    = current,              \  

        .func       = autoremove_wake_function,     \  

        .task_list  = LIST_HEAD_INIT((__wait).task_list),   \  

    }  

    for (;;) {                          \  

        prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \  

        if (condition)                      \  

            break;                      \  

        schedule();                     \  

    }                               \  

    finish_wait(&wq, &__wait);  

    其实,它定义了一个叫 __wait 的等待队列,private 指向当前进程的 task_struct 结构体(唤醒的时候好知道是哪个进程),然后调用 prepare_to_wait 将等待队列头加入到等待队列中去,并设置当前进程的状态为TASK_UNINTERRUPTIBLE。然后,如果 condition 为假,则schedule(),进程调度的时候,当前进程的状态不是 TASK_RUNNING 必然要被移除 “运行队列”,也就永远不会被调度除非直到醒来。如果 condition 为真,那么finish_wait
会把之前的工作都还原,你继续执行吧,你要的条件都满足了,你还休眠个屁!

    涉及的函数代码贴一贴

[cpp] view plain copy

void fastcall  

prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)  

{  

    unsigned long flags;  

  

    wait->flags &= ~WQ_FLAG_EXCLUSIVE;  

    spin_lock_irqsave(&q->lock, flags);  

    if (list_empty(&wait->task_list))  

        __add_wait_queue(q, wait);  

    /* 

     * don't alter the task state if this is just going to 

     * queue an async wait queue callback 

     */  

    if (is_sync_wait(wait))  

        set_current_state(state);  

    spin_unlock_irqrestore(&q->lock, flags);  

}  

[cpp] view plain copy

void fastcall finish_wait(wait_queue_head_t *q, wait_queue_t *wait)  

{  

    unsigned long flags;  

  

    __set_current_state(TASK_RUNNING);  

  

    if (!list_empty_careful(&wait->task_list)) {  

        spin_lock_irqsave(&q->lock, flags);  

        list_del_init(&wait->task_list);  

        spin_unlock_irqrestore(&q->lock, flags);  

    }  

}  

    下面来看看如何唤醒的

[cpp] view plain copy

#define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)  

[cpp] view plain copy

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);  

}  

[cpp] view plain copy

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 = list_entry(tmp, wait_queue_t, task_list);  

        unsigned flags = curr->flags;  

  

        if (curr->func(curr, mode, sync, key) &&  

                (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)  

            break;  

    }  

}  

   此时会调用到,我们在等待队列里指定的那个 func 函数,也就是 autoremove_wake_function

[cpp] view plain copy

int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)  

{  

    int ret = default_wake_function(wait, mode, sync, key);  

  

    if (ret)  

        list_del_init(&wait->task_list);  

    return ret;  

}  

[cpp] view plain copy

int default_wake_function(wait_queue_t *curr, unsigned mode, int sync,  

              void *key)  

{  

    return try_to_wake_up(curr->private, mode, sync);  

}  

   最终调用到 default_wake_function 来唤醒 等待队列里 private 里指定的那个进程。然后,移除将等待队列头移除等待队列。try_to_wake_up ,会将 要唤醒进程的 进程状态设置为 TASK_RUNNING ,然后放到 “运行队列”中。

有意思的是:

    我们休眠时,schedule() 在哪里? TMD 居然在 for 循环里

[cpp] view plain copy

for (;;) {                          \  

    prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \  

    if (condition)                      \  

        break;                      \  

    schedule();                     \  

}  

    唤醒之后,那么又开始了 prepare_to_wait ,判断 condition ....显然 condition 为真,才会真正的 唤醒。

    理解了他们,对于手动休眠也就很明白了。手动休眠就不用判断什么 condition 了。

[cpp] view plain copy

DECLARE_WAITQUEUE(name, tsk)  

tsk一般为当前进行current. 这个宏定义并初始化一个名为name的等待队列.  

  

#define DECLARE_WAITQUEUE(name, tsk)                    \  

    wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)  

#define __WAITQUEUE_INITIALIZER(name, tsk) {                \  

    .private    = tsk,                        \  

    .func        = default_wake_function,            \  

    .task_list    = { NULL, NULL } }  

      

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);  

void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);  

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);  

  

void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)  

{  

    unsigned long flags;  

  

    wait->flags &= ~WQ_FLAG_EXCLUSIVE;  

    spin_lock_irqsave(&q->lock, flags);  

    __add_wait_queue(q, wait);  

    spin_unlock_irqrestore(&q->lock, flags);  

}  

  

set_current_state(TASK_INTERRUPTIBLE);    

schedule();  

   简单明了:

    1、创建等待队列、等待队列头

    2、将等待队列头加入到等待队列中去

    3、设置当前进程的进程状态

    4、进程调度~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: