linux 等待队列

一、等待队列介绍 

  linux 内核的等待队列和进程调度息息相关,进程在某些情况下必须等待某些事件的发生,例如:等待一个磁盘操作的终止,等待释放系统资源,或等待指定的时间间隔。

等待队列实现了在事件上的条件等待:希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制权。

  下面以llinx 4.9为例介绍等待队列,等待队列的定义和接口函数都在头文件linux/include/wait.h中,等待队列的实现在linux/kernel/sched/wait.c中

  linux 用wait_queue_t表示等待队列,如下所示

typedef struct __wait_queue wait_queue_t;
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);/* __wait_queue::flags */
#define WQ_FLAG_EXCLUSIVE    0x01
#define WQ_FLAG_WOKEN        0x02

struct __wait_queue {
    unsigned int        flags;
    void            *private;
    wait_queue_func_t    func;
    struct list_head    task_list;
};

  用wait_queue_head_t表示等待队列的头部

struct __wait_queue_head {
    spinlock_t        lock;
    struct list_head    task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

 

二、等待队列定义和接口函数

2.1 定义等待队列和等待队列头部

  可以用DECLARE_WAITQUEUE(name, tsk)来定义等待队列,如DECLARE_WAITQUEUE(yy_wait, current)

DECLARE_WAITQUEUE是个宏,最终是调用了宏__WAITQUEUE_INITIALIZER来初始化等待队列接头体中的成员,如下所示:
#define __WAITQUEUE_INITIALIZER(name, tsk) {                \
    .private    = tsk,                        \
    .func        = default_wake_function,            \
    .task_list    = { NULL, NULL } }

#define DECLARE_WAITQUEUE(name, tsk)                    \
    wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

  可以用DECLARE_WAIT_QUEUE_HEAD(name)来初始化一个等待队列头部,如DECLARE_WAIT_QUEUE_HEAD(yy_wait_head)

  DECLARE_WAIT_QUEUE_HEAD也是个宏,最终是调用宏__WAIT_QUEUE_HEAD_INITIALIZER来初始化wait_queue_head_t中的成员

#define __WAIT_QUEUE_HEAD_INITIALIZER(name) {                \
    .lock        = __SPIN_LOCK_UNLOCKED(name.lock),        \
    .task_list    = { &(name).task_list, &(name).task_list } }

#define DECLARE_WAIT_QUEUE_HEAD(name) \
    wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

2.2 等待事件  

wait_event(wq, condition) :休眠,直到 condition 为真;退出的唯一条件是 condition 为真,无法被信号打断

wait_event_interruptible(wq, condition) :休眠,直到 condition 为真;休眠期间是可被信号打断

wait_event_timeout(wq, condition, timeout) :与wati_event相比多了一个timeout,表示最多等待timeout时间,以jiffy为单位,超过时间后无论condition是否为真都返回

wait_event_interruptible_timeout(wq, condition, timeout):与wait_event_timeout相比不同的就是休眠期间可被信号打断

2.3 唤醒事件

wake_up(x):唤醒以x为等待队列头部的所有进程,应与wait_event或wait_event_timeout成对使用,wake_up能 唤醒TASK_UNINTERRUPTIBLE和TASK_INTERRUPTIBLE状态的进程

wake_up_interruptible(x):唤醒以x为等待队列头部的所有进程,应与wait_event_interruptible或wait_event_interruptible_timeout成对使用,wake_up_interruptible只能能 唤醒TTASK_INTERRUPTIBLE状态的进程

 三、等待事件源码分析

3.1 wait_event源码分析

#define wait_event(wq, condition)                    \
do {                                    \
    might_sleep();                            \    \\在非debug模式这个函数是个空函数,可忽略,在debug模式提醒开发人员,调用该函数的函数可能会sleep
    if (condition)                            \    \\如果条件真则直接退出等待
        break;                            \
    __wait_event(wq, condition);                    \
} while (0)
#define __wait_event(wq, condition)                    \
    (void)___wait_event(wq, condition, TASK_UNINTERRUPTIBLE, 0, 0,    \
                schedule())
#define ___wait_event(wq, condition, state, exclusive, ret, cmd)    \
({                                    \
    __label__ __out;                        \
    wait_queue_t __wait;                        \
    long __ret = ret;    /* explicit shadow */            \
                                    \
    init_wait_entry(&__wait, exclusive ? WQ_FLAG_EXCLUSIVE : 0);    \    //初始化__wait结构体
    for (;;) {                            \
        long __int = prepare_to_wait_event(&wq, &__wait, state);\      //将等待队列__wait添加到等待队列头部wq中,并设置进程状态为state
                                    \
        if (condition)                        \                 //如果条件为真则直接结束等待
            break;                        \
                                    \
        if (___wait_is_interruptible(state) && __int) {        \      
            __ret = __int;                    \
            goto __out;                    \
        }                            \
                                    \
        cmd;                            \                      //执行__wait_event中传进来的schedule()函数,这个函数是让当前进程进入睡眠状态,调度别的进程
    }                                \
    finish_wait(&wq, &__wait);                    \                //结束等待,设置当前进程状态为TASK_RUNNING,并将__wait从等待队列头部wq中删除
__out:    __ret;                                \
})

 

