【Linux】内核同步机制之等待队列和完成量

文章目录

  • 完成量和等待队列
    • 1. 等待队列
      • 1.1 基本元素
      • 1.2 等待队列的创建和初始化
      • 1.3 等待队列元素的创建和初始化
      • 1.4 添加和移除等待队列元素到等待队列
    • 2. 等待事件机制
    • 3. 等待队列唤醒
    • 4. 总结
      • 4.1 等待事件方式
      • 4.2 手动休眠方式
      • 4.3 借助内核封装函数,进行手动休眠
    • 5. 完成量
      • 5.1 完成量定义
      • 5.2 完成量初始化
      • 5.3 完成量的使用

完成量和等待队列

1. 等待队列

Ref:https://www.cnblogs.com/hueyxu/p/13745029.html

1.1 基本元素

等待队列以循环链表为基础结构,链表头和链表项分别为等待队列头和等待队列元素,分别用结构体
wait_queue_head_t wait_queue_entry_t描述(定义在 linux/wait.h
等待队列头

struct wait_queue_head {
    spinlock_t          lock;
    struct list_head    head;
};

typedef struct wait_queue_head wait_queue_head_t;

等待队列元素

typedef int (*wait_queue_func_t)(struct wait_queue_entry *wq_entry, unsigned mode, int flags, void *key);
int default_wake_function(struct wait_queue_entry *wq_entry, unsigned mode, int flags, void *key);

/* wait_queue_entry::flags */
#define WQ_FLAG_EXCLUSIVE   0x01
#define WQ_FLAG_WOKEN       0x02
#define WQ_FLAG_BOOKMARK    0x04

/*
 * A single wait-queue entry structure:
 */
struct wait_queue_entry {
    unsigned int        flags;    //标识队列元素状态和属性
    void                *private; // 用于指向关联进程task_struct结构体的指针
    wait_queue_func_t   func;     //函数指针,用于指向等待队列被唤醒时的回调的唤醒函数
    struct list_head    entry;
};

typedef struct wait_queue_entry wait_queue_entry_t;

 以进程阻塞和唤醒的过程为例,等待队列的使用场景可以简述为:
  进程 A 因等待某些资源(依赖进程 B 的某些操作)而不得不进入阻塞状态,便将当前进程加入到等待队列 Q 中。进程 B 在一系列操作后,可通知进程 A 所需资源已到位,便调用唤醒函数 wake up 来唤醒等待队列上 Q 的进程,注意此时所有等待在队列 Q 上的进程均被置为可运行状态。
借助上述描述场景,说明等待队列元素属性 flags 标志的作用,下文也将结合源码进行详细解读。

  1. WQ_FLAG_EXCLUSIVE
     上述场景中看到,当某进程调用wake up函数唤醒等待队列时,队列上所有的进程均被唤醒,在某些场合会出现唤醒的所有进程中,只有某个进程获得了期望的资源,而其他进程由于资源被占用不得不再次进入休眠。如果等待队列中进程数量庞大时,该行为将影响系统性能。
    内核增加了"独占等待”(WQ_FLAG_EXCLUSIVE)来解决此类问题。一个独占等待的行为和通常的休眠类似,但有如下两个重要的不同:
  • 等待队列元素设置 WQ_FLAG_EXCLUSIVE 标志时,会被添加到等待队列的尾部,而非头部。
  • 在某等待队列上调用 wake up 时,执行独占等待的进程每次只会唤醒其中第一个(所有非独占等待进程仍会被同时唤醒)。
  1. WQ_FLAG_WOKEN
     这是内部使用的标志,调用者不应该使用它
  2. WQ_FLAG_BOOKMARK
     用于wake_up()唤醒等待队列时实现分段遍历,减少单次对自旋锁的占用时间。

1.2 等待队列的创建和初始化

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

void __init_waitqueue_head(struct wait_queue_head *wq_head, const char *name, struct lock_class_key *key)
{
    spin_lock_init(&wq_head->lock);
    lockdep_set_class_and_name(&wq_head->lock, key, name);
    INIT_LIST_HEAD(&wq_head->head);
}

OR

#define DECLARE_WAIT_QUEUE_HEAD(name)                       \
    struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

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

1.3 等待队列元素的创建和初始化

//tsk -> 可指定非当前进程

#define DECLARE_WAITQUEUE(name, tsk)                        \
    struct wait_queue_entry name = __WAITQUEUE_INITIALIZER(name, tsk)

#define __WAITQUEUE_INITIALIZER(name, tsk) {                \
    .private    = tsk,                                      \
    .func       = default_wake_function,                    \
    .entry      = { NULL, NULL } }

