091114

|



菜園状況。チンゲンサイを収穫しました。ヨトウムシにやられずに済んだ本当
に数少ない収穫。大根の二次隊も、いいところまで来てやられてしまった...。



ジャガイモが生えていた。これは初夏に収穫したジャガイモの収穫し残しから 発芽したようだ。他にも、もう一株。これは楽しみ。

久々にハイエースを洗車。明日車検に出すので。ハイエースは5年で45000km。 後もう少しするとディーゼル規制にかかってしまう。やっぱりガソリンにして おけばよかったかな...でもディーゼルのサウンド、パワーフィールが好きなん だよね。
さて、スレッドの継続サポート、最後にx86。x86ポートはまだ中途で割込みま わりもきちんと書いてないけれど。
// construct IRET frame when stack is newly allocated.
void
thread_x86_iret_setup (struct iret_frame *iret, addr_t func, uint32_t arg)
{

IRET命令でCPUによって操作されるスタックの部分をstruct iret_frameとして、
そこを操作し、%espをこの構造体の一番最初にしてやることでスレッドをスイッ
チします。

  iret->eip = func;
  iret->arg = arg;
これは継続された先が引数を必要な時、即ち一番スレッドが一番最初に呼ばれる時用。

  iret->cs = cs_get ();
セグメントは現在のもの。マイOSではCSは固定。

  iret->eflags = eflags_get () & ~0x200;// Disable interrupt.
現在のEFLAGSをそのまま割込み禁止にします。

  iret->noreturn = (addr_t)md_thread_noreturn_assert;
継続された関数が、間違ってリターンすることがあればこの場所にあるアドレスに
リターンするので、デバッグ用のフックを入れておきます。
}

thread_t
md_thread_create (thread_t tc, thread_func_t start, uint32_t arg)
{
  /*
    Interrupt handler.
    Stack usage with no privilege-level change.

    +---------------+
    |     EFLAGS    |-4
    +---------------+
    |     CS        |-8
    +---------------+
    |     EIP       |-12 <-ESP
    +---------------+
    |  Error code   |-16 <-ESP (8,10,11,12,13,14,17)
    +---------------+
    |               |
           ...
    |               | stack_top
    +---+---+---+---+
      3   2   1   0


    THIS ROUTINES SETTING

                     stack_bottom
    +---------------+
    |  1st arg      |-4
    +---------------+
    |return address |-8
    +---------------+
    |     EFLAGS    |-12
    +---------------+
    |     CS        |-16
    +---------------+
    |     EIP       |-20  <-ESP
    +---------------+
    |               |
           ...
    |               | stack_top
    +---+---+---+---+
      3   2   1   0
   */
  // install return address for 'IRET'
  if (tc->tc_stack)
    {
      tc->regs.sp = (addr_t)tc->tc_stack->bottom - sizeof (struct iret_frame);
      // Setup stack for 'IRET'
スタックが既に用意されていれば準備します。
      thread_x86_iret_setup ((struct iret_frame *)tc->regs.sp,
			     (addr_t)start, arg);
    }
  else
    {
      // IRET frame is constructed when stack is allocated.
スタックが用意されてない場合、継続先はtc->continuationでとっておけるけ
れど、引数はとっておけない。(今迄のARM,SH,H8は最初のいくつかの引数はレ
ジスタ渡しだったので、この段階で復帰されるスイッチフレームのレジスタに
引数を設定しておけばよかった。)
ここでは仕方なく、引数はESPに置いておくことにした。スイッチフレームはス
レッドが使われるまでいじられないので、どこでもいい。
でもちょっとコードの見た目的に素直じゃないけれど、一番最初のスレッドの
呼出しのためだけに、なにかを用意するのも無駄だということで。
      tc->regs.sp = arg;	//XXX kludge hack.
      tc->continuation = (continuation_func_t)start;
    }
  LPRINTF ("[%d]:%s func=%A esp=%x\n",  tc->id, tc->name, start, tc->regs.sp);

  return tc;
}

void
md_thread_stack_discard (thread_t tc)
{

  tc->regs.sp = 0;
}

void
md_thread_continuation_setup (thread_t tc)
{
  uint32_t arg = tc->regs.sp;	//XXX kludge hack.
上記でしたように、一番最初にスレッドが起動する時にはその引数がspに入っ
ているのでそれをとっておきます。

  // Setup register set for the next context switch.
  // Already stack is attached.
  tc->regs.sp = (addr_t)tc->tc_stack->bottom - sizeof (struct iret_frame);
IRET命令が操作するフレームを差すようにスタックポインタを設定。

  if (tc->continuation)
    {
      // Setup stack for 'IRET'
新しく割当てられたスタックなので、その底にIRET用のフレームを設定。
      thread_x86_iret_setup ((struct iret_frame *)tc->regs.sp,
			     (addr_t)tc->continuation, arg);
      tc->continuation = NULL;	// scheduled now.
    }
}

