信号量结构很简单,各个成员的作用通过名字基本就知道干啥用的:
struct semaphore {
raw_spinlock_t lock;//保证信号量原则操作的锁
unsigned int count;//“量”,如果为1一般叫互斥量,>1叫做计数信号量
struct list_head wait_list;//当信号量无法获取时,进程可以在这排队等着
};
结合信号量的初始化,和大多数内核的基础结构一样,分为静态和动态方法:
#define __SEMAPHORE_INITIALIZER(name, n) \
{ \
.lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock), \
.count = n, \
.wait_list = LIST_HEAD_INIT((name).wait_list), \
}
#define DEFINE_SEMAPHORE(name) \
struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
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);
}
__SEMAPHORE_INITIALIZER对三个成员做了下初始化,DEFINE_SEMAPHORE和sema_init就是将它包装了一下。
信号量的P操作down[_xxx]:
void down(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);//(1)
if (likely(sem->count > 0))//(2)
sem->count--;
else
__down(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);//(1)
}
EXPORT_SYMBOL(down);
内部逻辑也很简单,因为可能同时有多个进程在执行P操作,sem->count自然而然就成了临界资源所以在①用自旋锁进行互斥。②成立及获得信号量,否则将调用__down进行下一步操作(sem->wait要登场了)
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 semaphore_waiter waiter;//(4)
list_add_tail(&waiter.list, &sem->wait_list);//(4)
waiter.task = current;//(4)
waiter.up = false;//(4)
for (;;) {
if (signal_pending_state(state, current))//(1)
goto interrupted;
if (unlikely(timeout <= 0))//(2)
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)//(3)
return 0;
}
timed_out:
list_del(&waiter.list);
return -ETIME;
interrupted:
list_del(&waiter.list);
return -EINTR;
}
直接看for循环即可,这个for循环退出的条件有三个
①.有信号待处理
②.超时
③.waiter.up为真
这里有个问题:①和②很显然是进程在等锁时中途有事或等太久了返回了,显然③是取到锁了,但是怎么没有执行count--呢?
答案就在up操作里,不过在看up的实现前先看看down的④是在做些什么:
先是构造了一个semaphore_waiter:
struct semaphore_waiter {
struct list_head list;
struct task_struct *task;
bool up;
};
然后把它挂进了sem->wait_list里面。
再看看up的实现,代码结构和down有相互呼应的感觉:
void up(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(list_empty(&sem->wait_list)))//(1)
sem->count++;
else
__up(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
EXPORT_SYMBOL(up);
从①出可以看到,通过判断sem->wait_list是不是为空来决定是进行sem->count++还是__up(sem),很容易就能猜到,__up应该会将sem->wait_list的第一个semaphore_waiter的up置成true,然后将其从链表中去掉,最后将semaphore_waiter 包含的task唤醒,等到调度时机到了,被唤醒的进程检查下自己的up是不是为真,就代表自己拿到信号量了。
举个例子:上厕所排队,厕所里面的人准备出来时发现外面有人在排队就直接从厕所门底下的缝钻了出来,然后对外面的人说:“里面没人了,你直接从门底下缝钻进去吧,这样省得开关(P/V)一次门”。
__up的具体实现:
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);
}
其实mutex的实现也差不多,只是mutex有个可选的功能:因为mutex类似于互斥信号量,只有两个值,但是mutex之中加入了owner,操作时通过判断mutex的owner来防止一些问题。互斥锁和信号量都是一种保护临界资源的方法,但是还是不能很好的做到任务之间的同步。
有个例子:
假如三个线程都参与调度运行,虽然可以保证val在某个线程访问完成时不被其他线程影响,但是我们的需求是val每次大于1024时thread_3都要去记录下来,那显然是不符合预期的。
struct x {
struct semaphore lock;
int val;
}
thread_1(){
down(x.lock);
x.val = random();
up(x.lock);
}
thread_2(){
down(x.lock);
x.val = random();
up(x.lock);
}
thread_1(){
down(x.lock);
if(x.val > 1024)
print("val > 1024!\n");
x.val = 0;
up(x.lock);
}