- A+
如果一个任务获取信号量失败,该任务就必须等待,直到其他任务释放信号量。本文的重点是,在Linux中,当有任务释放信号量之后,如何唤醒正在等待该信号量的任务。
信号量定义如下:
struct semaphore { raw_spinlock_t lock; unsigned int count; struct list_head wait_list; };
其中wait_list
链表用于管理因没有成功获取信号量而处于睡眠状态的任务。
任务通过调用down()
函数,尝试获取信号量,如果获取信号量失败,调用__down()
函数。__down()
函数内部调用了__down_common
函数。(事实上down()
函数有多个变种,如down_interruptible
,在获取信号量失败时调用__down_interruptible
,__down_interruptible
也会调用__down_common
函数。不同的down()
函数最终调用__down_common
时传入不同的参数,以处理不同的获取信号量的情况)。
同时,整个down()函数使用sem->lock
保护起来。
void down(struct semaphore *sem) { unsigned long flags; raw_spin_lock_irqsave(&sem->lock, flags); if (likely(sem->count > 0)) sem->count--; else __down(sem); raw_spin_unlock_irqrestore(&sem->lock, flags); } static noinline void __sched __down(struct semaphore *sem) { __down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); }
下面是重点:__down_common
函数如何使任务休眠,休眠中的任务如何被唤醒并获得信号量。
semaphore_waiter
是一个关键的数据结构,代表一个获取信号量失败,正在等待的任务。up
字段标识了该任务是否是被该信号量唤醒,也就是休眠中的任务收到某种信号被唤醒之后,判断是否是被等待中的信号量唤醒的。
struct semaphore_waiter { struct list_head list; struct task_struct *task; bool up; };
__down_common
函数首先初始化了一个semaphore_waiter
。task字段标识当前任务,up设置为false。
static inline int __sched __down_common(struct semaphore *sem, long state, long timeout) { struct semaphore_waiter waiter; list_add_tail(&waiter.list, &sem->wait_list); waiter.task = current; waiter.up = false; ...
然后休眠当前任务,调用 schedule_timeout()主动让出 CPU。上文提到整个函数都是在sem->lock
的临界区中,但是在自旋锁的临界区是不可以休眠的,所以这里实际上在休眠之前释放了锁,被唤醒之后再重新获得锁。
当任务被唤醒后,如果waiter.up
是否为真,则该任务可以获得信号量。waiter.up
是必须要判断的,取决于__set_current_state()
函数传入的参数不同,任务可能处于不同的休眠状态,可能被不同的信号唤醒,而未必是被等待的信号唤醒。
for (;;) { if (signal_pending_state(state, current)) goto interrupted; if (unlikely(timeout <= 0)) goto timed_out; __set_current_state(state); raw_spin_unlock_irq(&sem->lock); timeout = schedule_timeout(timeout); raw_spin_lock_irq(&sem->lock); if (waiter.up) return 0; } timed_out: list_del(&waiter.list); return -ETIME; interrupted: list_del(&waiter.list); return -EINTR; }
当一个任务释放信号量时,如果信号量的等待队列中存在任务,则将队列中的第一个任务的 up
标记为true,并唤醒,同时从等待队列中删除。
同时,只有在等待队列为空的情况下,才会更新sem->count
,确保了等待队列中的任务优先于新来的任务获得信号量,保证了严格的先进先出,不会因为新来的任务导致等待队列中的任务饥饿。
void up(struct semaphore *sem) { unsigned long flags; raw_spin_lock_irqsave(&sem->lock, flags); if (likely(list_empty(&sem->wait_list))) sem->count++; else __up(sem); raw_spin_unlock_irqrestore(&sem->lock, flags); } static noinline void __sched __up(struct semaphore *sem) { struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list, struct semaphore_waiter, list); list_del(&waiter->list); waiter->up = true; wake_up_process(waiter->task); }
任务被唤醒之后,检测到up
为true,返回0,成功获得信号量。