内核有很多队列, 如等待队列, 工作队列等等。
所有的队列都是利用list机制做成一个双向链表/队列。
和内核种的一些模块自己使用的队列其实大同小异。
如usb的端点队列, v4l2中vb2_buffer使用的queued_list和done_list。
以vivi.c驱动为例, 记录些等待队列的实现及应用。
* 相关结构体和函数
1. DECLARE_WAIT_QUEUE_HEAD
定义一个等待队列头
54 #define __WAIT_QUEUE_HEAD_INITIALIZER(name) {› › › › › \
55 › .lock› › = __SPIN_LOCK_UNLOCKED(name.lock),› › › \
56 › .head› › = { &(name).head, &(name).head } }
57
58 #define DECLARE_WAIT_QUEUE_HEAD(name) \
59 › struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
即初始化一个静态/全局的等待队列头。
2. struct wait_queue_head结构体
34 struct wait_queue_head {
35 › spinlock_t› › lock;
36 › struct list_head› head;
37 };
两个变量, 一个spinlock, 一个list_head
3. DECLARE_WAITQUEUE
46 #define __WAITQUEUE_INITIALIZER(name, tsk) {› › › › › \
47 › .private› = tsk,› › › › › › › \
48 › .func› › = default_wake_function,› › › › \
49 › .entry› › = { NULL, NULL } }
50
51 #define DECLARE_WAITQUEUE(name, tsk)› › › › › › \
52 › struct wait_queue_entry name = __WAITQUEUE_INITIALIZER(name, tsk)
定义一个等待队列entry,
entry一般是翻译成入口的意思, 但放这里感觉不太合适...
比如list_entry函数
345 /**
346 * list_entry - get the struct for this entry
347 * @ptr:› the &struct list_head pointer.
348 * @type:› the type of the struct this is embedded in.
349 * @member:›the name of the list_head within the struct.
350 */
351 #define list_entry(ptr, type, member) \
352 › container_of(ptr, type, member)
所以我觉得这里entry翻译成元素, 条目一类的意思更为合适...
即list_head仅仅包含前后指针。
具体的条目内容本身需要另外定义一个结构体, 并通常把list_head结构体放最后。
不管怎样, DECLARE_WAITQUEUE定义了一个具体的等待队列条目/成员。 (可以放入等待队列中^^)
4. wait_queue_entry
24 /*
25 * A single wait-queue entry structure:
26 */
27 struct wait_queue_entry {
28 › unsigned int› › flags;
29 › void› › › *private;
30 › wait_queue_func_t› func;
31 › struct list_head› entry;
32 };
4. add_wait_queue
17 void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
ok, 该函数就是将等待队列条目挂载到等待队列中。 (是结尾还是开头??需要看内部实现。。。)
154 static inline void __add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
155 {
156 › list_add(&wq_entry->entry, &wq_head->head);
157 }
内部调用的是list_add, 所以是加载等待队列的头部。 (wait_queue_head的后一个)
ok, 该函数仅仅做了一个入队列的动作... 具体如何触发等待... 需要其他函数介入。
5. schedule_timeout_interruptible
/*
* We can use __set_current_state() here because schedule_timeout() calls
* schedule() unconditionally.
*/
signed long __sched schedule_timeout_interruptible(signed long timeout)
{
__set_current_state(TASK_INTERRUPTIBLE);
return schedule_timeout(timeout);
}
以上函数就是为了让当前线程进入睡眠。
依赖的方式为两个步骤,
1). __set_current_state(TASK_INTERRUPTIBLE)
设置当前状态为可中断
2). schedule_timeout
相比schedule, 多了个timeout超时机制。 具体后面再看。
schedule() 函数就会让当前线程挂起/睡眠, 让出cpu, 让cpu去调度其他线程。
*具体调度器的代码一下子看不懂... 先贴点源码注释, 后面慢慢分析吧...
2743 /*
2744 * __schedule() is the main scheduler function.
2745 *
2746 * The main means of driving the scheduler and thus entering this function are:
2747 *
2748 * 1. Explicit blocking: mutex, semaphore, waitqueue, etc.
2749 *
2750 * 2. TIF_NEED_RESCHED flag is checked on interrupt and userspace return
2751 * paths. For example, see arch/x86/entry_64.S.
2752 *
2753 * To drive preemption between tasks, the scheduler sets the flag in timer
2754 * interrupt handler scheduler_tick().
2755 *
2756 * 3. Wakeups don't really cause entry into schedule(). They add a
2757 * task to the run-queue and that's it.
2758 *
2759 * Now, if the new task added to the run-queue preempts the current
2760 * task, then the wakeup sets TIF_NEED_RESCHED and schedule() gets
2761 * called on the nearest possible occasion:
2762 *
2763 * - If the kernel is preemptible (CONFIG_PREEMPT=y):
2764 *
2765 * - in syscall or exception context, at the next outmost
2766 * preempt_enable(). (this might be as soon as the wake_up()'s
2767 * spin_unlock()!)
2768 *
2769 * - in IRQ context, return from interrupt-handler to
2770 * preemptible context
2771 *
2772 * - If the kernel is not preemptible (CONFIG_PREEMPT is not set)
2773 * then at the next:
2774 *
2775 * - cond_resched() call
2776 * - explicit schedule() call
2777 * - return from syscall or exception to user-space
2778 * - return from interrupt-handler to user-space
2779 */
2780 static void __sched __schedule(void)
6. wake_up_interruptible
一看名字, 就是唤醒等待队列中的睡眠线程的。
165 #define wake_up_interruptible(x)› __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
78 /**
79 * __wake_up - wake up threads blocked on a waitqueue.
80 * @q: the waitqueue
81 * @mode: which threads
82 * @nr_exclusive: how many wake-one or wake-many threads to wake up
83 * @key: is directly passed to the wakeup function
84 *
85 * It may be assumed that this function implies a write memory barrier before
86 * changing the task state if and only if any tasks are woken up.
87 */
88 void __wake_up(wait_queue_head_t *q, unsigned int mode,
89 › › › int nr_exclusive, void *key)
90 {
91 › unsigned long flags;
92
93 › spin_lock_irqsave(&q->lock, flags);
94 › __wake_up_common(q, mode, nr_exclusive, 0, key);
95 › spin_unlock_irqrestore(&q->lock, flags);
96 }
55 /*
56 * The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just
57 * wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve
58 * number) then we wake all the non-exclusive tasks and one exclusive task.
59 *
60 * There are circumstances in which we can try to wake a task which has already
61 * started to run but is not in state TASK_RUNNING. try_to_wake_up() returns
62 * zero in this (rare) case, and we handle it by continuing to scan the queue.
63 */
64 static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
65 › › › int nr_exclusive, int wake_flags, void *key)
66 {
67 › wait_queue_t *curr, *next;
68
69 › list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
70 › › unsigned flags = curr->flags;
71
72 › › if (curr->func(curr, mode, wake_flags, key) &&
73 › › › › (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
74 › › › break;
75 › }
76 }
这个函数还挺容易看,
就是拿出等待队列中对应要唤醒的条目, 调用func回调函数。
可惜关于调用的哪个回调函数...又没这么简单了...
47 #define __WAITQUEUE_INITIALIZER(name, tsk) {› › › › \
48 › .private› = tsk,› › › › › › \
49 › .func› › = default_wake_function,› › › \
50 › .task_list› = { NULL, NULL } }
1650 /**
1651 * try_to_wake_up - wake up a thread
1652 * @p: the thread to be awakened
1653 * @state: the mask of task states that can be woken
1654 * @wake_flags: wake modifier flags (WF_*)
1655 *
1656 * Put it on the run-queue if it's not already there. The "current"
1657 * thread is always on the run-queue (except when the actual
1658 * re-schedule is in progress), and as such you're allowed to do
1659 * the simpler "current->state = TASK_RUNNING" to mark yourself
1660 * runnable without the overhead of this.
1661 *
1662 * Return: %true if @p was woken up, %false if it was already running.
1663 * or @state didn't match @p's state.
1664 */
1665 static int
1666 try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
1667 {
1668 › unsigned long flags;
1669 › int cpu, success = 0;
1670
1671 › /*
1672 › * If we are going to wake up a thread waiting for CONDITION we
1673 › * need to ensure that CONDITION=1 done by the caller can not be
1674 › * reordered with p->state check below. This pairs with mb() in
1675 › * set_current_state() the waiting thread does.
1676 › */
1677 › smp_mb__before_spinlock();
1678 › raw_spin_lock_irqsave(&p->pi_lock, flags);
1679 › if (!(p->state & state))
1680 › › goto out;
1681
1682 › success = 1; /* we're going to change ->state */
1683 › cpu = task_cpu(p);
1684
1685 › /*
1686 › * Ensure we load p->on_rq _after_ p->state, otherwise it would
1687 › * be possible to, falsely, observe p->on_rq == 0 and get stuck
1688 › * in smp_cond_load_acquire() below.
1689 › *
1690 › * sched_ttwu_pending() try_to_wake_up()
1691 › * [S] p->on_rq = 1; [L] P->state
1692 › * UNLOCK rq->lock -----.
1693 › * \
1694 › *› › › › +--- RMB
1695 › * schedule() /
1696 › * LOCK rq->lock -----'
1697 › * UNLOCK rq->lock
1698 › *
1699 › * [task p]
1700 › * [S] p->state = UNINTERRUPTIBLE [L] p->on_rq
1701 › *
1702 › * Pairs with the UNLOCK+LOCK on rq->lock from the
1703 › * last wakeup of our task and the schedule that got our task
1704 › * current.
1705 › */
1706 › smp_rmb();
1707 › if (p->on_rq && ttwu_remote(p, wake_flags))
1708 › › goto stat;
1709
1710 #ifdef CONFIG_SMP
1711 › /*
1712 › * If the owning (remote) cpu is still in the middle of schedule() with
1713 › * this task as prev, wait until its done referencing the task.
1714 › */
1715 › while (p->on_cpu)
1716 › › cpu_relax();
1717 › /*
1718 › * Pairs with the smp_wmb() in finish_lock_switch().
1719 › */
1720 › smp_rmb();
1721
1722 › p->sched_contributes_to_load = !!task_contributes_to_load(p);
1723 › p->state = TASK_WAKING;
1724
1725 › if (p->sched_class->task_waking)
1726 › › p->sched_class->task_waking(p);
1727
1728 › cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags);
1729 › if (task_cpu(p) != cpu) {
1730 › › wake_flags |= WF_MIGRATED;
1731 › › set_task_cpu(p, cpu);
1732 › }
1733 #endif /* CONFIG_SMP */
1734
1735 › ttwu_queue(p, cpu);
1736 stat:
1737 › ttwu_stat(p, cpu, wake_flags);
1738 out:
1739 › raw_spin_unlock_irqrestore(&p->pi_lock, flags);
1740
1741 › return success;
1742 }
回调函数默认是一个通用的函数, default_wake_function, 会调用try_to_wake_up唤醒对应线程。
一下子也不好理解, 反正只要知道他是唤醒线程(根据常识设计肯定是从当时挂起的下一条指令开始执行)
ok, 先知其然吧...