OR

//都指定当前进程

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

#define DEFINE_WAIT_FUNC(name, function)                    \
    struct wait_queue_entry name = {                        \
        .private    = current,                              \
        .func       = function,                             \
        .entry      = LIST_HEAD_INIT((name).entry),         \
    }

#define init_wait(wait)                                     \
    do {                                                    \
        (wait)->private = current;                          \
        (wait)->func = autoremove_wake_function;            \
        INIT_LIST_HEAD(&(wait)->entry);                     \
        (wait)->flags = 0;                                  \
    } while (0)

1.4 添加和移除等待队列元素到等待队列

添加

add_wait_queue():在等待队列头部添加普通的等待队列元素(非独占等待,清除WQ_FLAG_EXCLUSIVE标志)。

void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
    unsigned long flags;

    // 清除WQ_FLAG_EXCLUSIVE标志
    wq_entry->flags &= ~WQ_FLAG_EXCLUSIVE;
    spin_lock_irqsave(&wq_head->lock, flags);
    __add_wait_queue(wq_head, wq_entry);
    spin_unlock_irqrestore(&wq_head->lock, flags);
}   

static inline void __add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
    list_add(&wq_entry->entry, &wq_head->head);
}
add_wait_queue_exclusive():在等待队列尾部添加独占等待队列元素(设置了WQ_FLAG_EXCLUSIVE标志)
 
void add_wait_queue_exclusive(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
    unsigned long flags;

    // 设置WQ_FLAG_EXCLUSIVE标志
    wq_entry->flags |= WQ_FLAG_EXCLUSIVE;
    spin_lock_irqsave(&wq_head->lock, flags);
    __add_wait_queue_entry_tail(wq_head, wq_entry);
    spin_unlock_irqrestore(&wq_head->lock, flags);
}

static inline void __add_wait_queue_entry_tail(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
    list_add_tail(&wq_entry->entry, &wq_head->head);
}

移除

remove_wait_queue()函数用于将等待队列元素 wq_entry从等待队列wq_head中移除。
 
void remove_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
    unsigned long flags;

    spin_lock_irqsave(&wq_head->lock, flags);
    __remove_wait_queue(wq_head, wq_entry);
    spin_unlock_irqrestore(&wq_head->lock, flags);
}

static inline void
__remove_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
{
    list_del(&wq_entry->entry);
}

【Linux】内核同步机制之等待队列和完成量_第1张图片

2. 等待事件机制

等待事件机制封装了等待队列和等待元素的初始化及两者之间插入和移除操作
内核中提供了等待事件wait_event()宏(以及它的几个变种),可用于实现简单的进程休眠,等待直至某个条件成立,主要包括如下几个定义:

wait_event(wq_head, condition)
wait_event_timeout(wq_head, condition, timeout) 
wait_event_interruptible(wq_head, condition)
wait_event_interruptible_timeout(wq_head, condition, timeout)
io_wait_event(wq_head, condition)

上述所有形式函数中, wq_head 是等待队列头(采用 值传递 的方式传输函数), condition 是任意一个布尔表达式。使用 wait_event ,进程将被置于非中断休眠,而使用 wait_event_interruptible 时,进程可以被信号中断。另外两个版本 wait_event_timeout 和 wait_event_interruptible_timeout 会使进程只等待限定的时间(以jiffy表示,给定时间到期时,宏均会返回0,而无论 condition 为何值)

API实现见Ref

3. 等待队列唤醒

等待事件机制向用户提供进程睡眠接口,等待队列唤醒向用户提供唤醒进程接口
前文已经简单提到,wake_up 函数可用于将等待队列上的所有进程唤醒,和 wait_event相对应wake_up 函数也包括多个变体。

wake_up(&wq_head)                        //唤醒等待队列上的所有进程
wake_up_interruptible(&wq_head)          //唤醒执行可中断休眠的进程
wake_up_nr(&wq_head, nr)                 //唤醒给定数目nr个独占等待进程,而非一个(wake_up函数唤醒一个)
wake_up_interruptible_nr(&wq_head, nr)   //唤醒给定数目nr个可中断的独占等待进程
wake_up_interruptible_all(&wq_head)      //唤醒等待队列上的所有可中断休眠独占等待进程

API实现见Ref

4. 总结

上述等待队列的使用方式有三种

4.1 等待事件方式

下面的程序展示了wait_event()wake_up()函数配合实现进程的休眠和唤醒流程
此方法只需要定义等待队列,等待队列元素将会在wait_event()函数中自动被创建、入等待队列、出等待队列