void
md_thread_continuation_call (continuation_func_t cont)
{
  // Rewind stack and call continuation.
  current_thread->continuation = NULL;	// dispatch here.
  __asm volatile ("movl %1, %%esp\n"
		  "pushl %0\n"
		  "jmp *%2" ::
		  "g"(md_thread_noreturn_assert),
		  "g"(current_thread->tc_stack->bottom), "r"(cont));
  // NOTREACHED
}

この関数はブートストラップ時に一番最初のスレッドを初めるのにしか使われない。

void
md_thread_continuation_call_with_arg (thread_func_t cont, uint32_t arg)
{
   // Rewind stack and call continuation with arg.
  __asm volatile ("movl %2, %%esp\n"
		  "pushl %0\n"
		  "pushl %1\n"
		  "jmp *%3" ::
		  "r"(arg),
		  "g"(md_thread_noreturn_assert),
		  "g"(current_thread->tc_stack->bottom), "r"(cont));
}

これで実装終わり。ロガーに戻ります。ロガーのスレッドをこの継続を使うよ
うに書き直します。実際書き直してみると

while (1)
{
  thread_sleep()
   何かする
}

を

start:
  何かする
  thread_block(start)

に書き替えることになるので、while(){}をdo{}whileに書き替えるような不自
由さがある。

延々と書き替えて、ついにここまで。

log> help
---Monitor---
U ringbuffer
        lock : 
        event: 3(uart send) 
U ringbuffer
        lock : 
        event: 
---Ready Queue---
<0>: 
<1>: 
<2>: 
<3>: 1 
---Thread Status---
id   pri(used/total)
[7] W 3 (no stack) gps
[6] W 1 (no stack) data
[5] W 1 (no stack) storage
[4] W 3 (no stack) controller
[3] W 0 (no stack) uart send
[2] W 0 (no stack) uart recv
[1] R 3 (256/1028) root
avaliable command: help reset gps font contrast dac rtc rm ls logger test fs write read sdinit 
log> 

rootスレッドはシェルプロンプトで、これは結構深い位置でブロックするので
スタックを放棄するのが難しく、専用スタック。それ以外は全て放棄できるよ
うにした。
ここで、いやらしく思いっきりスピードセンサの割り込みを入れると、スタック
が足りなくなった。スタックは放棄できるスレッド用に二つだけ用意した。

Asssertion failed. ../../..//kernel/thread_stack.c, 116 caller:0x4000216c
---Monitor---
U ringbuffer
        lock : 
        event: 3(uart send) 
W ringbuffer
        lock : 
        event: 1(root) 
---Ready Queue---
<0>: 3 
<1>: 6 
<2>: 
<3>: 7 4 
---Thread Status---
id   pri(used/total)
[7] r 3 (716/1028) gps
[6] r 1 (440/1028) data
[5] W 1 (no stack) storage
[4] r 3 (no stack) controller
[3] r 0 (no stack) uart send
[2] W 0 (no stack) uart recv
[1] W 3 (412/1028) root

これはどういう事態かというと、gpsは一番優先度を低くしている。そしてgps
のパーサが時間がかかる。その間にスピードセンサからの割り込みが入って、
その終了時に再スケジューリンングされて、優先度の高いdataスレッドが走る
ので、gpsスレッドはスタックを持ったまま、制御を渡し、dataスレッドが実行
中にprintfして、さらに優先度の高い(この設定は間違い)uart sendスレッドに
渡ってしまった。優先度が高くとも割込みさえ入らなかければ制御は渡さない
のだけれど、どこか、controllerの10msecごとの割り込みか、次のスピードセ
ンサからの割り込みからかで、制御が移ってしまった。

つまりスレッドの優先度の数で、最悪消費されるスタックの数が決まる。この場合
スレッドの優先度が0,1,3の3つあるので、割り合て可能なスタックを三つはも
たないといけない。スレッドが同じ優先度にある分には、自分で制御を明け渡
さない限りその優先度にあるスレッドには制御がまわらないので、スタックを
さらに消費することはない。

これはUnix/Mach的な時分割スケジューリングとは違う。ITRON的なスケジュー
リング。