深入理解Solaris内核中互斥锁(mutex)与条件变量(condvar)之协同工作原理

时间:2022-10-20 20:15:17

在Solaris上写内核模块总是会用到互斥锁(mutex)与条件变量(condvar), 光阴荏苒日月如梭弹指一挥间,Solaris的大船说沉就要沉了,此刻心情不是太好(Orz)。每次被年轻的有才华的同事们(比如Letty同学)问起mutex和cv怎么协同工作的,我总是不能给出一个非常清晰的解释。直到今天,看了cv_wait()的源代码之后,我终于可以给他们一个清楚明白的回答了。

Solaris的源码无法被公开粘贴出来,幸好还有OpenSolaris的继承者illumos。 先贴cv_wait()的源码,再讲互斥锁(mutex)与条件变量(condvar)的协同工作原理。

 /*
186 * Block on the indicated condition variable and release the
187 * associated kmutex while blocked.
188 */
void
cv_wait(kcondvar_t *cvp, kmutex_t *mp)
{
if (panicstr)
return;
ASSERT(!quiesce_active); ASSERT(curthread->t_schedflag & TS_DONT_SWAP);
thread_lock(curthread); /* lock the thread */
cv_block((condvar_impl_t *)cvp);
thread_unlock_nopreempt(curthread); /* unlock the waiters field */
mutex_exit(mp);
swtch();
mutex_enter(mp);
}

注意: 198, 200-202行,等会儿再解释。

首先,一个典型的使用mutex和cv的例子是这样子滴,

 static kmutex_t         mutex;
static kcondvar_t condv;
static unsigned int ready = ; /* 1. init mutex and cv */
mutex_init(&mutex, NULL, MUTEX_DRIVER, NULL);
cv_init(&condv, NULL, CV_DRIVER, NULL); /* 2. use mutex and cv */ /* Thread 1 */ | /* Thread 2 */
mutex_enter(&mutex); | mutex_enter(&mutex);
while (ready == ) | ready = ;
cv_wait(&condv, &mutex); | cv_signal(&condv);
mutex_exit(&mutex); | mutex_exit(&mutex); /* 3. destroy mutex and cv */
cv_destroy(&condv);
mutex_destroy(&mutex);
  1. 在Thread 1中,首先获得互斥锁,然后判断条件(ready==0)是否成立,如果成立,则调用cv_wait(&condv, &mutex)进入睡眠;
  2. 在Thread 2中,首先获得互斥锁,然后将ready赋值为1,调用cv_signal(&condv)唤醒正在睡眠的Thread1,同时释放持有的互斥锁;
  3. Thread1一旦醒来,会重新判断条件(ready==0)是否成立,如果不成立,则释放互斥锁。 (当然,如果成立,则将再次进入睡眠,等待下次被唤醒)

然后, 问题(Letty同学曾经问过我的来了既然Thread 1在L12行获得了互斥锁然后睡过去了,那么Thread 2怎么可能获得互斥锁?

This is a good question, a really good question! (ps. 老美每次被问住了的时候都这么说)

在今天之前我无法回答,或者只能估摸着回答说"只能看具体实现了"。 好了,我今天就是真看完了具体实现。在cv_wait()的源代码中,

198    cv_block((condvar_impl_t *)cvp);
...
200 mutex_exit(mp);
swtch();
mutex_enter(mp);

第198行将自己(currthread)加入睡眠队列,第200行将互斥锁释放,然后在第201行进入睡眠,等待被唤醒。一旦被唤醒,在第202行重新获得互斥锁。

也就是说,睡前释放互斥锁,醒来再获取互斥锁。这样别的线程就有机会获得互斥锁后干活,活干完后将睡眠的线程唤醒。

这也解释了为什么cv_wait()函数不仅仅只有一个参数kcondvar_t *cvp, 还包含参数kmutex_t *mp。

行文至此,我想用一句话作为总结,"The source code is the final world." 如果你想成为一个非常优秀的程序员,请记住RTFSC

深入理解Solaris内核中互斥锁(mutex)与条件变量(condvar)之协同工作原理

PS:

1. 如果想进一步弄懂为什么要将条件变量和互斥锁一起使用保证同步,请自行google或阅读OS相关的book。

2. 所有关于互斥锁和条件变量的协同工作原理应该是一致的,比如POSIX的pthread_mutex和pthread_cond,Linux内核mutex和completion variable等。