//声明
struct xxxx_touch {
    ...
    wait_queue_head_t   wait_queue;
    ...
}
//定义
static struct xxxx_touch xxxx_touch_dev = {
    ...
    .wait_queue = __WAIT_QUEUE_HEAD_INITIALIZER(xxxx_touch_dev.wait_queue),
    ...
}

//等待
static ssize_t p_sensor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
    struct xxxx_touch_pdata *pdata = dev_get_drvdata(dev);
    struct xxxx_touch *touch_dev = pdata->device;

    wait_event_interruptible(touch_dev->wait_queue, pdata->psensor_changed);
    pdata->psensor_changed = false;

    return snprintf(buf, PAGE_SIZE, "%d\n", pdata->psensor_value);
}

//唤醒
int update_p_sensor_value(int value)
{
    struct xxxx_touch *dev = NULL;

    mutex_lock(&xxxx_touch_dev.psensor_mutex);

    if (!touch_pdata) {
        mutex_unlock(&xxxx_touch_dev.psensor_mutex);
        return -ENODEV;
    }

    dev = touch_pdata->device;

    if (value != touch_pdata->psensor_value) {
        MI_TOUCH_LOGI(1, "%s %s: value:%d\n", MI_TAG, __func__, value);
        touch_pdata->psensor_value = value;
        touch_pdata->psensor_changed = true;
        wake_up(&dev->wait_queue);
    }

    mutex_unlock(&xxxx_touch_dev.psensor_mutex);
    return 0;
}

4.2 手动休眠方式

DECLARE_WAIT_QUEUE_HEAD(queue);      //创建等待队列queue
DECLARE_WAITQUEUE(wait, current);    //创建等待队列元素,tsk为当前进程,wakefunc为default_wake_function


for (;;) {
    add_wait_queue(&queue, &wait);            //等待队列元素入队列
    set_current_state(TASK_INTERRUPTIBLE);    //设定当前进程可被外部信号唤醒
    if (condition)                            //如果条件成立出循环
        break;
    schedule();                               //调用调度器,让出CPU,进程休眠
    remove_wait_queue(&queue, &wait);         //被唤醒,等待队列结束,将等待队列元素出队列
    if (signal_pending(current))              //检查当前进程是否有信号处理,返回不为0表示有信号需要处理
        return -ERESTARTSYS;                  //如果有信号需要处理,则触发系统调用
}
set_current_state(TASK_RUNNING);              //设定当前进程运行态
remove_wait_queue(&queue, &wait);             //等待队列元素出队列

4.3 借助内核封装函数,进行手动休眠

DELARE_WAIT_QUEUE_HEAD(queue);    //创建等待队列queue
DEFINE_WAIT(wait);                //创建等待队列元素,tsk为当前进程,wakefunc为autoremove_wake_function


while (! condition) {
    prepare_to_wait(&queue, &wait, TASK_INTERRUPTIBLE);//实现见Ref
    if (! condition)
        schedule();
    finish_wait(&queue, &wait)                         //实现见Ref
}

5. 完成量

这是一个基于等待队列实现的内核同步机制,可以视为等待队列的一种封装
Ref: http://blog.chinaunix.net/uid-29680694-id-5768610.html

5.1 完成量定义

struct completion {
        unsigned int done;               //非零则条件满足不需要等待
        struct swait_queue_head wait;    //等待队列
};

5.2 完成量初始化

静态初始化

/**
 * DECLARE_COMPLETION - declare and initialize a completion structure
 * @work:  identifier for the completion structure
 *
 * This macro declares and initializes a completion structure. Generally used
 * for static declarations. You should use the _ONSTACK variant for automatic
 * variables.
 */
#define DECLARE_COMPLETION(work) \
        struct completion work = COMPLETION_INITIALIZER(work)

#define COMPLETION_INITIALIZER(work) \
        { 0, __SWAIT_QUEUE_HEAD_INITIALIZER((work).wait) }

#define __SWAIT_QUEUE_HEAD_INITIALIZER(name) {                                \
        .lock                = __RAW_SPIN_LOCK_UNLOCKED(name.lock),                \
        .task_list        = LIST_HEAD_INIT((name).task_list),                \
}

动态初始化

#define init_completion(x) __init_completion(x)

/**
 * init_completion - Initialize a dynamically allocated completion
 * @x:  pointer to completion structure that is to be initialized
 *
 * This inline function will initialize a dynamically created completion
 * structure.
 */
