シリアルドライバを継続を使うように書きかえた。このように単純に割り込み
を待って処理してまたすぐ寝るような、実行時だけのスタックがあればいいよ
うなスレッドにはスタック放棄が効く。これでマイOSでもスタックの量が減っ
たのだけど、スタックの量は確定しない。それはスタックを放棄できるスレッ
ドでもスタックを放棄せずにブロックすることがあるからだ。ここは微妙だ...。
void
uart_recv_thread (uint32_t arg)
{
__st_recv = (struct uart_thread *)(addr_t)arg;
__st_recv->started = TRUE;
thread_wakeup (parent_thread);
recv_loop ();
// NOTREACHED
}
受信スレッドにおいては単純な継続への置き換えができる。
void
recv_loop ()
{
uint8_t buf[UART_FIFO_SIZE];
size_t sz;
cpu_status_t s;
s = intr_suspend ();
sz = rbuf_nolock_read (uart_recv_buf, buf, UART_FIFO_SIZE);
intr_resume (s);
ここのリングバッファの書き込みでは、消費者スレッドがバッファを消費して
なく、バッファが満杯であれば、スタックを保持したままブロックする可能性
がある。
if (sz)
ringbuffer_write (__st_recv->rb, buf, sz);
ここでブロックしたところでスタックを放棄して、次にスケジューリングされた
時にはrecv_loopの最初からスタックは巻戻されて実行される。
thread_block (recv_loop);
// NOTREACHED
while (/*CONSTCOND*/1)
;
}
送信の場合は、実のところスレッドにしなくてもいい。ここは富豪的。
void
uart_send_thread (uint32_t arg)
{
__st_send = (struct uart_thread *)(addr_t)arg;
__st_send->started = TRUE;
thread_wakeup (parent_thread);
send_loop ();
// NOTREACHED
}
void
send_loop ()
{
uint8_t buf[UART_FIFO_SIZE];
size_t i;
while (/*CONSTCOND*/1)
{
バッファが空の場合、ブロックし、供給者スレッドがバッファに入れたところ
で起こされる。このブロックするところは結構ネストしたところでブロックす
るため(ARMだとスタックを120-140byteほど使ったところ)、ちょっと苦心がい
る。ここでringbuffer_read_extという継続を使うためのインターフェースを新
に用意した。
size_t sz = ringbuffer_read_ext (__st_send->rb, buf, UART_FIFO_SIZE, send_loop);
for (i = 0; i < sz; i++)
md_uart_putc1 (buf[i]);
}
}
size_t
ringbuffer_read_ext (ringbuffer_t rb, uint8_t *buf, size_t size,
continuation_func_t cont)
{
monitor_t mon = &rb->mon;
monitor_enter (mon);
DPRINTF ("\t\t\t\tR< i(%d) o(%d) f(%d) s(%d)\n", rb->input, rb->output,
rb->free, rb->size);
if (rb->free == rb->size)
{
DPRINTF ("\t\t\t\tR:empty!\n");
if (!rb->event_hooked)
{
rb->event_hooked = TRUE;
バッファが空なのであれば、モニタのロックを外して、継続を呼ぶ。この例の場合
これはsend_loopの先頭に戻るということ。
monitor_event_hook (mon, RINGBUFFER_EMPTY, cont);
thread_block (cont)
// NOTREACHED (jump to continuation.)
}
}
もしバッファがあって、このスレッドを起すフックがあるならそれを解除。
if (rb->event_hooked)
{
rb->event_hooked = FALSE;
monitor_event_unhook (mon);
}
return __read_subr (rb, buf, size);
}
これによって、この手のスレッドは全てスタックを共有できるようになった。
これはSH4Aの例
=> go 0x89000000
## Starting application at 0x89000000 ...
stack_start: 0x89100000
RAM data: 0x89009558-0x89009ac4 1388byte
bss: 0x89009ae0-0x89015620 47936byte
Privilege-mode, bank 1, Exception disabled, FPU enabled, IMASK=0xf
bss memory check passed.
CPU mode 16, 32bit address mode, input clock 33MHz
PMB page size 16MB mask=ffffff VPN:a4000000 PPN:4000000
PMB page size 128MB mask=7ffffff VPN:90000000 PPN:58000000
: INTC: 1f1f0000 89008804
udelay_param = 12
thread_stack_setup: 8900a0b0-89009cb0 (400)
thread_stack_setup: 8900a4b0-8900a0b0 (400)
thread_stack_setup: 8900a8b0-8900a4b0 (400)
thread_create: [2]:uart recv pc=890053e0 sp=3d00d stack=0byte
thread_start: [2]
thread_create: [3]:uart send pc=890053b0 sp=3d00d stack=0byte
thread_start: [3]
thread_create: [4]:test pc=89001988 sp=3d00d stack=0byte
thread_start: [4]
Pck 1/24 50MHz
DUck 1/-1 Disabled.
GDTAck 1/8 150MHz
DDRck 1/4 300MHz
Bck 1/12 100MHz
SHck 1/4 300MHz
Uck 1/4 300MHz
Ick 1/2 600MHz
test_thread:1234abcd 136
hello 0
test4>
test4>
test4> help
---Monitor---
W ringbuffer
lock :
event: 3(uart send)
U ringbuffer
lock :
event:
---Ready Queue---
<0>:
<1>:
<2>:
<3>: 1
---Thread Status---
id pri(used/total)
[4] W 1 (0/0) test スタックなし
[3] W 0 (0/0) uart send スタックなし
[2] W 0 (0/0) uart recv スタックなし
[1] R 3 (328/1028) root 専用スタック
avaliable command: help reset vm mmu tlbclear tlbdump cacheflush memtest mem pmb cache timer exception test
test4>
しかし、この見通しの悪さはどうだ。古のUnixではシステムコールでカーネル
に入って、そこからユーザ層に戻るのにsetjmp/longjmpを使っていたと聞いた
ことがある。この継続の実装は、それの最適化もできる。しかしだ。やる事と
してスタックを巻き戻すことを考慮に入れないといけない点で、結局は
setjmp/longjmpにオブラートを被せた程度にしかならない。とはいえ、この手
の最適化が、実行時には有用なのも事実。そしてこの面倒臭さは、割込み形態
のOSは常に明示的に自分の継続先を設定してブロックしないといけないという
のがとても制限になるということだ。
