Linux等待队列waitqueue

waitqueue

内核中提供了等待队列,作用是实现阻塞操作。等待队列用于使进程等待某一特定的事件发生而无需频繁的轮询,进程在等待期间睡眠,在某些事件发生时,由内核自动唤醒。

首先,Linux中所有的进程都是由task_struct这个结构管理。在生成进程的时候会分配一个task_struct结构,之后将通过这个结构对进程进行管理。task_struct位于独立的连续区间。task_struct结构中有一个state成员,有下面几种状态:

状态说明
TASK_RUNNING执行可能状态
TASK_INTERRUPTIBLE等待状态,可接受信号
TASK_UNINTERRUPTIBLE等待状态,不能接受信号
TASK_ZOMBIE僵尸状态,exit后的状态
TASK_STOPPED延缓状态

1. 创建一个等待队列

Linux内核中,wait_queue_head_t代表一个等待队列头,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;

等待队列中每个元素用wait_queue_t来表示,wait_queue_t数据结构如下:

typedef struct __wait_queue wait_queue_t;
struct __wait_queue {
    unsigned int flags;         // WQ_FLAG_EXCLUSIVE-表示等待进程想要被独占地唤醒; 0-可以和其他进程一起唤醒。
#define WQ_FLAG_EXCLUSIVE 0x01  // 在结构体中定义宏跟一般的宏没区别,这里表示flags会用到该宏,提高直观性。
    void *private;              // 指向等待进程的task_struct地址
    wait_queue_func_t func;     // 用于唤醒被挂起任务的回调函数
    struct list_head task_list; // 链表元素,用于链接到wait_queue_head_t中的task_list链表中
};

① 可以调用init_waitqueue_head接口来初始化此队列,init_waitqueue_head主要是将wait_queue_head_t结构体中的两个成员进行初始化。

staitc wait_queue_head_t prod_wq;
init_waitqueue_head(&prod_wq);

#define init_waitqueue_head(q)                  \
    do {                                        \
        static struct lock_class_key __key;     \
                                                \
        __init_waitqueue_head((q), #q, &__key); \
    } while (0)

void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *key)
{
    spin_lock_init(&q->lock);   // 初始化自旋锁
    lockdep_set_class_and_name(&q->lock, key, name);    //和防止死锁有关
    INIT_LIST_HEAD(&q->task_list);  // 初始化链表
}

② 也可以使用DECLARE_WAIT_QUEUE_HEAD来定义和初始化等待队列头。

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

③ 定义和初始化等待队列项

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

2. 让当前进程开始等待

内核提供了如下的接口来让当前进程在条件不满足的情况下,阻塞等待:

wait_event(wq, condition)
wait_event_timeout(wq, condition, timeout)
wait_event_interruptible(wq, condition)
wait_event_interruptible_timeout(wq, condition, timeout)
2.1 wait_event

wait_event的实现如下:

void add_wait_queue_exclusive(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_tail(q, wait);
        spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue_exclusive);

void 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);
        set_current_state(state);
        spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(prepare_to_wait);

void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
{
        unsigned long flags;

        __set_current_state(TASK_RUNNING);
        /*
         * We can check for list emptiness outside the lock
         * IFF:
         *  - we use the "careful" check that verifies both
         *    the next and prev pointers, so that there cannot
         *    be any half-pending updates in progress on other
         *    CPU's that we haven't seen yet (and that might
         *    still change the stack area.
         * and
         *  - all other users take the lock (ie we can only
         *    have _one_ other CPU that looks at or modifies
         *    the list).
         */
        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);
        }
}

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

#define wait_event(wq, condition)                                       \
do {                                                                    \
        if (condition)                                                  \
                break;                                                  \
        __wait_event(wq, condition);                                    \
} while (0)

里面有个宏定义即DEFINE_WAIT,详细如下:

#define DEFINE_WAIT_FUNC(name, function)                                \
        wait_queue_t name = {                                           \
                .private        = current,                              \
                .func           = function,                             \
                .task_list      = LIST_HEAD_INIT((name).task_list),     \
        }

#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

可以看到private成员是当前task对象的地址current, func成员是autoremove_wake_function(在下面wake_up时再做说明)。
所以整个wait_event的逻辑就是:
① 首先判断条件是否满足,如果满足,直接退出;如果不满足,调用__wait_event

② __wait_event中首先基于当前经常构建一个等待队列项;然后进入死循环:
- 调用prepare_to_wait,该函数将新建的等待队列项加入到等待队列中,并修改当前任务的state为TASK_UNINTERRUPTIBLE;(注,该函数flags的结果必然是0,也就是说这个函数是将非独占进程添加到等待队列当中。而add_wait_queue_exclusive函数则是将独占进程添加到等待队列的尾部,也就是说一个等待队列,非独占进程总是在前面,独占进程总是在后面)
- 判断condition条件,满足就退出循环,不满足继续
- 调用schedule()进行任务调度后,重新开始循环

