Linux等待队列分析

    技术2025-03-27  35

    Linux等待队列分析

    说明数据结构等待队列操作定义并初始化“等待队列头”等待事件唤醒队列    在Linux驱动程序中,可以使用等待队列(Wait Queue)来实现阻塞进程的唤醒。等待队列很早就作为一个基本的功能单位出现在Linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,可以用来同步对系统资源的访问。

    说明

    kernel版本:4.14.111

    数据结构

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

       其中,自旋锁lock是用来防止并发访问,task_list字段是等待队列中等待的进程链表的头。

    /* * A single wait-queue entry structure: */ struct wait_queue_entry { unsigned int flags; void *private; (1) wait_queue_func_t func; (2) struct list_head entry; (3) };

    1)通常指向当前任务task_struct数据结构 2)回调函数 3)挂入等待队列的链表

    等待队列操作

    定义并初始化“等待队列头”

    1)动态初始化

    wait_queue_head_t my_queue; init_waitqueue_head(&my_queue); //会将自旋锁初始化为未锁,等待队列初始化为空的双向循环链表。

    2)静态定义

    DECLARE_WAIT_QUEUE_HEAD (my_queue);

    等待事件

    #define wait_event(wq_head, condition) \ do { \ might_sleep(); \ if (condition) \ break; \ __wait_event(wq_head, condition); \ } while (0) wait_event(queue,condition); //等待以queue为等待队列头等待队列被唤醒,condition必须满足,否则阻塞 wait_event_interruptible(queue,condition); //可被信号打断 wait_event_timeout(queue,condition,timeout); //阻塞等待的超时时间,时间到了,不论condition是否满足,都要返回 wait_event_interruptible_timeout(queue,condition,timeout)

    1)首先判断条件是否满足,满足则直接返回 2)条件不满足则执行__wait_event

    /* wait_event */ #define __wait_event(wq_head, condition) \ (void)___wait_event(wq_head, condition, TASK_UNINTERRUPTIBLE, 0, 0, \ schedule()) /* wait_event_interruptible */ #define __wait_event_interruptible(wq_head, condition) \ ___wait_event(wq_head, condition, TASK_INTERRUPTIBLE, 0, 0, \ schedule()) /* wait_event_timeout */ #define __wait_event_timeout(wq_head, condition, timeout) \ ___wait_event(wq_head, ___wait_cond_timeout(condition), \ TASK_UNINTERRUPTIBLE, 0, timeout, \ __ret = schedule_timeout(__ret))

       上述分别为wait_event 、wait_event_interruptible 、wait_event_timeout 宏。可以看出,最终都是调用了___wait_event接口。    区别仅仅在于传递的进程状态,以及执行的调度函数。

    #define ___wait_event(wq_head, condition, state, exclusive, ret, cmd) \ ({ \ __label__ __out; \ struct wait_queue_entry __wq_entry; \ (1) long __ret = ret; /* explicit shadow */ \ \ init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0); \ (2) for (;;) { \ long __int = prepare_to_wait_event(&wq_head, &__wq_entry, state);\ (3) \ if (condition) \ (4) break; \ \ if (___wait_is_interruptible(state) && __int) { \ __ret = __int; \ goto __out; \ } \ \ cmd; \ (5) } \ finish_wait(&wq_head, &__wq_entry); \ (6) __out: __ret; \ })

    1)定义一个等待队列 2)初始化该等待队列,代码实现如下:

    void init_wait_entry(struct wait_queue_entry *wq_entry, int flags) { wq_entry->flags = flags; wq_entry->private = current; //private指针设置为当前进程task_struct wq_entry->func = autoremove_wake_function; //设置默认回调函数 INIT_LIST_HEAD(&wq_entry->entry); //初始化等待队列链表 }

    3)for循环内调用prepare_to_wait_event

    long prepare_to_wait_event(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry, int state) { unsigned long flags; long ret = 0; spin_lock_irqsave(&wq_head->lock, flags); if (unlikely(signal_pending_state(state, current))) { list_del_init(&wq_entry->entry); ret = -ERESTARTSYS; } else { if (list_empty(&wq_entry->entry)) { if (wq_entry->flags & WQ_FLAG_EXCLUSIVE) __add_wait_queue_entry_tail(wq_head, wq_entry); else __add_wait_queue(wq_head, wq_entry); } set_current_state(state); } spin_unlock_irqrestore(&wq_head->lock, flags); return ret; }

    if (list_empty(&wq_entry->entry))判断当前等待队列链表是否初始化,如果未初始化,则将当前等待队列链表加入等待队列头链表中。 set_current_state,设置当前进程状态,wait_event为TASK_UNINTERRUPTIBLE wait_event_interruptible为TASK_INTERRUPTIBLE 4)调用完prepare_to_wait_event后,再次判断条件是否满足,如果条件满足,则跳出for循环。 5)如果条件不满足,则执行cmd接口,对于wait_event,为schedule,对于wait_event_timeout,为schedule_timeout。即主动执行调度。 6)跳出循环,设置当前进程状态为TSAK_RUNNING。将等待队列从等待队列头链表中删除。对于wait_event,这里是进程被wake_up,且条件满足,才会执行到这里。对于wait_event_timeout,可能是条件满足,也可能是timeout超时,根据返回值判断。

    唤醒队列

    #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) void __wake_up(struct wait_queue_head *wq_head, unsigned int mode, int nr_exclusive, void *key) { __wake_up_common_lock(wq_head, mode, nr_exclusive, 0, key); } static void __wake_up_common_lock(struct wait_queue_head *wq_head, unsigned int mode, int nr_exclusive, int wake_flags, void *key) { unsigned long flags; wait_queue_entry_t bookmark; bookmark.flags = 0; bookmark.private = NULL; bookmark.func = NULL; INIT_LIST_HEAD(&bookmark.entry); spin_lock_irqsave(&wq_head->lock, flags); nr_exclusive = __wake_up_common(wq_head, mode, nr_exclusive, wake_flags, key, &bookmark); spin_unlock_irqrestore(&wq_head->lock, flags); while (bookmark.flags & WQ_FLAG_BOOKMARK) { spin_lock_irqsave(&wq_head->lock, flags); nr_exclusive = __wake_up_common(wq_head, mode, nr_exclusive, wake_flags, key, &bookmark); spin_unlock_irqrestore(&wq_head->lock, flags); } } static int __wake_up_common(struct wait_queue_head *wq_head, unsigned int mode, int nr_exclusive, int wake_flags, void *key, wait_queue_entry_t *bookmark) { wait_queue_entry_t *curr, *next; int cnt = 0; if (bookmark && (bookmark->flags & WQ_FLAG_BOOKMARK)) { curr = list_next_entry(bookmark, entry); list_del(&bookmark->entry); bookmark->flags = 0; } else curr = list_first_entry(&wq_head->head, wait_queue_entry_t, entry); if (&curr->entry == &wq_head->head) return nr_exclusive; list_for_each_entry_safe_from(curr, next, &wq_head->head, entry) { (1) unsigned flags = curr->flags; int ret; if (flags & WQ_FLAG_BOOKMARK) continue; ret = curr->func(curr, mode, wake_flags, key); (2) if (ret < 0) break; if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) break; if (bookmark && (++cnt > WAITQUEUE_WALK_BREAK_CNT) && (&next->entry != &wq_head->head)) { bookmark->flags = WQ_FLAG_BOOKMARK; list_add_tail(&bookmark->entry, &next->entry); break; } } return nr_exclusive; }

    1)遍历当前等待队列头链表中的等待队列。根据传递的唤醒数量,执行循环。 2)执行等待队列定义时,传递的func回调。上文中提到了,默认回调函数为autoremove_wake_function

    int autoremove_wake_function(struct wait_queue_entry *wq_entry, unsigned mode, int sync, void *key) { int ret = default_wake_function(wq_entry, mode, sync, key); if (ret) list_del_init(&wq_entry->entry); return ret; } int default_wake_function(wait_queue_entry_t *curr, unsigned mode, int wake_flags, void *key) { return try_to_wake_up(curr->private, mode, wake_flags); }

       autoremove_wake_function最终会调用try_to_wake_up函数将进程置为TASK_RUNNING状态。这样后面的进程调度便会调度到该进程,从而唤醒该进程继续执行。

    Processed: 0.014, SQL: 9