自旋锁是一种忙等的锁,会一直占用cpu;而信号量则运行进程进入睡眠状态,即获取锁失败的进程可以进行睡眠,主动让出cpu的使用权。
信号量定义:
struct semaphore {
raw_spinlock_t lock;//用于保护成员count和wait_list
unsigned int count;//允许进入临界区的内核执行路径个数
struct list_head wait_list;//用于管理在该信号量上睡眠的进程
};
自旋锁初始化:
static inline void sema_init(struct semaphore *sem, int val)
{
static struct lock_class_key __key;
*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}
#define __SEMAPHORE_INITIALIZER(name, n) \
{ \
.lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock), \
.count = n, \
.wait_list = LIST_HEAD_INIT((name).wait_list), \
}
#define __RAW_SPIN_LOCK_INITIALIZER(lockname) \
{ \
.raw_lock = __ARCH_SPIN_LOCK_UNLOCKED, \
SPIN_DEBUG_INIT(lockname) \
SPIN_DEP_MAP_INIT(lockname) }
#define __RAW_SPIN_LOCK_UNLOCKED(lockname) \
(raw_spinlock_t) __RAW_SPIN_LOCK_INITIALIZER(lockname)
down获取信号量,up释放信号量
down:访问count需要独占访问。使用raw_spin_lock_irqsave保证独占访问,屏蔽本cpu中断。如果count>0表示还有资源(count)能够访问,成功获取信号量。将count--。反之获取信号量失败,进入__down()。
可以看到count可以被看做资源的个数或者说是表示能够同时指出多少个人访问。即信号量支持同时被多人访问。当count被初始化为1时,它就变得和自旋锁类似,变为了独占访问,但是信号量可以睡眠,自旋锁不行。
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);
}
__down_common:一个死循环,在这个循环中不断的调用schedule_timeout让出cpu,指定信号量释放后被唤醒,或者被其他信号打断,才退出循环。
1、有待处理的信号;2、等待信号量超时;3、其他人释放信号量被唤醒(即成功获得信号量)
那如果是前两个原因导致的返回,这不代表信号量并未获取成功吗?但是__down并未对返回值进行判断,他们是怎么知道是成功获取了信号量,还是并未获取到呢???明天在看看
解答:
1、可以看到__down_common里面传入的TASK_UNINTERRUPTIBLE:一种不可中断的睡眠状态,不可以被信号打断,必须等到等待的条件满足时才被唤醒。TASK_UNINTERRUPTIBLE只能被wake_up()唤醒
2、signal_pending_state函数实现,可以看到state为TASK_UNINTERRUPTIBLE一直都是返回的0,也从代码上验证了不可以被信号打断
3、__down_common传入的时间是MAX_SCHEDULE_TIMEOUT,在函数schedule_timeout的实现可以看到必然不会因为超时返回。
基于以上三点原因,得出down函数的返回只可能是获取到信号量而返回,因此也不需要对返回值进行判断。而对于其他函数,eg down_interruptible等,则需要返回值进行判断,详情见文末。
static inline int signal_pending_state(long state, struct task_struct *p)
{
if (!(state & (TASK_INTERRUPTIBLE | TASK_WAKEKILL)))
return 0;
if (!signal_pending(p))
return 0;
return (state & TASK_INTERRUPTIBLE) || __fatal_signal_pending(p);
}
static noinline void __sched __down(struct semaphore *sem)
{
__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
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);
/* 让出cpu一段时间 */
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;
}
对于down:schedule_timeout传入的MAX_SCHEDULE_TIMEOUT。因此对于down的动作就是不断调用schedule主动换下cpu,当被再次调度的时候,继续检查被唤醒的原因。如果进程是超时或者被其他人发生信号,则转到timeout或者interrupted处。如果是waiter.up == true,则表示其他人释放了信号量被唤醒。
signed long __sched schedule_timeout(signed long timeout)
{
struct timer_list timer;
unsigned long expire;
switch (timeout)
{
case MAX_SCHEDULE_TIMEOUT:
schedule();
goto out;
default:
if (timeout < 0) {
printk(KERN_ERR "schedule_timeout: wrong timeout "
"value %lx\n", timeout);
dump_stack();
current->state = TASK_RUNNING;
goto out;
}
}
expire = timeout + jiffies;
setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
__mod_timer(&timer, expire, false, TIMER_NOT_PINNED);
schedule();
del_singleshot_timer_sync(&timer);
/* Remove the timer from the object tracker */
destroy_timer_on_stack(&timer);
timeout = expire - jiffies;
out:
return timeout < 0 ? 0 : timeout;
}
信号量释放
void up(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
/*
等待队列为空,说明没有人因为获取信号量失败为休眠
直接将count++即可
*/
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);
}
down_interruptible:可以看到该函数就需要对返回值进行判断,以便知道是否成功获取信号量
int down_interruptible(struct semaphore *sem)
{
unsigned long flags;
int result = 0;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;
else
result = __down_interruptible(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
return result;
}
同样看传入的参数TASK_INTERRUPTIBLE和MAX_SCHEDULE_TIMEOUT。其中TASK_INTERRUPTIBLE是可以被信号和wake_up()唤醒,当信号到来时,进程会被设置为可运行
因此对于这个函数存在成功获取信号量和因为待处理的信号导致信号量获取失败的两种情况。所以需要返回值辅助判断。
static noinline int __sched __down_interruptible(struct semaphore *sem)
{
return __down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
相似的还有__down_timeout以及__down_killable等