③ 退出循环后调用finish_wait,将当前任务的state设置为TASK_RUNNING,并将新建的等待队列从任务队列中删除。

2.2 wait_event_timeout

wait_event_timeout 的实现如下:

signed long __sched schedule_timeout(signed long timeout)
{
        struct timer_list timer;
        unsigned long expire;

        switch (timeout)
        {
        case MAX_SCHEDULE_TIMEOUT:
                /*
                 * These two special cases are useful to be comfortable
                 * in the caller. Nothing more. We could take
                 * MAX_SCHEDULE_TIMEOUT from one of the negative value
                 * but I' d like to return a valid offset (>=0) to allow
                 * the caller to do everything it want with the retval.
                 */
                schedule();
                goto out;
        default:
                /*
                 * Another bit of PARANOID. Note that the retval will be
                 * 0 since no piece of kernel is supposed to do a check
                 * for a negative retval of schedule_timeout() (since it
                 * should never happens anyway). You just have the printk()
                 * that will tell you if something is gone wrong and where.
                 */
                if (timeout < 0) {
                        printk(KERN_ERR "schedule_timeout: wrong timeout "
                              "value %lx\n", timeout);
                        dump_stack();
                        current->state = TASK_RUNNING;
                        goto out;
                }
        }

        expire = timeout + jiffies;

        setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
        __mod_timer(&timer, expire, false, TIMER_NOT_PINNED);
        schedule();
        del_singleshot_timer_sync(&timer);

        /* Remove the timer from the object tracker */
        destroy_timer_on_stack(&timer);

        timeout = expire - jiffies;

 out:
        return timeout < 0 ? 0 : timeout;
}

#define __wait_event_timeout(wq, condition, ret)                        \
do {                                                                    \
        DEFINE_WAIT(__wait);                                            \
                                                                        \
        for (;;) {                                                      \
                prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \
                if (condition)                                          \
                        break;                                          \
                ret = schedule_timeout(ret);                            \
                if (!ret)                                               \
                        break;                                          \
        }                                                               \
        if (!ret && (condition))                                        \
                ret = 1;                                                \
        finish_wait(&wq, &__wait);                                      \
} while (0)

#define wait_event_timeout(wq, condition, timeout)                      \
({                                                                      \
        long __ret = timeout;                                           \
        if (!(condition))                                               \
                __wait_event_timeout(wq, condition, __ret);             \
        __ret;                                                          \
})

wait_event_timeout 和 wait_event逻辑类似,就一个地方差异较大,即schedule_timeout。
schedule_timeout中构建了一个定时器,该定时器到期后将调用process_timeout(通过中断的形式),传入的参数则是当前进程的指针current。然后调用schedule,等待调度器回到该位置(由于任务状态为UNINTERRUPTABLE,不能通过调度或信号回到该位置)。这个时候就有两种情况(唤醒在后面wake_up部分详细说明):
① 超时了,调用process_timeout函数,该函数调用wake_up_process函数,核心代码类似wake_up_xxx(current)
② 在其他任务中调用了wake_up_xxx(wq)函数,将任务状态修改为TASK_RUNNING
一旦任务状态为TASK_RUNNING,就又回到了cpu的run queue中,可以通过调度回到函数中的schedule位置。

wait_event_timeout 返回值如下:
- 大于0: 表示condition满足,返回值表示距离设定超时还有多久(jiffies)
- 等于0: 表示超时发生

2.3 wait_event_interruptible

wait_event_interruptible 的实现如下:

#define __wait_event_interruptible(wq, condition, ret)                  \
do {                                                                    \
        DEFINE_WAIT(__wait);                                            \
                                                                        \
        for (;;) {                                                      \
                prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);      \
                if (condition)                                          \
                        break;                                          \
                if (!signal_pending(current)) {                         \
                        schedule();                                     \
                        continue;                                       \
                }                                                       \
                ret = -ERESTARTSYS;                                     \
                break;                                                  \
        }                                                               \
        finish_wait(&wq, &__wait);                                      \
} while (0)

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

wait_event_interruptible的实现和wait_event类似,区别是有多了一个signal_pending操作。
signal_pending检查给定进程是否有信号需要处理,返回0表示没有信号需要处理。
所以此时退出循环的条件是: 满足 condition 和 有信号 两者之一就行 (如果执行到schedule,需要另外一个进程调用wake_up_xxx(&wq)操作,或者该进程收到了信号,将任务加入到run queue中。)

