1. void e_assert_wait(tid_t event_word, boolean_t interruptible) ... int e_block_thread()配对使用在效果上等同于int e_sleep_thread(tid_t *event_word, void *lock_word, int flags),两者的区别在于e_sleep_thread在睡眠之前会释放掉参数lock_word所指定的锁,而e_assert_wait()与e_block_thread()每个独立个体均不涉及锁的操作,所以:如果有多于1个锁需要释放,那么应该放在e_assert_wait()与e_block_thread()之间,例如:
e_assert_wait();
simple_unlock(&lock0);
simple_unlock(&lock1);
simple_unlock(&lock2);
simple_unlock(&lock3);
e_block_thread();
如果此处我们和Linux内核做个类比,那么大约是:
e_assert_wait() -------- enter_waitqueue(current); __set_current_state(TASK_INTERRUPTIBLE);
e_block_thread() -------- schedule()
这使得e_assert_wait()和e_block_thread()在不同进程间的同步方面有了独一无二的价值,比如下面的使用场景:有两个进程A和B
process_A()
{
e_sleep_thread(event0, ...);
e_wakeup(event1);
}
process_B()
{
e_wakeup(event0);
e_sleep_thread(event1, ...);
}
上面两个进程的本意是,B先唤醒A,然后等待A先结束,然后B退出。但是这里有个潜在的问题,在B通过e_wakeup唤醒A的时候,调度器的行为不可预测,这导致A的e_wakeup(event1)的调用有可能早于B的e_sleep_thread(),这种情形的出现将使得B一直睡眠(丢失了一个event1上的唤醒)。为了解决这个问题,必须将B写成这样:
process_B()
{
e_assert_wait(event1, ...);
e_wakeup(event0);
e_block_thread();
}
改写后的B之所以能解决死锁的问题,是因为它先调用e_assert_wait将B加入event1的队列中,如果A被B唤醒后先于B调用e_wakeup(), 那么将会导致B从event1队列中移出,这样B后续的e_block_thread的调用不会导致sleep. 而如果A被B唤醒后,调度器先调度B执行e_block_thread(),这样B睡眠,直到A唤醒之。因此可以保证B退出时,A一定已经退出。
如果只有一个锁,那么:
e_assert_wait();
simple_unlock(&lock0);
e_block_thread();
就直接等同于:
e_sleep_thread(.., &lock0, ...);
为了叙述简化起见,称{e_assert_wait, e_block_thread}为S1函数, e_sleep_thread为S2函数。
e_sleep_thread有四个类型的返回值:THREAD_AWAKENED, THREAD_INTERRUPTED, THREAD_TIMED_OUT, THREAD_OTHER,这些返回值完全等同e_block_thread,这从另一个侧面反映出S1与S2的一致性。第一个返回值THREAD_AWAKENED无需多言,当S1或者S2被e_wakeup*等函数唤醒时返回该值,即所谓normal wakeup。第二种返回值THREAD_INTERRUPTED实际上表明S1或者S2除了被e_wakeup*等函数normal wakeup之外,还可以通过signal被唤醒,比如kthread_kill()的调用。第三种返回值THREAD_TIMED_OUT猛一看有点困惑,因为无论S1还是S2,其实都不涉及到时间参数,这个返回值主要是e_clear_wait()和e_wakeup_w_result()来做唤醒时,附加的参数。第四种返回值很少用,此处不提。
2. e_clear_wait()这个唤醒函数有点特别,其原型为:void e_clear_wait(tid_t tid,int result)
特别之处在于其第一个参数是tid_t tid,而不是tid_t event_word,所以它跟event没有什么关系,即不是一个EVENT BASED的唤醒,它是基于thread id,如此就很容易区分它与其他e_wakeup*的区别:e_clear_wait()能明确确定某一个thread,而不是一堆sleep在event_word上的thread,再直白一点:它唤醒一个特定的由tid标识的thread,而不是一大堆睡眠在event_word上的众多的thread. e_clear_wait很专一,不花心. 用一个具体的例子说明:
tid_t g_event = EVENT_NULL; //全局变量,表示一个event
tid_t g_tid; //全局变量,表示一个kthread id
//内核线程0
void kthread_0(void *data)
{
...
int rc;
g_tid = thread_self(); // kernel service, get the id of current kthread
rc = e_sleep_thread(&g_event, NULL, INTERRUPTIBLE); //sleeping...
...
}
假使我们想要唤醒内核线程0,有几种做法呢?
方法1(EVENT BASED):
void kthread_1(void *data)
{
...
e_wakeup*(&g_event); // normal wakeup for kthread_0, rc=THREAD_AWAKENED
...
}
方法2(Signal based)
void kthread_1(void *data)
{
...
kthread_kill(g_tid, SIGTERM); // interrupted wakeup for kthread_0, rc = THREAD_INTERRUPTED,不要轻易使用SIGSTOP,否则程序行为很怪异
...
}
对于方法2,如果我们将kthread_0中的e_sleep_thread(&g_event, NULL, INTERRUPTIBLE)改为e_sleep_thread(&g_event, NULL, 0),则signal wakeup无效:不唤醒睡眠进程,但kill出的signal将pending直到被sig_chk().
方法3(thread based)
void kthread_1(void *data)
{
...
e_clear_wait(&g_tid, result); // wakeup for kthread_0, rc = result
...
}
注意:方法3没有使用g_event,而是直接使用睡眠线程的id.