long prepare_to_wait_event(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
    unsigned long flags;
    long ret = 0;

    spin_lock_irqsave(&q->lock, flags);
    if (unlikely(signal_pending_state(state, current))) {

        list_del_init(&wait->task_list);
        ret = -ERESTARTSYS;
    } else {
        if (list_empty(&wait->task_list)) {    //如果wait还没有添加到等待队列头部中则执行添加,如果已经添加了就不在重复添加
            if (wait->flags & WQ_FLAG_EXCLUSIVE)
                __add_wait_queue_tail(q, wait);
            else
                __add_wait_queue(q, wait);
        }
        set_current_state(state);          //设置当前进程状态
    }
    spin_unlock_irqrestore(&q->lock, flags);

    return ret;
}

3.2 wait_event_interruptible源码分析

#define __wait_event_interruptible(wq, condition)            \
    ___wait_event(wq, condition, TASK_INTERRUPTIBLE, 0, 0,        \
              schedule())

#define wait_event_interruptible(wq, condition) \ ({ \ int __ret = 0; \ might_sleep(); \ if (!(condition)) \ __ret = __wait_event_interruptible(wq, condition); \ __ret; \ })

  从wait_event_interruptible源码中可以看出,它与wait_event的不同只是在调用___wait_event时候传入的state参数不一样,wait_event传入的state参数是TASK_UNINTERRUPTIBLE,wait_event_interruptible传入的参数是TASK_INTERRUPTIBLE

四、唤醒事件源码分析

4.1 wake_up源码分析

  wake_up定义在wait.h中,如下所示:

  

  wake_up是调用__wake_up,__wake_up定义在linux/kernel/sched/wait.c中,

  

void __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 wake_flags, void *key)
{
    wait_queue_t *curr, *next;

    list_for_each_entry_safe(curr, next, &q->task_list, task_list) {    //遍历等待队列头部q下的所有等待等待队列,并执行每个等待队列中的func函数
        unsigned flags = curr->flags;

        if (curr->func(curr, mode, wake_flags, key) &&
                (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
            break;
    }
}

  从源码中可以看到,__wake_up就是加上锁后调用__wake_up_common,__wake_up_common函数就是遍历等待队列头部q下的所有等待等待队列,并执行每个等待队列中的func函数。func函数在2.1节中可以看到是default_wake_function,所以真正唤醒的函数就是这个default_wake_function。这个函数定义在linux/kernel/sched/core.c 中。

  

default_wake_function调用的是try_to_wake_up, try_to_wake_up函数通过把进程状态设置为TASK_RUNNING,并把该进程插入本地CPU运行队列rq来达到唤醒睡眠和停止的进程的目的。例如:调用该函数唤醒等待队列中的进程,或恢复执行等待信号的进程。

static int
try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{
    unsigned long flags;
    int cpu, success = 0;

    smp_mb__before_spinlock();
    raw_spin_lock_irqsave(&p->pi_lock, flags);
    if (!(p->state & state))
        goto out;

    trace_sched_waking(p);

    success = 1; /* we're going to change ->state */
    cpu = task_cpu(p);

    smp_rmb();
    if (p->on_rq && ttwu_remote(p, wake_flags))
        goto stat;

#ifdef CONFIG_SMP

    smp_rmb();

    smp_cond_load_acquire(&p->on_cpu, !VAL);

    p->sched_contributes_to_load = !!task_contributes_to_load(p);
    p->state = TASK_WAKING;

    cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags);
    if (task_cpu(p) != cpu) {
        wake_flags |= WF_MIGRATED;
        set_task_cpu(p, cpu);
    }
#endif /* CONFIG_SMP */

    ttwu_queue(p, cpu, wake_flags);
stat:
    ttwu_stat(p, cpu, wake_flags);
out:
    raw_spin_unlock_irqrestore(&p->pi_lock, flags);

    return success;
}

4.2 wake_up_interruptible

  

 

  wake_up_interruptible和wake_up不一样的地方就是传入__wake_up mode参数是TASK_INTERRUPTIBLE

  

 

posted @ 2023-01-06 18:13  YYFaGe  阅读(530)  评论(0编辑  收藏  举报