(linux/kernel/locking/semaphore.c)
down()---->__down()---->__down_common()
down_interruptible()---->__down()---->__down_common()
down_killable()---->__down_killable()---->__down_common()
down_trylock()---->__down_trylock()---->__down_common()
down_timeout()---->__down_timeout()---->__down_common()
up()---->__up()---->__up_common()
我们以down()为例进行分析
(1)、 在down()函数中,会先检查sem->count变量,如果>0表示获得该锁,直接返回,程序会继续往下跑,进入临界区; 如果<=0,则表示未获得该锁,则调用__down()进行排队和睡眠;
这里有个疑问,已知在__down()中会进入睡眠,而在spinlock的临界区是不允许睡眠的? 那么这里怎么回事呢?莫急,请继续往下看
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);
}
(2)、在__down()中,传入TASK_UNINTERRUPTIBLE标志后,直接调用__down_common
static noinline void __sched __down(struct semaphore *sem)
{
__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
(3)、在__down_common中,我们可以看到,首先将当前的task加入到了sem->wait_list,然后调用schedule_timeout进行睡眠.
注意在睡眠之前,调用了raw_spin_unlock_irq,释放自旋锁. 睡眠时间到了之后再调用raw_spin_lock_irq获得该锁, 这也就回答了在(1)中的疑问
static inline int __sched __down_common(struct semaphore *sem, long state,
long timeout)
{
struct task_struct *task = current;
struct semaphore_waiter waiter;
list_add_tail(&waiter.list, &sem->wait_list);
waiter.task = task;
waiter.up = false;
for (;;) {
if (signal_pending_state(state, task))
goto interrupted;
if (unlikely(timeout <= 0))
goto timed_out;
__set_task_state(task, 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;
}
总结一下 信号量semaphore:
- down()函数,sem->count是一个计数功能,大于0表示获得该锁,则直接返回进入临界区。<=0则说明未获得该锁,调用__down(),该进程则加入到sem->wait_list,
然后该进程调用schedule_timeout进入睡眠状态; - up()函数,先检查sem->wait_list,则表示没有需要唤醒的task,直接返回;
否则的话调用__up(),先从sem->wait_list删除这个task,然后再调用wake_up_process(waiter->task),唤醒这个task;