linux-等待队列(Wait Queue)

本文围绕Linux等待队列展开,介绍其以循环链表为基础结构,包含等待队列头和元素。阐述了创建、初始化、添加和移除等操作,还说明了等待事件、唤醒机制及相关函数。通过MMC驱动子系统源码实例展示应用,总结了等待队列的三种使用方式。

1. Linux等待队列概述

Linux内核的等待队列(Wait Queue)是重要的数据结构,与进程调度机制紧密相关联,可以用来同步对系统资源的访问、异步事件通知、跨进程通信等。
在Linux中,等待队列以循环链表为基础结构,包括两种数据结构:等待队列头(wait queue head)等待队列元素(wait queue),整个等待队列由等待队列头进行管理。下文将用内核源码(基于Linux kernel 5.2)对等待队列进行介绍,详细说明采用等待队列实现进程阻塞和唤醒的方法。

2. 等待队列头和等待队列元素

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

2.1 基本概念

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;
    wait_queue_func_t   func;
    struct list_head    entry;
};

typedef struct wait_queue_entry wait_queue_entry_t;

等待队列头结构包括一个自旋锁和一个链表头。等待队列元素除了包括链表项,还包括:

  •  flags : 标识队列元素状态和属性
  •  *private : 用于指向关联进程 task_struct 结构体的指针
  •  func : 函数指针,用于指向等待队列被唤醒时的回调的唤醒函数

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

  (1) WQ_FLAG_EXCLUSIVE 

上述场景中看到,当某进程调用 wake up 函数唤醒等待队列时,队列上所有的进程均被唤醒,在某些场合会出现唤醒的所有进程中,只有某个进程获得了期望的资源,而其他进程由于资源被占用不得不再次进入休眠。如果等待队列中进程数量庞大时,该行为将影响系统性能。
内核增加了"独占等待”(WQ_FLAG_EXCLUSIVE)来解决此类问题。一个独占等待的行为和通常的休眠类似,但有如下两个重要的不同:

  • 等待队列元素设置 WQ_FLAG_EXCLUSIVE 标志时,会被添加到等待队列的尾部,而非头部。
  • 在某等待队列上调用 wake up 时,执行独占等待的进程每次只会唤醒其中第一个(所有非独占等待进程仍会被同时唤醒)。

  (2) WQ_FLAG_WOKEN 
暂时还未理解,TODO

  (3) WQ_FLAG_BOOKMARK 
用于 wake_up() 唤醒等待队列时实现分段遍历,减少单次对自旋锁的占用时间。

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

等待队列头的定义和初始化有两种方式: init_waitqueue_head(&wq_head) 和宏定义  DECLARE_WAIT_QUEUE_HEAD(name)   。

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

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

创建等待队列元素较为普遍的一种方式是调用宏定义 DECLARE_WAITQUEUE(name, task) ,将定义一个名为 name 的等待队列元素, private 数据指向给定的关联进程结构体 task ,唤醒函数为 default_wake_function() 。后文介绍唤醒细节时详细介绍唤醒函数的工作。

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

内核源码中还存在其他定义等待队列元素的方式,调用宏定义 DEFINE_WAIT(name) 和 init_wait(&wait_queue) 。
这两种方式都将当前进程(current)关联到所定义的等待队列上,唤醒函数为 autoremove_wake_function() ,注意此函数与上述宏定义方式时不同(上述定义中使用 default_wake_function() )。
下文也将介绍 DEFINE_WAIT() 和 DECLARE_WAITQUEUE() 在使用场合上的不同。

#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 {                                                    \
        
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值