不管是mutex还是spinlock,都限定了某一时刻只有一个线程可以获得临界区资源,但在某些场景下,临界区资源允许多个线程同时访问,这时就可以使用semaphore。Linux中semaphore的定义同mutex很相似,都是包含一个保护该结构体的spinlock和一个等待队列"wait_list"。struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
semaphore是没有"owner"的,它只需要一个标识共享资源数目的"count",因而也被称为counting semaphore。只要semaphore对应的"count"的值大于0,线程获取semaphore就可以成功,但它会使可进入临界区的线程数目减少(对应"count"值减1),所以其函数名为down(),基于存在的和mutex_lock()一样的问题(不能被signal打断),实际多使用的是down_interruptible()。如果count的值小于或等于0,说明当前进入临界区的线程数目已满,那么新的线程只能加入"wait_list"等待队列,进入休眠态。int down_interruptible(struct semaphore *sem){
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);
...
如果等待队列为空,释放semaphore会使可进入临界区的线程的数目增加(对应count值加1),反之则需要唤醒等待队列中的第一个waiter线程。void up(struct semaphore *sem){
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(list_empty(&sem->wait_list)))
sem->count++;
else
struct semaphore_waiter *waiter = list_first_entry(...);
list_del(&waiter->list);
wake_up_process(waiter->task);
raw_spin_unlock_irqrestore(&sem->lock, flags);
线程试图获取semaphore时可能因为资源不足,进入睡眠等待,但释放semaphore则不会,因而使用up()即可,不需要"up_interruptible()"之类的函数。从某种意义上来说,mutex可以被视作是一种"count"值只能为0和1的特殊semaphore,也就是binary semaphore(二值信号量)。但严格地来将,mutex和binary semaphore还是存在一些区别的。semaphore已经不能算是一种锁了,对于像mutex这样真正的锁,必须遵循“解铃还须系铃人”的原则,也就是“谁占有谁释放”。这在一定程度上限制了mutex的使用,比如一些内核和用户空间之间的交互,但同时也更不容易出错。就像rwlock之于spinlock,semaphore也对应了一个将read和write的操作路径区别对待的版本,即rwsem(reader/writer semaphore)。rwsem可视作是rwlock的可睡眠版本,它们在语义是相同的:在没有writer处于临界区时,允许多个reader同时进入临界区;在没有reader和其他writer处于临界区时,允许一个writer进入临界区。从"rw_semaphore"的结构体定义来看,好像就是在普通semaphore结构体的基础上,增加了一个"owner"。struct rw_semaphore {
atomic_long_t count;
atomic_long_t owner;
raw_spinlock_t wait_lock;
struct list_head wait_list;
但它并没有像"ww-mutex"的结构体包含"struct mutex"那样,直接包含"struct semaphore",因为虽然部分结构体元素的名称相同,但意义不同。普通semaphore中的"count"表示的是允许同时获取临界资源的线程数目,而rwsem中的"count"分成了若干部分,以32位系统为例,其中使用1个bit表示是否有writer持有rwsem,23个bits表示持有rwsem的reader数目。
阅读时点击图片可获得更清晰视图
"owner"用于表示获得rwsem的reader或writer信息。当一个writer线程获得rwsem后,它就会将自己的"task_struct"指针填入"owner",直到释放时才清除。如果排在等待队列首位的线程是一个reader,那么队列中将可能有多个reader被唤醒来获取这个rwsem,"owner"填入的将是最后一个获得rwsem的reader线程的"task_struct"指针。前面介绍mutex的时候曾经讲过,mutex结构体中曾经的"count"和"owner"经过重构后合二为一,这是因为mutex中"count"的值只会是0或者1,容易和指向"task_struct"的指针的值区分开来,但是这并不适用于rwsem,因为rwsem中的"count"的数值可能较大。同mutex类似,rwsem中的reader在等待时也可以采用optimistic spinning的方式,但如同rwlock中的writer受到的“不公正待遇”一样,rwsem中的writer可能需要等待多个reader的临界区结束,因而不适合使用optimistic spinning。