其实实现了一种管程
xv6的锁,用的时候都是acquire和release包起来,保证互斥。中间可以sleep,睡觉过程中,放弃锁,别人可以进入临界区。醒来又要获得锁(因为又重新进入临界区了)。
wakeup和Semaphore的signal()操作不一样,不会累计唤醒次数。如果此刻没有进程在等这个chan,就什么也不做。
综上,实际上sleep-lock在这里,相当于实现了一个Brinch Hansen(1975)的monitor。
也就是访问临界区的过程要互斥,唤醒操作必须是管程过程的最后一句。
sleep函数
sleep(void *chan, struct spinlock *lk)
第一个参数chan并不一定要用什么,只要你释放掉当前持有的spinlock (这里是q->lock
)就可以。这里chan直接用q
,只是方便,不是必须的。
NetBSD
其实在别的操作系统上,是用mutex
和conditional variable
实现的。比如NetBSD。
参见:https://netbsd.gw.com/cgi-bin/man-cgi?condvar+9+NetBSD-current
pthread也提供了类似的原语。
疑问
xv6有两套锁的原语(primitives),一套是acquire/release,就是spinlock。
另一套是acquiresleep/relasesleep。这里与sleep配合使用的,竟然是普通的spinlock。
这就意味着如果多个进程要抢这个锁,其他进程要忙等,而不是去睡觉。
我以为sleep一定要配合sleep版的acquire和release使用。
xv6源码
400 struct q {
401 struct spinlock lock;
402 void *ptr;
403 };
404
405 void*
406 send(struct q *q, void *p)
407 {
408 acquire(&q->lock);
409 while(q->ptr != 0)
410 ;
411 q->ptr = p;
412 wakeup(q);
413 release(&q->lock);
414 }
415
416 void*
417 recv(struct q *q)
418 {
419 void *p;
420
421 acquire(&q->lock);
422 while((p = q->ptr) == 0)
423 sleep(q, &q->lock);
424 q->ptr = 0;
425 release(&q->lock);
426 return p;
427 }