AIX内核的睡眠及唤醒

4820阅读 1评论2014-02-20 MagicBoy2010
分类:AIX

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_AWAKENEDTHREAD_INTERRUPTEDTHREAD_TIMED_OUTTHREAD_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_waittid_t tidint 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.   



上一篇:AIX内核定时器的使用
下一篇:封装在AIX下的Linux spin lock与mutex的接口代码片段

文章评论