wait_event_interruptible 返回值如下:
- -ERESTARTSYS: 表示被信号激活唤醒。该错误的意思表示发生系统调用,任务正处在睡眠状态,等wakeup之后,会重新调用一次系统调用。
- 等于0: 表示condition满足

2.4 wait_event_interruptible_timeout

wait_event_interruptible_timeout 的实现如下:

#define __wait_event_interruptible_timeout(wq, condition, ret)          \
do {                                                                    \
        DEFINE_WAIT(__wait);                                            \
                                                                        \
        for (;;) {                                                      \
                prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);      \
                if (condition)                                          \
                        break;                                          \
                if (!signal_pending(current)) {                         \
                        ret = schedule_timeout(ret);                    \
                        if (!ret)                                       \
                                break;                                  \
                        continue;                                       \
                }                                                       \
                ret = -ERESTARTSYS;                                     \
                break;                                                  \
        }                                                               \
        if (!ret && (condition))                                        \
                ret = 1;                                                \
        finish_wait(&wq, &__wait);                                      \
} while (0)

#define wait_event_interruptible_timeout(wq, condition, timeout)        \
({                                                                      \
        long __ret = timeout;                                           \
        if (!(condition))                                               \
                __wait_event_interruptible_timeout(wq, condition, __ret); \
        __ret;                                                          \
})

wait_event_interruptible_timeout的实现和上面wait_event等类似,退出循环的条件是: 满足 condition、timeout 和 有信号 三者之一(如果执行到schedule,需要另外一个进程调用wake_up_xxx(&wq)操作,或者超时了,或者该进程收到了信号,这三者都会将任务加入到run queue中。)。

wait_event_interruptible_timeout 返回值如下:
- -ERESTARTSYS: 表示被信号激活唤醒
- 大于0: 表示condition满足,返回值表示距离设定超时还有多久(jiffies)
- 等于0: 表示超时发生

3. 唤醒等待队列上的进程

内核提供了如下接口来唤醒等待队列上的进程:

#define wake_up(x)                      __wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_nr(x, nr)               __wake_up(x, TASK_NORMAL, nr, NULL)
#define wake_up_all(x)                  __wake_up(x, TASK_NORMAL, 0, NULL)
#define wake_up_locked(x)               __wake_up_locked((x), TASK_NORMAL, 1)
#define wake_up_all_locked(x)           __wake_up_locked((x), TASK_NORMAL, 0)
#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)
#define wake_up_interruptible_sync(x)   __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)

可以到这些接口调用了三个函数__wake_up,__wake_up_locked,__wake_up_sync。先看看__wake_up的实现:

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) {
                unsigned flags = curr->flags;

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

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

可以看到__wake_up会调到__wake_up_common函数,该函数的逻辑是,遍历等待队列上的的wait_queue_t结构体,进行如下的操作:
① 获取curr->flags值放入flags中。
② 进行判断,如果同时满足三个条件就退出循环。(注对于if来说,如果前面有一项不满足,后续的判断就不会做)
第一个条件是curr->func的返回结果,依据前面的说明,该函数实际上就是autoremove_wake_function,其详细说明如下,如果返回1,表明已经将相关的任务加入到cpu的run queue,并修改任务的状态成功。依据前面定义的wait_event_xxx的实现,该项正常来说均返回1。
第二个条件是flags & WQ_FLAG_EXCLUSIVE,如果该wait_queue_t是独占的,就为真。对于一个任务队列来说,只有前面的非互斥项执行curr->func之后,才轮到互斥进程,也只有互斥进程flags & WQ_FLAG_EXCLUSIVE才为真。
第三个条件是!–nr_exclusive,如果nr_exclusive为0,依据常理,该项始终为假;如果nr_exclusive为1,则第一次就为真;如果nr_exclusive为一个整数nr,则第nr次,该项为真。
所以可以得出:
wake_up 唤醒全部的非独占任务,唤醒一个独占任务。
wake_up_nr 唤醒全部的非独占任务,唤醒nr个独占任务。
wake_up_all 唤醒全部的非独占任务,唤醒全部独占任务。
wake_up_interruptible_xxx等函数类似上面。

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

        /*
         * If we are going to wake up a thread waiting for CONDITION we
         * need to ensure that CONDITION=1 done by the caller can not be
         * reordered with p->state check below. This pairs with mb() in
         * set_current_state() the waiting thread does.
         */
        smp_mb__before_spinlock();
        raw_spin_lock_irqsave(&p->pi_lock, flags);
        if (!(p->state & state))
                goto out;

        success = 1; /* we're going to change ->state */
        cpu = task_cpu(p);  /* 获取最后执行该任务的CPU */

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

    /* support smp 在很多架构上还不支持smp可以忽略此处
     * 判断是否要将任务转移到另外一个CPU的执行队列上,负载均衡
     * /
#ifdef CONFIG_SMP
        /*
         * If the owning (remote) cpu is still in the middle of schedule() with
         * this task as prev, wait until its done referencing the task.
         */
        while (p->on_cpu)
                cpu_relax();
        /*
         * Pairs with the smp_wmb() in finish_lock_switch().
         */
        smp_rmb();

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

        if (p->sched_class->task_waking)
                p->sched_class->task_waking(p);

        cpu = select_task_rq(p, 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);
stat:
        ttwu_stat(p, cpu, wake_flags);
out:
        raw_spin_unlock_irqrestore(&p->pi_lock, flags);

        return success;
}