static inline void __init_completion(struct completion *x)
{
        x->done = 0;
        init_swait_queue_head(&x->wait);
}

5.3 完成量的使用

API

unsigned long __sched wait_for_completion_timeout(struct completion *x, unsigned long timeout);
void complete(struct completion *x);
static inline void reinit_completion(struct completion *x);

API 实现

/**
 * wait_for_completion_timeout: - waits for completion of a task (w/timeout)
 * @x:  holds the state of this particular completion
 * @timeout:  timeout value in jiffies
 *
 * This waits for either a completion of a specific task to be signaled or for a
 * specified timeout to expire. The timeout is in jiffies. It is not
 * interruptible.
 *
 * Return: 0 if timed out, and positive (at least 1, or number of jiffies left
 * till timeout) if completed.
 */
unsigned long __sched
wait_for_completion_timeout(struct completion *x, unsigned long timeout)
{
        return wait_for_common(x, timeout, TASK_UNINTERRUPTIBLE);
}
EXPORT_SYMBOL(wait_for_completion_timeout);

/**
 * complete: - signals a single thread waiting on this completion
 * @x:  holds the state of this particular completion
 *
 * This will wake up a single thread waiting on this completion. Threads will be
 * awakened in the same order in which they were queued.
 *
 * See also complete_all(), wait_for_completion() and related routines.
 *
 * If this function wakes up a task, it executes a full memory barrier before
 * accessing the task state.
 */
void complete(struct completion *x)
{
        unsigned long flags;

        raw_spin_lock_irqsave(&x->wait.lock, flags);

        if (x->done != UINT_MAX)
                x->done++;
        swake_up_locked(&x->wait);
        raw_spin_unlock_irqrestore(&x->wait.lock, flags);
}
EXPORT_SYMBOL(complete);

/**
 * reinit_completion - reinitialize a completion structure
 * @x:  pointer to completion structure that is to be reinitialized
 *
 * This inline function should be used to reinitialize a completion structure so it can
 * be reused. This is especially important after complete_all() is used.
 */
static inline void reinit_completion(struct completion *x)
{
        x->done = 0;
}

着重看下
其实还是等待队列那套套路
wait_for_completion_timeout

/**
 * wait_for_completion_timeout: - waits for completion of a task (w/timeout)
 * @x:  holds the state of this particular completion
 * @timeout:  timeout value in jiffies
 *
 * This waits for either a completion of a specific task to be signaled or for a
 * specified timeout to expire. The timeout is in jiffies. It is not
 * interruptible.
 *
 * Return: 0 if timed out, and positive (at least 1, or number of jiffies left
 * till timeout) if completed.
 */
unsigned long __sched
wait_for_completion_timeout(struct completion *x, unsigned long timeout)
{
        return wait_for_common(x, timeout, TASK_UNINTERRUPTIBLE);
}
EXPORT_SYMBOL(wait_for_completion_timeout);

static long __sched
wait_for_common(struct completion *x, long timeout, int state)
{
        return __wait_for_common(x, schedule_timeout, timeout, state);
}

static inline long __sched
__wait_for_common(struct completion *x,
                  long (*action)(long), long timeout, int state)
{
        might_sleep();          //this func may sleep when user use it 

        complete_acquire(x);    // is NULL in 5.10

        raw_spin_lock_irq(&x->wait.lock);
        timeout = do_wait_for_common(x, action, timeout, state);
        raw_spin_unlock_irq(&x->wait.lock);

        complete_release(x);    // is NULL in 5.10

        return timeout;
}

static inline long __sched
do_wait_for_common(struct completion *x,
                   long (*action)(long), long timeout, int state)
{
        if (!x->done) {
                DECLARE_SWAITQUEUE(wait); //swait_queue declare,tsk is current

                do {
                        if (signal_pending_state(state, current)) {
                                timeout = -ERESTARTSYS;
                                break;
                        }
                        __prepare_to_swait(&x->wait, &wait);//add swait_queue to swait_queue_head
                        __set_current_state(state);//set current state, here is TASK_UNINTERRUPTIBLE
                        raw_spin_unlock_irq(&x->wait.lock);//this action is sleep until timeout in here
                        timeout = action(timeout);
                        raw_spin_lock_irq(&x->wait.lock);
                } while (!x->done && timeout);
                __finish_swait(&x->wait, &wait);
                if (!x->done)
                        return timeout;
        }
        if (x->done != UINT_MAX)
                x->done--;
        return timeout ?: 1;
}

你可能感兴趣的:(#,Linux,kernel,linux,等待队列,完成量)