(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.
前回よーやくH8への移植ができたが,移植の作業内容について説明しよう.
作業した内容として,だいたい以下のようなものがあった.
■ ブートローダー側の対応
■ OS側の対応
まあこんなとこかな.
まずブートローダーの修正だけど,ロードするELFファイルのサイズが格段に増えたので,そのままだとうまくロードできなかったので何箇所か手を入れた.
まずはそもそもバッファのサイズが足りなかったので拡張する修正.
さらに,BSSの展開時に(BSSのサイズがこれがまたでかいため)上記 loadbuf の内容を上書きしてしまうことが判明した.このためloadbuf上にあるELFヘッダを上書きしてしまい誤動作していることが判明.本当はなんかきちんと考える必要があるのだが,とりあえずBSSを展開したらそのまま終了するように修正.
上述のようにBSSの展開でELFヘッダが破壊されてしまうので,エントリポイントを保存しておくように修正している.
次に,割り込みまわり.
まず割り込みハンドラを呼ぶ際に,引数として割り込み番号を渡すように修正.これはOS側で割り込みハンドラを共通化して内部で割り込み番号を見て処理を分けるために利用する.
さらに,従来は割り込み発生時にはそのときのスタックをそのまま利用していたのだけど,スレッドベースでの動作になったときに,スレッドのスタックをそのまま割り込み処理に使うのはなんかイヤ.スレッドから見ると,なんか突然スタックの未使用領域が汚されることになるからね.ということで,割り込み処理用のスタックを用意してそっちに切替えて動作するように修正.
スタックポインタ(ER7)の保存のためにER0を使っている.なのでER0の値は現在使用中のスタックに退避するようになっている.なので厳密にアプリのスタックをまったく使わないということではないのだけど,まあそもそも割り込み発生時にスタックにPCとかCCRとかが退避されてしまうので,アプリのスタックをまったく汚さないというのは不可能なので,まあそのまま引続きアプリのスタック上で動作しなければまあいいかなということで,これでいいとする.
ちなみに上記修正のために,前々回のサンプルプログラムはそのままでは動作しなくなってしまう.これは,前々回まではスレッドベースの動作ではなくアプリは 0xffff00 をスタックとして利用していたが,割り込み処理でも0xffff00 をスタックとして利用するようになったため,スタックポインタが前に戻ってアプリのスタックを上書きしてしまうから.
なのでもしも前々回のアプリを動かすならば,アプリに以下の修正を入れて,アプリ用のスタックと割り込み処理用のスタックを別々にする必要あり.
あとついでにブートローダーのメッセージをなんかそれっぽいものに変える修正.
ブートローダー側の修正は以上.
次にOS側の対応.
まずは Makefile の修正.
KOZOS移植で追加されるソースをコンパイル対象に追加して,あとターゲット名をhelloからkozosに変更.さらに -g がついてるとデバッグ情報が付加されてリンカスクリプトで指定したメモリ上に入りきれなくてリンクエラーになってしまうので,まあどうせ strip でデバッグ情報は捨ててしまうので-gを削除.あとサイズを極力小さくするために,コンパイルオプションに -Os を追加(これがけっこう威力がでかくて,これのおかげでなんとかメモリ上に収まった).
次にKOZOSのソースコードで大きな変更を行った部分やハマッた部分について説明しよう.
まずはthread.cなどで,32ビットが必要な部分(ポインタの値を保存しているとか)でintを使っている部分を uint32 に変更.まあ本来なら -mint32 オプションでコンパイルすることで int を32ビットにできるのだけど,そもそも16ビットCPUなのだからネイティブな状態で動作させたいし,KOZOSの16ビット対応をするいい機会なので,ごっそり書き換えることにした.
ここで1箇所ハマッた部分があって,レディーキューのビットマップを立てる以下の箇所
上記はシフト演算が16ビットで行われてしまうので,優先度が16以上のスレッドが動作できないという問題が発覚(このためidleスレッドが動作できず,動作可能なスレッドの検索に失敗して schedule() 内で停止してしまっていた).以下のように修正した.
スレッドのコンテキストは,レジスタの内容をH8に合わせて thread.h の定義を変更.
あとは割り込み発生時に割り込み処理スタックの先頭にレジスタ情報が退避されるので,そっから情報を吸い上げてスレッドのコンテキストに保存する対応.
あとちなみに INTR_STACK_START の定義だが,configure.h で
のようにしているけど,これも最初は
のように書いてしまっていて,割り込みスタックからレジスタ情報をうまく保存できなくてその後のスレッドのディスパッチに失敗するというバグでハマッた.ああまぬけ.configure.h では,スレッドのスタックの位置やサイズも調整してある.
startup.s にはディスパッチ用の処理を追加.
上述したようにER0の値はスレッドのスタック上に退避されているので,そっちを利用するようにしてある.これも,最初はER0の値を読み出すのを忘れていたためにスタックポインタがER0を指している状態で rte してしまって,おかしなアドレスにジャンプしていたというバグがあった.
あと,serial.c は3本のSCIを選択できるように引数に index を渡せるように修正(これは,流用したPowerPCのKOZOSがそのようになっていたのでそれに合わせた).あと memory.c はもともとは湯水のようにメモリを使っていたのだけど,あんまし無駄にメモリ獲得しないように修正.extintr.c もバッファに無駄に1024バイトとか使っていた部分があったので,修正.
extintr.c はとりあえずSCI1を利用するようにして,あと USE_MESSAGE を未指定にするようにした.これは USE_MESSAGE 有効だと,割り込み発生時にはメッセージ送信だけ行って割り込みフラグを落とさずにスレッドのディスパッチに入ってしまい,ディスパッチ直後にまた割り込みが入ってしまって無限ループになって,そのうちメッセージ用のメモリの枯渇で固まる,というバグがあったから.これもハマッた.(PowerPCはスレッドレベルがゼロのスレッドは割り込み禁止で動作する対応が入っていて,H8にも同じ対応を入れたのだけど,extintr のスレッドレベルをゼロにするのを忘れていて現状では USE_MESSAGE 有効だとうまく動作しない)
kozos.c はSCI1が利用されるように command1 スレッドを起動するように修正.これも,当初はメッセージが出なくてちょっと悩んだところだ.
あと puts() とか putxval() とかのライブラリが main.c にあったのだけど,lib.c に移動した.
あと thread.c の thread_run() で,従来はスタックをてきとうに確保していたのだけど,下方伸長なので thread_stack を増加させてからスタックポインタを設定するように修正.これでスタックひとつぶんのメモリが節約できる.thread_run()は他にもレジスタ周りの設定をH8に合わせて修正.
あとはまあこまごまと,メモリ節約のためにいろんなところを修正してある.
説明のためにソースを見返すと,なんというかちょっとなんだかなあな箇所がいっぱい.ちょっと細かいところをきれいにして,すっきりしたソースコードにしたいところだ.
まあでもサービスルーチンをBIOS側で用意するっつーのは,それはそれで面白いかも.BIOSがどんなことをやっているのかの学習用にはいいかもしれん.
こー考えると,H8って内蔵フラッシュROMが512KBで内蔵RAMが16KBなのでROMのほうがずっとサイズがでかいのだけど,これって基本的にはプログラムはROM上で動かして,RAMはスタックとデータ領域とBSSだけに使うっていう使いかたを想定しているんだろうね.今回のKOZOSのように,ROMはブートローダーだけにして,すべてRAM上で動作させるっつー使いかたをするときは,RAMを増設して使うべきなのかもしれん.
前回よーやくH8への移植ができたが,移植の作業内容について説明しよう.
作業した内容として,だいたい以下のようなものがあった.
■ ブートローダー側の対応
- ELFファイルのサイズが一気に増えたことに対する対応.
- 割り込み時に割り込み用スタックを利用するように修正.
■ OS側の対応
- メモリ調整.
- 割り込みまわりをH8に合わせて修正.
- intが16ビットなので,必要に応じて uint32 にする対応.
まあこんなとこかな.
まずブートローダーの修正だけど,ロードするELFファイルのサイズが格段に増えたので,そのままだとうまくロードできなかったので何箇所か手を入れた.
まずはそもそもバッファのサイズが足りなかったので拡張する修正.
diff -ruN h8_06/kzload/main.c h8_08/kzload/main.c
--- h8_06/kzload/main.c Tue Sep 15 00:26:55 2009
+++ h8_08/kzload/main.c Sun Sep 20 00:33:51 2009
@@ -4,7 +4,7 @@
#include "elf.h"
#include "interrupt.h"
-#define BUFSIZE 4096
+#define BUFSIZE 7400
static unsigned char loadbuf[BUFSIZE];
static int init()
さらに,BSSの展開時に(BSSのサイズがこれがまたでかいため)上記 loadbuf の内容を上書きしてしまうことが判明した.このためloadbuf上にあるELFヘッダを上書きしてしまい誤動作していることが判明.本当はなんかきちんと考える必要があるのだが,とりあえずBSSを展開したらそのまま終了するように修正.
diff -ruN h8_06/kzload/elf.c h8_08/kzload/elf.c
--- h8_06/kzload/elf.c Tue Sep 15 00:26:55 2009
+++ h8_08/kzload/elf.c Sun Sep 20 00:33:51 2009
@@ -72,6 +72,11 @@
phdr->file_size);
} else {
memset((char *)phdr->physical_addr, 0, phdr->memory_size); /* for BSS */
+ /*
+ * BSSがバッファ領域にまたがっている場合,BSSをクリアすることで
+ * バッファが破壊されてしまうので,BSSが見つかったらそのまま終了する.
+ */
+ break;
}
}
@@ -81,12 +86,16 @@
char *elf_load(char *buf)
{
struct elf_header *header = (struct elf_header *)buf;
+ char *entry_point;
if (elf_check(header) < 0)
return NULL;
+ /* BSSの展開でバッファが書きつぶされる場合があるので,保存しておく */
+ entry_point = (char *)header->entry_point;
+
if (elf_load_program(header) < 0)
return NULL;
- return (char *)header->entry_point;
+ return entry_point;
}
上述のようにBSSの展開でELFヘッダが破壊されてしまうので,エントリポイントを保存しておくように修正している.
次に,割り込みまわり.
まず割り込みハンドラを呼ぶ際に,引数として割り込み番号を渡すように修正.これはOS側で割り込みハンドラを共通化して内部で割り込み番号を見て処理を分けるために利用する.
diff -ruN h8_06/kzload/interrupt.c h8_08/kzload/interrupt.c
--- h8_06/kzload/interrupt.c Tue Sep 15 00:26:55 2009
+++ h8_08/kzload/interrupt.c Sun Sep 20 00:33:51 2009
@@ -19,6 +19,6 @@
{
interrupt_handler_t handler = VECTORS[vec];
if (handler)
- handler();
+ handler(vec);
return;
}
diff -ruN h8_06/kzload/interrupt.h h8_08/kzload/interrupt.h
--- h8_06/kzload/interrupt.h Tue Sep 15 00:26:55 2009
+++ h8_08/kzload/interrupt.h Sun Sep 20 00:33:51 2009
@@ -3,7 +3,7 @@
#define VECTOR_NUM 64
#define VECTOR_ADDR ((void *)0xffbf20)
-typedef void (*interrupt_handler_t)(void);
+typedef void (*interrupt_handler_t)(int vec);
#define VECTORS ((interrupt_handler_t *)VECTOR_ADDR)
さらに,従来は割り込み発生時にはそのときのスタックをそのまま利用していたのだけど,スレッドベースでの動作になったときに,スレッドのスタックをそのまま割り込み処理に使うのはなんかイヤ.スレッドから見ると,なんか突然スタックの未使用領域が汚されることになるからね.ということで,割り込み処理用のスタックを用意してそっちに切替えて動作するように修正.
diff -ruN h8_06/kzload/intr.S h8_08/kzload/intr.S
--- h8_06/kzload/intr.S Tue Sep 15 00:26:55 2009
+++ h8_08/kzload/intr.S Sun Sep 20 00:33:51 2009
@@ -5,6 +5,9 @@
.type _intr01,@function
_intr01:
mov.l er0,@-er7
+ mov.l er7,er0
+ mov.l #_stack,sp
+ mov.l er0,@-er7
mov.l er1,@-er7
mov.l er2,@-er7
mov.l er3,@-er7
@@ -20,12 +23,17 @@
mov.l @er7+,er2
mov.l @er7+,er1
mov.l @er7+,er0
+ mov.l er0,er7
+ mov.l @er7+,er0
rte
スタックポインタ(ER7)の保存のためにER0を使っている.なのでER0の値は現在使用中のスタックに退避するようになっている.なので厳密にアプリのスタックをまったく使わないということではないのだけど,まあそもそも割り込み発生時にスタックにPCとかCCRとかが退避されてしまうので,アプリのスタックをまったく汚さないというのは不可能なので,まあそのまま引続きアプリのスタック上で動作しなければまあいいかなということで,これでいいとする.
ちなみに上記修正のために,前々回のサンプルプログラムはそのままでは動作しなくなってしまう.これは,前々回まではスレッドベースの動作ではなくアプリは 0xffff00 をスタックとして利用していたが,割り込み処理でも0xffff00 をスタックとして利用するようになったため,スタックポインタが前に戻ってアプリのスタックを上書きしてしまうから.
なのでもしも前々回のアプリを動かすならば,アプリに以下の修正を入れて,アプリ用のスタックと割り込み処理用のスタックを別々にする必要あり.
diff -ruN serial/ld.scr serial2/ld.scr
--- serial/ld.scr Sat Sep 19 16:58:54 2009
+++ serial2/ld.scr Sat Sep 19 19:56:47 2009
@@ -9,7 +9,8 @@
* and 256bytes space for ELF header.
*/
ram(rwx) : o = 0xffbf20 + 0x200, l = 0x004000 - 0x200 /* 16kb */
- stack(rw) : o = 0xffff00, l = 0x000010 /* end of RAM */
+ bootstack(rw) : o = 0xfffd00, l = 0x000010 /* end of RAM */
+ intrstack(rw) : o = 0xffff00, l = 0x000010 /* end of RAM */
}
SECTIONS
@@ -42,7 +43,11 @@
_end = . ;
- .stack : {
- _stack = .;
- } > stack
+ .bootstack : {
+ _bootstack = .;
+ } > bootstack
+
+ .intrstack : {
+ _intrstack = .;
+ } > intrstack
}
diff -ruN serial/startup.s serial2/startup.s
--- serial/startup.s Sat Sep 19 16:58:54 2009
+++ serial2/startup.s Sat Sep 19 19:57:14 2009
@@ -3,7 +3,7 @@
.global _start
.type _start,@function
_start:
- mov.l #_stack,sp
+ mov.l #_bootstack,sp
jsr @_main
_loop:
あとついでにブートローダーのメッセージをなんかそれっぽいものに変える修正.
@@ -121,10 +121,10 @@
init();
- puts("Hello World!\n");
+ puts("kzboot (kozos boot loader) started.\n");
while (1) {
- puts("> ");
+ puts("kzboot> ");
gets(buf);
if (!strcmp(buf, "load")) {
ブートローダー側の修正は以上.
次にOS側の対応.
まずは Makefile の修正.
--- h8_07/os/Makefile Sat Sep 19 17:24:41 2009
+++ h8_08/os/Makefile Sun Sep 20 00:34:04 2009
@@ -1,15 +1,24 @@
OBJS += startup.o main.o interrupt.o
-OBJS += lib.o serial.o timer.o
+OBJS += lib.o serial.o
+#OBJS += timer.o
+
+# sources of kozos
+OBJS += thread.o syscall.o memory.o idle.o
+OBJS += extintr.o
+OBJS += kozos.o command.o
+
#LIBOBJS +=
#LIB = libxxx.a
-TARGET ?= hello
+TARGET ?= kozos
#CC ?= gcc
CFLAGS =
CFLAGS += -Wall -mh -nostdinc -nostdlib -fno-builtin
#CFLAGS += -mint32 # intを32ビットにすると掛算/割算ができなくなる
CFLAGS += -I.
-CFLAGS += -g
+#CFLAGS += -g
#CFLAGS += -O
+CFLAGS += -Os
+CFLAGS += -DKOZOS
LFLAGS += -static -T ld.scr -L.
KOZOS移植で追加されるソースをコンパイル対象に追加して,あとターゲット名をhelloからkozosに変更.さらに -g がついてるとデバッグ情報が付加されてリンカスクリプトで指定したメモリ上に入りきれなくてリンクエラーになってしまうので,まあどうせ strip でデバッグ情報は捨ててしまうので-gを削除.あとサイズを極力小さくするために,コンパイルオプションに -Os を追加(これがけっこう威力がでかくて,これのおかげでなんとかメモリ上に収まった).
次にKOZOSのソースコードで大きな変更を行った部分やハマッた部分について説明しよう.
まずはthread.cなどで,32ビットが必要な部分(ポインタの値を保存しているとか)でintを使っている部分を uint32 に変更.まあ本来なら -mint32 オプションでコンパイルすることで int を32ビットにできるのだけど,そもそも16ビットCPUなのだからネイティブな状態で動作させたいし,KOZOSの16ビット対応をするいい機会なので,ごっそり書き換えることにした.
ここで1箇所ハマッた部分があって,レディーキューのビットマップを立てる以下の箇所
readyque_bitmap |= (1 << current->pri);
上記はシフト演算が16ビットで行われてしまうので,優先度が16以上のスレッドが動作できないという問題が発覚(このためidleスレッドが動作できず,動作可能なスレッドの検索に失敗して schedule() 内で停止してしまっていた).以下のように修正した.
readyque_bitmap |= ((uint32)1 << current->pri);
スレッドのコンテキストは,レジスタの内容をH8に合わせて thread.h の定義を変更.
typedef struct _kz_context {
uint32 er[8];
uint32 pc;
} kz_context;
あとは割り込み発生時に割り込み処理スタックの先頭にレジスタ情報が退避されるので,そっから情報を吸い上げてスレッドのコンテキストに保存する対応.
void thread_intr(int vec)
{
...
p = (uint32 *)INTR_STACK_START;
current->context.er[7] = *(--p);
current->context.er[1] = *(--p);
current->context.er[2] = *(--p);
current->context.er[3] = *(--p);
current->context.er[4] = *(--p);
current->context.er[5] = *(--p);
current->context.er[6] = *(--p);
current->context.er[0] = *(uint32 *)(current->context.er[7]);
current->context.pc = *(uint32 *)(current->context.er[7] + 4);
...
あとちなみに INTR_STACK_START の定義だが,configure.h で
extern uint32 intrstack;
#define INTR_STACK_START (&intrstack)
のようにしているけど,これも最初は
extern uint32 intrstack;
#define INTR_STACK_START intrstack
のように書いてしまっていて,割り込みスタックからレジスタ情報をうまく保存できなくてその後のスレッドのディスパッチに失敗するというバグでハマッた.ああまぬけ.configure.h では,スレッドのスタックの位置やサイズも調整してある.
startup.s にはディスパッチ用の処理を追加.
.global _dispatch
.type _dispatch,@function
_dispatch:
mov.l er0,er7
mov.l @er7+,er0
mov.l @er7+,er1
mov.l @er7+,er2
mov.l @er7+,er3
mov.l @er7+,er4
mov.l @er7+,er5
mov.l @er7+,er6
mov.l @er7+,er0
mov.l er0,er7
mov.l @er7+,er0
rte
上述したようにER0の値はスレッドのスタック上に退避されているので,そっちを利用するようにしてある.これも,最初はER0の値を読み出すのを忘れていたためにスタックポインタがER0を指している状態で rte してしまって,おかしなアドレスにジャンプしていたというバグがあった.
あと,serial.c は3本のSCIを選択できるように引数に index を渡せるように修正(これは,流用したPowerPCのKOZOSがそのようになっていたのでそれに合わせた).あと memory.c はもともとは湯水のようにメモリを使っていたのだけど,あんまし無駄にメモリ獲得しないように修正.extintr.c もバッファに無駄に1024バイトとか使っていた部分があったので,修正.
extintr.c はとりあえずSCI1を利用するようにして,あと USE_MESSAGE を未指定にするようにした.これは USE_MESSAGE 有効だと,割り込み発生時にはメッセージ送信だけ行って割り込みフラグを落とさずにスレッドのディスパッチに入ってしまい,ディスパッチ直後にまた割り込みが入ってしまって無限ループになって,そのうちメッセージ用のメモリの枯渇で固まる,というバグがあったから.これもハマッた.(PowerPCはスレッドレベルがゼロのスレッドは割り込み禁止で動作する対応が入っていて,H8にも同じ対応を入れたのだけど,extintr のスレッドレベルをゼロにするのを忘れていて現状では USE_MESSAGE 有効だとうまく動作しない)
kozos.c はSCI1が利用されるように command1 スレッドを起動するように修正.これも,当初はメッセージが出なくてちょっと悩んだところだ.
あと puts() とか putxval() とかのライブラリが main.c にあったのだけど,lib.c に移動した.
あと thread.c の thread_run() で,従来はスタックをてきとうに確保していたのだけど,下方伸長なので thread_stack を増加させてからスタックポインタを設定するように修正.これでスタックひとつぶんのメモリが節約できる.thread_run()は他にもレジスタ周りの設定をH8に合わせて修正.
あとはまあこまごまと,メモリ節約のためにいろんなところを修正してある.
説明のためにソースを見返すと,なんというかちょっとなんだかなあな箇所がいっぱい.ちょっと細かいところをきれいにして,すっきりしたソースコードにしたいところだ.
- モトローラSフォーマットにして,メモリ上に直接ロードするようにしてロード用のバッファを不要にする.
- schedule()の処理をすっきりさせる.
- スタックサイズをスレッドごとに指定可能にする.
- extintr.c のUSE_MESSAGE対応.
- 全体的に見直して,不要な部分を削除してすっきりさせる.
まあでもサービスルーチンをBIOS側で用意するっつーのは,それはそれで面白いかも.BIOSがどんなことをやっているのかの学習用にはいいかもしれん.
こー考えると,H8って内蔵フラッシュROMが512KBで内蔵RAMが16KBなのでROMのほうがずっとサイズがでかいのだけど,これって基本的にはプログラムはROM上で動かして,RAMはスタックとデータ領域とBSSだけに使うっていう使いかたを想定しているんだろうね.今回のKOZOSのように,ROMはブートローダーだけにして,すべてRAM上で動作させるっつー使いかたをするときは,RAMを増設して使うべきなのかもしれん.