int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,
                          void *key)
{
        return try_to_wake_up(curr->private, mode, wake_flags);
}

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

以上是autoremove_wake_function的实现,具体看try_to_wake_up函数。
该函数有3个参数:
- p 任务结构体指针
- state 需要唤醒的进程状态掩码,即需要唤醒符合该状态掩码的进程
- wake_flags 此处等待队列传过来的值为0。表示是同步唤醒sync,还是异步唤醒 async;

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Linux waitqueue 是一种 Linux 内核的同步机制,它用于控制进程的执行顺序,使进程之间能够协调进行。 它通过让一个进程在等待另一个进程完成某个操作时进入睡眠状态,并在另一个进程完成操作后唤醒等待的进程。这样,它可以避免进程在不同步的情况下同时进行某些操作,从而减少系统资源的浪费。 因此,waitqueue 机制是 Linux 内核常用的一种机制,它可以保证系统的正确性和高效性。 ### 回答2: 在Linux操作系统waitqueue是一种用于进程或线程等待的机制。 当一个进程或线程需要等待某个条件满足时,它可以使用waitqueue机制来挂起自己的执行。等待队列waitqueue)是一个数据结构,用于维护等待某个事件发生的进程或线程的列表。 当一个条件被满足时,比如某个共享资源变为可用,就会唤醒等待该条件的进程或线程。唤醒的过程是通过使用wake_up函数来实现的。 当一个进程或线程需要等待条件满足时,它会调用wait_event函数,将自己加入到等待队列,并将自己标记为等待状态。之后,该进程或线程就会进入睡眠状态,并且由调度器决定运行其他进程或线程。 当条件满足时,比如共享资源变为可用,唤醒该条件的进程或线程的时候,会调用wake_up函数来唤醒等待的进程或线程。被唤醒的进程或线程会从wait_event的调用处继续执行,并继续执行后续逻辑。 需要注意的是,使用waitqueue机制需要配合锁机制使用,以避免竞态条件的产生。在加入等待队列和唤醒过程,需要对共享资源进行加锁保护,以防止并发访问导致的数据不一致性。 总之,waitqueueLinux一种用于进程或线程等待的机制,它通过等待队列来管理等待某个条件满足的进程或线程,并通过唤醒函数来唤醒等待的进程或线程。它是实现同步和互斥的重要工具之一,能够实现进程或线程之间的协作与同步。 ### 回答3: Linuxwaitqueue等待队列)是一种用于进程调度的机制。它允许一个或多个进程阻塞并等待某个特定条件的满足。 waitqueue是一个数据结构,类似于一个队列,用于存储等待某个条件满足的进程。当一个进程等待某个条件时,它会将自己添加到waitqueue,并进入睡眠状态。 在Linux内核waitqueue通常与锁(如spinlock或mutex)结合使用。当一个进程需要等待某个条件时,它需要先获取锁,在锁的保护下将自己添加到waitqueue,然后释放锁并进入睡眠状态。当条件满足时,另一个进程会获取相同的锁,唤醒等待waitqueue的进程。 waitqueue的实现依赖于内核调度器。当一个进程被唤醒时,它会从睡眠状态返回到可运行状态,并进入内核调度器的调度队列等待分配CPU执行。 waitqueue提供了一种线程同步的机制,使得进程可以等待某个条件满足而不需要忙等待。它在很多Linux内核的子系统广泛使用,如设备驱动、文件系统等。 总结来说,waitqueueLinux内核用于进程调度的一种机制,它允许一个或多个进程等待某个条件的满足。它借助锁和睡眠状态实现进程的阻塞和唤醒,依赖于内核调度器进行进程的调度。waitqueue在提供进程同步、避免忙等待等方面发挥了重要作用。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值