081225

|


シリアルコンソールの出力をリングバッファ経由にしてみました。これはかな
りのスレッド周りのデバッグになる。わらわらと至らない点がでてきました。

#define	RINGBUFFER_SIZE(x)	(sizeof (struct ringbuffer) + (x))

#define	FIFO_SIZE	8

uint8_t rbuf[RINGBUFFER_SIZE (FIFO_SIZE)];
uint8_t sbuf[RINGBUFFER_SIZE (FIFO_SIZE)];

void send_thread (uint32_t);
ringbuffer_t rb_send

void __sci_putc (int8_t);

void
sci_console (uint32_t arg)
{
  int id;
  rb_send = ringbuffer_init (sbuf, FIFO_SIZE);

  thread_create (send_thread, &id, (uint32_t)rb_send);
  thread_start (id);
  thread_switch (); // make sure to send_thread ring_buffer_read is waiting.

モニタのセマンティクスは、先にWAITで次にシグナル。先に口を開けるように
しておかないで書きはじめると、「バッファがフルだから、抜いて!」というシ
グナルを送ってもその受け手がいないのでデッドロックする。シグナルを送る
時点で受け手がいなければ捨てられてしまうはモニタの定義。

  sci_putc = __sci_putc;

  return;
}

void
send_thread (uint32_t arg)
{
  ringbuffer_t rb = rb_send;
  uint8_t buf[FIFO_SIZE];
  int i;

  while (/*CONSTCOND*/1)
    {
      size_t sz = ringbuffer_read (rb, buf, FIFO_SIZE);
      for (i = 0; i < sz; i++)
	{
	  while ((SCI1->SSR & SSR_TDRE) == 0)
	    ;
	  SCI1->TDR = buf[i];
	  SCI1->SSR &= ~SSR_TDRE;
 リングバッファが空でなければringbuffer_readのブロックが落ちるので
その分を書きこみます。
	}
    }
}

void
__sci_putc (int8_t c)
{
  int8_t cr = '\r';

  if (c == '\n')
    {
      ringbuffer_write (rb_send, &cr, 1);
    }
  ringbuffer_write (rb_send, &c, 1);
}

こんな感じで動かしてみたところ、デッドロックしまくり。

monitor_enter:lock by 2              書き込みに入った。
W< i(0) o(0) f(8) s(8) remain=1
====>1 8 1
monitor_signal:[2] signal-->1        空で待っているの(1番)がいたので起こします。
monitor_exit: [2] wakeup------>1     exitでthread_wakeupしました。
monitor_exit: unlock by 2
monitor_enter:lock by 3              !しかしここで(1番)の前に他が入った!
W< i(1) o(0) f(7) s(8) remain=1
====>1 7 1
monitor_signal:[3] signal-->1        さっきのシグナルで起きてないのでまた起こし。
thread_sleep: wakeup: 1       いきなり(1番)が起きた。タイマのスケジューリングで
monitor_wait: lock by 1     waitはロックをとれることが保証されてるのでロック。
monitor_wait: [1] steeled lock from [3]  !!!!ここでレース!!!!
XXXXXXXXXX Asssertion failed. kernel/monitor.c, 153 XXXXXXXXXX
                                R:filled! ==>2 2 8 6
                                R> i(2) o(2) f(8) s(8)
monitor_exit: unlock by 1
XXXXXXXXXX Asssertion failed. kernel/monitor.c, 66 XXXXXXXXXX
monitor_exit: unlock by 3
ttmonitor_enter:lock by 1
                                R< i(2) o(2) f(8) s(8)
                                R:empty!
monitor_wait: unlock by 1
thread_sleep: sleep: 1
W> i(2) o(2) f(8) s(8)
monitor_enter:lock by 2
W< i(2) o(2) f(8) s(8) rema

つまり、monitor_exitでthread_wakeupしても、それはレディキューにつないだ
だけ。exitの時点で既にenter待ちよりは先に実行されるけれど、exitしてから
ラウンドロビンした先が(この時点ではまだenterしていない)、enterしたらそ
れが入ってしまうのでレース状況が発生してしまう。exitが終了する所で、イ
ベント待ちのは全て処理されないといけないんだ。

ということで、monitor_exitを変更

void
monitor_exit (monitor_t m)
{
  struct thread_control *tc;
  int s = intr_suspend ();

  assert (m->lock_thread == current_thread);
  // wakeup scheduled thread if any.
  // monitor assures wakeuped thread can handle resource.
  SIMPLEQ_FOREACH (tc, &m->event_queue, tc_monitor_event)
    {
      if (!tc->monitor_wakeup_request)
	continue;
      DPRINTF ("%s: [%d] wakeup------>%d\n", __FUNCTION__, current_thread->id,
	       tc->id);
イベント待ちのスレッドの優先度を最高レベルに上げてmonitor_enterに入るスレッド
より先に処理することを確定させます。

      if (!tc->inherit_priority)
	tc->native_priority = tc->priority;
      tc->inherit_priority = TRUE;

      thread_priority (tc->id, 0);
イベント待ちのスレッドはTHR_WAIT状態なので、コントロールブロックの値が
変更されるだけで、switchは行なわれません。(したところで、レディキューに
登録されてないので実行されることはないので)

      thread_wakeup (tc->id);
ここで、レディキューにつながれます。つながれるだけでスイッチはしません。
    }

  while ((tc = SIMPLEQ_FIRST(&m->lock_queue)) != NULL)
    {
      SIMPLEQ_REMOVE_HEAD(&m->lock_queue, tc_monitor_lock);
      thread_wakeup (tc->id);
    }
ここでロック待ちのスレッドがレディキューにつながれます。

  m->state = MON_UNLOCKED;
  m->lock_thread = NULL;
  current_thread->monitor_event = 0;
  // Inherit priority.
  if (current_thread->inherit_priority)
    {
      SCI_PRINTF_BOOT ("%s: inherit priority [%d] (%d->%d)\n",
		       __FUNCTION__, current_thread->id,
		       current_thread->priority,
		       current_thread->native_priority);
      current_thread->inherit_priority = FALSE;
      thread_priority (current_thread->id, current_thread->native_priority);
もし、ロック待ち、あるいは、イベント待ちで優先度継承されていたら元に戻します。
    }
  DPRINTF ("%s: unlock by %d\n", __FUNCTION__, current_thread->id);

  thread_switch (); //make sure to run thread that wakeuped by event.
ここでスイッチすることで、確実にイベント待ちのスレッドがmonitor_exitを
抜けた後に実行されることを確定させます。

  intr_resume (s);
}

結果は。

fiber 1
fiber 2
fiber return.
monitor_exit: inherit priority [1] (0->1)
tttthhhhrrrreeeeaaaadddd    2345((((rrrseeeecccneeediiievvvreee)rrr:))) :::a   raaagrrr=ggg8===82488
変な出力ですが、一文字putcして、そこでモニタのスケジューリングが入ってそれぞれの
スレッドがディスパッチされて一つのリングバッファに入るのでこうなります。


monitor_exit: inherit priority [1] (0->1)
monitor_exit: inherit priority [4] (0->1)

monitor_exit: inherit priority [1] (0->1)
thread 4: RECV 8
ohayodesyo1234

なんとかなったかな。