Tag Archives: OKL4

KTESTはパスするようになったのでソースコードを公開します。動かすためにはSH2007か同等のコンピュータが必要になります。

OKL4 3.0 for SH7780

2009-04-12

  • L4_Mutexが失敗する。

2009-04-11

  • 割り込みを有功にした。
    • SH7780では割り込みのマスクを解除するだけでなく、割り込みの優先度を2以上にしなければならなかった。
  • Pckは50MHz。ティックは10ms。Pck/4のタイマーで10msを測るには?
  • TMUの割り込み要因がクリアされていない。
    • TMU_TCRのUNFをクリアしなければならない。
  • キャッシュをフラッシュする区間を割り込み禁止にしていたのだが、これはその区間外が割り込み許可であることが前提。ところが、実際には、まわりの区間は 割り込み許可である。キャッシュをフラッシュした後も割り込み禁止でなければならないのだが、割り込みを許可してしまっていた。
  • カーネルモードでの割り込みの処理に間違いがあった。
  • 以上、Rev.125
  • 結果、KTESTが最後まで走るようになった!

2009-04-10

  • CACHE0600で落ちる。
    • キャッシュをフラッシュする際にアドレスが有効かどうか調べる機能をONにする。
  • CACHE0600の状況を修正する過程でページフォルトが処理されない問題が発生。
    • UTCB参照アドレスのキャッシュが更新されていない。
      • (ワークアラウンド) UTCB参照アドレスのUTLBエントリをライトスルーにすることでなんとなく動いているが…Rev.120
      • 大量に出ていたinvalid page faultも嘘のように消えてしまった。
      • UTCB参照アドレスをキャッシング不可にした。
  • TODO UTCB参照ページをキャッシュするかどうかについては、調査が必要。
  • L4_NotifyがエラーでかえってくるIPC3400
    • システムコールを修正。Rev.121
  • KMEM02でUTCBの上限値を設定。Rev.122
  • L4_Yieldでブロックする。
    • どうやらタイマーが有功になっていないみたいだ。

2009-04-09

  • IPCがうまく行っていない。
    • スレッド1がスレッド2を生成し、スレッド2にBlock Sendする。スレッド2はまだ走っていないので、このBlock Sendは一旦スケジューリングキューに登録される。スレッド2はページャと通信し、メモリページを取得する。スレッド2はスレッド1からのメッセージを 受信する。
    • 最後のスレッド2がスレッド1からのメッセージを受信するところが、受信されていない。
    • スレッド1からスレッド2へのIPCはコンティニュエーションを使って復帰される。(restart_ipc)
    • restart_ipcはSYS_IPC_RESTART(アーキテクチャ依存コード)を呼び出す。SYS_IPC_RESTARTはcall_with_asm_continuation(アーキテクチャ依存コード)を呼び出す。
    • call_with_asm_continuationが正しく状態を復帰していなかった。
      • この関数を実装した当初は、continuationを呼び出すものだと思っていたので、continuationにジャンプしていたが、これは間違い。
      • 正しくは、continuationをPRレジスタに格納し、functionを呼び出さなければならない。Rev.116
  • 新しくアドレス空間を生成した時にkmem_resourceが確保されていないようだ。
    • kmem_resourceはどのタイミングで確保されるのか?
    • この場合には、そもそもkmem_resourceを使ってはいけない。kmem_resourceはUTCB参照ページを参照するページテーブルエント リの確保に使われている。つまり、このページテーブルエントリを作成するのが間違いということになる。幸いなことに、UTCB参照ページを共有ページにす ればページテーブルエントリの確保は必要なくなる。ただし、以下の条件を満たす必要がある。
      • UTCB参照ページはTLBから追い出されない。
      • UTCB参照ページのエントリはTLBの中で最大一つ。
    • 直した。Rev.119
  • キャッシュテストで落ちる。

2009-04-08

  • ページフォルト(TLB例外)後、ページフォルトハンドラが例外アドレスをマッピングする。この操作はアドレスをページテーブルに登録するだけで、TLB に登録するわけではない。したがって、ページフォルトを発生したスレッドに一旦処理は戻り、再び、TLB例外を発生させ、TLBをフィルするという流れに なっていた。
    • ページテーブル登録後にTLBをフィルするように変更。Rev.113
  • (ワークアラウンド) ipc_handler内でTLBにないUTCBアドレスにアクセスする可能性がある。UTCBアドレスはP0領域の仮想アドレスなのでTLB例外を発生 させる。現在の実装では、ipc_handlerは割り込み禁止区間に設定されている。そのため、TLBにないUTCBアドレスにアクセスすると処理が進 まなくなり、クラッシュする。
    • (方法1)割り込み禁止区間を撤廃する
      • TLB例外以外の優先度の高い例外や割り込みが発生する可能性がある。
        • データTLB例外の優先順位は6番め。
    • (方法2)物理アドレスでUTCBにアクセスする。
      • TCB経由でUTCBの物理アドレスを得られる。
    • とりあえず方法1を試す。動作はするが、最終的にはエラーが出てKDBに落ちる。
      • kernel access raised user page fault (space.cc l212)
    • ここから得られる結論は、そもそもカーネルモードでUTCBに対するTLB例外を発生させてはならないということである。
    • 方法2でうまくいった。

2009-04-07

  • ページテーブルエントリが思ったように更新されていない。
    • ページテーブルエントリのバグ(pgent_t::clear)
  • get_current_kmem_resourceが失敗する。アサーションに引っかかる line 149 @ kmem_resource.cc。
    • generic_space_t::activate内でUTCB参照ページのマッピングを常に新しく作成していたため、メモリリークしていた。
  • ipc_syscall_return内でUTCBにアクセスするとき、ページフォルトが発生する場合がある。この場合に対処していなかったため、クラッシュしていた。

2009-04-06

  • (ワークアラウンド) データキャッシュにもTLBにも存在しないエントリをライトバック(OCBP)しようとして、クラッシュしている。Rev.108
    • ページテーブルに登録されているが、TLBに登録されていない場合には次の二つが考えられる。
      1. マッピングしたがそのアドレスにアクセスしていない。
      2. マッピング後アクセスし一旦はTLBに登録されたが、追い出された。
    • 2.の場合はデータはキャッシュされるのでOCBP命令は成功する。

2009-04-02

  • ktestがアボートされる。
    • irqのリストが取れないのが原因。ElfWeaverがNO_DEVICE_IRQ_LISTという構造体をアロケーションするのだが、どのような仕組みでアロケーションするのかよくわからない。
    • とりあえずアサーションを無視。
  • ThreadControlでアサーションに引っかかる。
    • システムコールの引数の処理の間違いだった。
  • IPCでデッドロック
    • IPCの受信時に送信元を正しく返していなかった。Rev.101
  • KDBのスタック管理にバグがあった。修正。Rev.99
  • KDB呼び出しのレジスタ管理にバグがあった。修正。Rev.100
  • IPCのメッセージが配送されない。
    • KDB呼び出しがレジスタを破壊していた。Rev.105

2009-04-01

  • IPCがおかしい。ブロックしない。
    • UTCBをカーネルが読み取れていない。L4_WaitはフラグをセットしL4_Ipcを呼び出す。このフラグがセットされることでIPCはブロックする 仕組みになっている。このフラグはMsgTagにセットされ、UTCBに保存される。カーネルはUTCBからこのフラグを読み出す。カーネルがブロックし ないということは、このフラグを正しく読み出せていないことを意味する。
    • IPCの実装が未完成だった。Rev.93
  • UTCBでTLB多重ヒット例外が出る。
    • generic_space_t::activate内
    • UTCBを参照するためのページは共有ページに設定されていたのだが、この設定ではASIDが比較されない。結果としてTLB多重ヒット例外を発生させていた。Rev.95
    • メモ:UTCB参照ページは共有にすることでUTLBを節約できる。

2009-03-31

  • ページテーブルエントリをフリーするときにフリーリスト(kmem_free_list)のアドレスが変(0×80000000)
    • kmem_free_listはkmem_tのメンバー。
    • kmem_tはkmem_resource_tのメンバーになる(heap)。
    • kmem_free_listは0番目のメンバーで、kmem_resource_tの中のkmem_tは0番目のメンバーなので、アドレス0×80000000はkmem_resource_tのアドレスになる。
    • つまり、kmem_resource_tの配置がおかしいことになる。
    • kmem_resource_tはget_current_kmem_resource()で取得する。
  • free_utcb_area_memory()がフリーリストを書き換えてしまう。
  • utcbのアドレスに対応するpgentの持つ物理アドレス(utcbの物理アドレス)が0になっている。これが原因でfree_utcb_area_memoryがおかしな挙動をする。原因はUTCBのアロケーションの失敗か?
  • 結局のところ、ページテーブルエントリの有効・無効の判断の仕方に間違いがあったということが分かった。疑わしきは自分の書いたコードである。Rev.92

2009-03-30

  • SpaceControlがちゃんと動かない。

2009-03-27

  • トラップハンドラの見直し終わり。ユーザーコンテキストをTCBに保存するようにした。Rev.83
    • KDBが使えるようになった。
  • Hello, world! Rev.85
  • KTESTを走らせてみる。SPACE_CONTROLでこける。コンティニュエーションのアドレスがおかしい。

2009-03-26

  • トラップハンドラを見直し中。コンテキストの退避と復帰、再突入の考慮など。
  • メモ
    • ユーザーモードからカーネルモードに入る(システムコール、例外、割り込み)
      • ユーザーコンテキストをユーザースタックに退避
      • カーネルスタックに切り替え
      • システムコール、例外、割り込みの処理
      • カーネルの特定の場所でプリエンプション可能になる
      • 例外、割り込みの発生
      • カーネルコンテキストは破棄する
      • カーネルスタックをリセット
      • 例外、割り込みの処理
      • 終了後、コンティニュエーションを実行。TCBからレジスタを復帰。スタックはリセット。
      • 終了後、コンティニュエーションを実行。TCBからレジスタを復帰

2009-03-25

  • ユーザープログラムがこけるのも当たり前、ルートタスク用のcrt0がちゃんと実装されていなかった。
  • おそらくHelloWorldは実行されているが、メッセージが表示されない。シリアルドライバが不完全か?
    • KDBコール(システムコールの一種)がおかしいので、シリアルドライバをデバッグできない。まずはKDBコールを直さなければ。
      • 一般例外ハンドラの配置が違ってたので修正。
      • KDBコールは正しいようだ。
      • 部分的に未実装なので、実装を完璧にする必要がある。
  • read_write_lockのアサーションに引っかかる。一時的なエラーだったみたい。
  • ユーザーレベルのシリアルデバイスドライバがおかしい。

2009-03-24

  • EXPEVTが0の問題は、とりあえず、0を読み込み例外(本来は0×40)と判断することにした。
  • ElfWeaverのスタックポインタの計算が間違っている?
DEFAULT_STACK_SIZE = 0x1000

def get_sp():
    virt_addr = self.stack.virt_addr
    size = self.stack.size
    return virt_addr + size

となっているけど、これではスタックポインタが次のページに行ってしまう。

def get_sp():
    virt_addr = self.stack.virt_addr
    size = self.stack.size - 4
    return virt_addr + size

とすべきだろう。

  • pgent_t::is_readableの判定が間違っていたので修正。
  • 正体不明のページフォルト(0×00117550)が発生する。
    • 正体不明ではなく、期待通りのページフォルト。しかし、カーネルが正しく処理できていない。
    • make_subtreeで64kページを考慮していないのが原因だった。修正。
  • ユーザーコンテキストが元に戻らない。例外発生前のコンテキストが例外処理後に復元されない。
    • tcb_tの中のktcb_tにユーザーコンテキストを保存。回復にはexception_returnを新たに実装。
    • 8ビットイミディエイトの加算演算にはまる。結果が8ビットを越えてもキャリーしない。つまり、
      • 0×88301C60 + 0xA4 = 0×88301C04となる。
      • 上記の式の正しい答えは0×88301D04
    • この様な場合にはイミディエイトではなくレジスタに一旦値をロードして計算する。
  • ユーザープログラムがアサートでこける。OKL4_ENV_HEAER_MAGICが取れていない。

2009-03-23

  • TCBは空っぽではなかった。しかし、PCが指すアドレス(0×100000)には何も配置されていない。
      HelloWorldが期待通りにマッピングされていないのが原因のようだ。map_regionを見直す。
  • TLBミスハンドラがオフセット0×400にない!
  • クラッシュの原因はTLBミスハンドラにあるようだ。
    • TLBミスハンドラの冒頭でユーザーコンテキストをユーザースタックに退避する。しかしながら、ユーザースタックはTLBにエントリがない。これが原因でダブルフォルトが発生し、カーネルがリセットされる。
    • generic_space_t::activate内でユーザースタックをTLBエントリに追加した。
  • TLBミスハンドラで例外コードがとれない。EXPEVTを読み込むと値が0。

2009-03-19

  • ページマッピングがうまくいかない。カーネルモードでP0領域にアクセスしようとしてこける。UTLBはちゃんとセットしているんだが。。。
    • 読み込みは成功しているようだが、書き込みはダメだ。
    • ライトバックのページを複数箇所にマッピングする時はキャッシュをフラッシュする必要がある。
    • 下記のページテーブルの構成に付随した問題だった。ページサイズを直接指定することで取り合えず解決。
  • ページテーブルの構成はTODO
    • add_mappingで指定したページサイズとlookup_mappingで取れるページサイズが違う。ページテーブルの構成がコアの実装と合っていないようだ。
    • 直した。
  • initial_to_userでTCBが空っぽ。なのでリセットされてしまう。TCBをどこかでセットアップしているはずなんだが…

2009-03-18

  • ページマッピングでパニック。ページサイズが0になっている。アドレスが0×117400となっており、1kアラインである。これはおかしい。設定ファイル(machines.py)で1kページを許可しないようにしたら直った。本質的な解決ではないかもしれない。
  • パニックするようなエラーはでなくなった。ちゃんと動いている?HelloWorldが使うシリアルポートドライバがまだ出来ていないので確認できない。
  • シリアルポートデバイスドライバを実装。でもメッセージは出ない。どこかでハングしているようだ。
    • それもそのはず。switch_toが未実装でコンテキストスイッチが起きていなかった。
  • switch_to実装も新たなバグ。UTCBの実装を見直さなければならない。

2009-03-17

  • キャッシュのコードを修正。オペランドキャッシュのフラッシュのためにVIRT_RAM_BASE(0×88210000)を使っていたのだが、実はこの アドレスにはグローバル変数が格納されおり、このグローバル変数の変更がライトバックされていない状態でキャッシュをフラッシュしようとすると、今のアル ゴリズムではグローバル変数が破壊されてしまう。VIRT_RAM_BASEを止めて、KERNEL_AREA_START(0×80000000)に変 更。
    • 0×88000000とすべきか?
  • トレースを表示するように変更。コマンドラインで指定する方法を探すのに手間取った。カーネルソースでは、VERBOSE_INITというディレクティブ でトレースの表示をコントロールしている。これを単にコマンドラインでVERBOSE_INIT=trueとすれば、トレースの表示が有効になる。他の ディレクティブも同様と思われる。
  • small_alloc_t::allocateのメモリテストでエラー。check_poisoned_areaで引っかかる。メモリが汚染されているらしい。
    • 他の誰かが使っている
    • オーバーフロー
    • キャッシュ操作の間違い
void *
small_alloc_t::allocate(bool zeroed)
{
    ...
    if (!first_free) {
        /* ここでは4Kのブロックを確保 */
        first_free = allocate_block(mem_group);
        if (!first_free) {
            return NULL;
        }
        /* 確保したブロックを汚染しておく */
        poison_area(get_object(first_free, 0), objs_per_block * obj_size, KMEM_POISON_FREE);
    }

    /* ファーストフィットでフリーブロックを確保 */
    int position = bitmap_findfirstset(first_free->get_bitmap(), objs_per_block);
    void* object = get_object(first_free, position);
    bitmap_clear(first_free->get_bitmap(), position);

    /* 現在のブロックがすべて使われているならば、first_freeをアップデート */
    while (bitmap_findfirstset(first_free->get_bitmap(), objs_per_block) == -1) {
        first_free = first_free->next;
        if (!first_free) {
            break;
        }
    }
    num++;

    check_poisoned_area(object, obj_size, KMEM_POISON_FREE, object);
    ...
}
void
poison_area(void* addr, word_t size, unsigned char poison_value)
{
    memset(addr, poison_value, size);
}
void
check_poisoned_area(void* addr, word_t size, unsigned char poison_value, void* obj_addr)
{
    unsigned char* p = (unsigned char*)addr;
    while (size--) {
        if (*p != poison_value) {
            enter_kdebug("poison");
        }
        ++p;
    }
}
  • 原因はmsbを実装していないことにあった。
  • l1_entry_tとl2_entry_tのサイズがおかしくなっていたせいで、メモリアロケーションがおかしなことになっていた。
    • l1_entry_tとl2_entry_tは4バイトでなければならないのだが、ビット指定を間違えていて33ビットのデータ構造になってしまっていた。

2009-03-16:カーネルが動き始める

  • MMUCRなどを更新した際に、任意のアドレスでICBIを発行するのだが、その任意のアドレスを0×1000とするとクラッシュする。
    • 0×80000000として回避した。
  • 最初のカーネルメッセージが表示された!
  OKL4 - (provider: Open Kernel Labs) built on Mar 16 2009 14:50:15 using gcc version 3.4.5.
  • run_init_scriptでこける
    • phys_to_virtのバグ。Machine.pyを修正。
    • Elf Weaverが物理アドレスでinit scriptを書き込んでいる一方で、カーネルは仮想アドレスでそのスクリプトをロードする。

2009-03-13

  • いよいよ実機で走らせつつ、デバッグを開始する。
    • まずはデバッガ用のウィンドウズのインストールから。

2009-03-12:ビルドが通るようになる

  • ldスクリプトのしょうもないバグを修正。ビルドが大分進む用になった。
  • crt0_rootserver.sppを実装したら、ビルドが通った

2009-03-11

  • とりあえずカーネル内の空の関数は実装した。
  • ライブラリの実装に取りかかる。
    • KDBインターフェースのテンプレートを作成
    • 実装完了か?
  • シリアルデバイスドライバがないのでビルドが停止する。

2009-03-10

  • トラップハンドラ実装の続き。
  • 例外ハンドラの実装。
  • KDBインターフェースを実装しなければならない。lib/l4/include/syscall_asm.hにKDB用のシステムコール番号だけは予約した。

2009-03-09

  • トラップハンドラを大幅に変更。例外コードをオフセットにして各ベクタに飛ぶようにした。
  • コンティニュエーションの仕組みと役割を理解した。

2009-03-06

  • レジスタのネーミングを仕様書どおりにする。いままでは可読性を高めようと独自に名前を変更していたが、かえって仕様書との読み替えが面倒になるのではないかと考えた。その代わり、コメントで可読性を補うことにする。

2009-03-05

  • 割り込みがいやらしい。L4ではSHでいうところの外部割り込みしか想定していないようだ。内部割り込みもユーザ側に投げる必要があると思うので、その場 合、内部・外部割り込みを一つの空間で管理しなければならないが、各割り込みは必ずしも連番になっていない。一方でL4内部では配列で割り込みの管理をし ている。割り込みの番号空間を連番空間にマッピングする関数が必要なのだが。。。
  • 結局、虫食いになってもいいから、大きめの配列を用意することにした。将来的に今穴の開いている割り込み番号に何かが加わるかもしれないし。

割り込みコントローラ

SH7780では割り込みの受付時にINTEVTレジスタ(メモリ割り付けレジスタ)を読むことでどの要因で割り込みが発生したのか特定できるようになっている。割り込みコントローラ(ハードウェア)は、タイマーやDMACなどから割り込みの要求が入るとINTEVTにその要求に割り当てられた数字を書き込む。割り込みハンドラ(プログラム)はINTEVTレジスタを読み込み、スケジューリングやIRQの転送などの処理へ分岐していく。

割り込みの要因は四種類に大別できる。NMI、IRQ、IRL、内蔵モジュールである。NMIはNon Maskable Interruptで、マスクできない、つまり、プロセッサがどのような状態にあっても入ってくる割り込みである。IRQはInterrupt Requestで、一般に周辺機器を制御するための割り込みである。SH7780には8チャネルのIRQ端子があり、それぞれ独立して割り込みの状態を設定できる。IRLはIRQと同じように周辺機器を制御するための割り込みなのだが、割り込みをチャネルではなくレベル(あるいはチャネルの状態のパターン)で分類する。IRLを使うと効果的な場面はよくわからない。内蔵モジュールの割り込みは、タイマー、DMAC、シリアルインタフェースなどからの割り込みである。

割り込みには優先順位をつけることが出来る。レベル16が最高で、レベル1が最低となっている。レベル0では割り込みが受け付けられない。割り込みの優先順位は同時刻に発生した割り込みを開発者の思うとおりに制御するために必要である。たとえば、タイマー割り込みの優先度が高ければ、スケジューリングの遅延を避けることが出来、結果として、実時間性を向上させることが出来る。

割り込み要因の考慮

先述のように割り込みの要因は四つある。NMIはカーネルで処理する。したがって、ユーザー空間に転送しなければならない割り込みは、大別すると外部割り込み(IRQもしくはIRL)と内部割り込み(内蔵モジュール)の二種類になる。L4のAPIでは割り込みは一つの空間にまとめられている。IA32やARMでは割り込みの空間は一種類なので良かったが、二種類の割り込みの空間があるSH4Aではカーネルはどのように処理したらよいのだろうか。

カーネル内部ではIRQを管理する配列がある。IRQそれぞれに対し、一要素を割り当てる。SH4Aでは12ビットの例外コードによって、外部割り込み、内部割り込みに関わらず、要因を区別する。これらの割り込みは合計すると68種類にもなる。例外コードの内、下位5ビットは使われていない。つまり、ゼロだ。下位5ビットを算術シフト演算で取り除き、オフセット値を引けば、0からの通し番号に変換できる。しかし、例外コードをよく見てみると、所々番号が飛んでいるところがある。なんともいやらしいなぁ。

外部割り込みだけなら話は簡単だ。しかし、デバイスドライバの処理に必要なDMACやシリアルポートの割り込みは内部割り込みだ。

とりあえずの解法として、飛び石の番号を無視して配列を確保してしまうことにした。例外コードは0×200から始まり0xFE0までなので、0×20毎に例外コードがあるとすれば112要素必要になる。実際には例外コードは68種類しかないので54要素分は不要になる。各要素は12バイトのデータ構造なので、648バイトは無駄になる。

割り込みコントローラの設定

  • 端子モードはIRQを使用する(IRLモードは使用しない。効果的な使い方を知らないので。)
  • IRQ0からIRQ7はオフ(ユーザプロセスからの要求に従ってオン)
  • TMU0はオン

SH4Aは二種類のタイマーを提供する。TMUは独立したチャンネルが六つあり、割り込みも独立して入る。CMTにもチャンネルはあるが、割り込みは共通している。特に理由はなくTMU0をスレッドディスパッチ用タイマーとして選択する。

カーネル内の割り込みハンドラ

例外処理ルーチンの先頭アドレスは各割り込みで共通である。なので、割り込み要因を識別するためにINTEVTをオフセットとして分岐する。

  • IRQをIPCに変換する (IRQ0からIRQ7)
  • スケジューリングする (TMU)

カーネル割り込みハンドラの中でやらなければいけないこと

1.IRQを配送する

IRQはユーザプロセスで処理されるので、そのためにカーネルは受け取った割り込み要求をユーザプロセスに配送しなければならない。例:simplesoc_handle_irq_rescheduleを呼び出す。(rescheduleの意味は何?)

2.スケジューリングする

handle_timer_interruptを呼び出す。

カーネル割り込みハンドラの中でやらないこと

シリアルインタフェースの割り込み処理など、その他の割り込み。

割り込みAPI

割り込みハンドラの登録

割り込み処理

設計

GCCの呼び出し規約を参考にしてシステムコールの呼び出し規約を定義する。

GCC(ルネサス?)の呼び出し規約

R0:返却値
R1からR3:空
R4からR7:引数
R8からR13:呼び出し先が退避する
R14:フレームポインタ、呼び出し先が退避する
R15:スタックポインタ
PR:サブルーチンの戻りアドレス

参考1:gcc/config/sh/sh.h
参考2:http://www.ertl.jp/~takayuki/readings/info/no04.html

呼び出し規約

第一引数:R4
第二引数:R5
第三引数:R6
第四引数:R7
第五引数:R1
第六引数:R2
第七引数:R3
第八引数:R9
システムコール番号:R8
返却値:R0

ユーザプログラムはR8にシステムコール番号を格納し、TRAPA命令を発行する。システムコールの引数は指定のレジスタを介して渡される。システムコールは8個までの引数を取ることが出来る。

TRAPAはイミディエイトオペランドを持つので、このオペランドを使ってシステムコール番号を指定することも可能だ。

TRAPA

PC+2、SR、R15の値をSPC、SSR、SGRに退避し、イミディエートデータをTRAレジスタに格納する。処理モードを特権モードに切り替え、BANK1レジスタを選択し、例外コード0×160をEXPEVTレジスタに書き込む。処理は例外ハンドラに分岐する。

システムコールの流れ

ユーザー側の処理(呼び出し前)
・前提:以下の処理は関数の先頭で行う。(R4からR7以外は自由に使える状態であること)
・スタックから第五引数から第八引数をR0からR3に格納する。
・R8にシステムコール番号を格納する
・TRAPAを発行する。
・R10からR14は使わない

カーネル側の処理
・TCBにシステムレジスタを退避する
・カーネルスタックに切り替える
・バンク1からバンク0に切り替える(ユーザーのレジスタに切り替える)
・R0からR3をスタックに格納する
・コンティニュエーションをPRに格納
・システムコールを呼び出す

コンティニュエーション
・TCBからシステムレジスタを復帰する
・ユーザースタックに切り替える
・RTEを発行する

ユーザー側の処理(呼び出し後)
・呼び出し元に戻る。

コンティニュエーションの利用

カーネルスタックの管理を簡単にするためにL4はコンティニュエーションを多用している。

IPCの最適化

マイクロカーネルのシステムで最も大きなボトルネックとなるのがIPCである。たとえば、モノリシックカーネルではサブシステム間のコンテキストスイッチが起きない。それに対し、マイクロカーネルではサブシステムがユーザレベルのプロセスとして動くのでサブシステム間でコンテキストスイッチが起こる。サブシステムを細かく分割すれば安全性は高まるが、より多くのIPCが発生し、結果としてシステムの性能は低下する。

IPCはシステムコールであるが、トラップハンドラ内でIPC専用のパスを用意することで最適化する。

はじめに

まずは例外ハンドラの実装から。例外ハンドラが処理する事象は三つある。

  • システムコール
  • 例外
  • 割り込み

システムコールは例外の一種として扱われる。SH-4Aのマニュアルでは、第5章に例外処理の解説がある。詳細はマニュアルを参照されたい。

システムコール

システムコールとはユーザプログラムからのカーネルの機能の呼び出しのことである。L4マイクロカーネルは自身を特権モードで動かし、ユーザプログラムを非特権モードで動かす。一般に非特権モードで動いているプログラムは特権モードで動いているプログラムよりも出来ることが少ないので、非特権モードのプログラムが特別な処理を使用とする場合には、特権モードで動くプログラムにお願いする必要がある。このお願いする方法が多くのカーネルではシステムコールとして提供されている。

SH-4AではTRAPAという命令でシステムコールを発行する。ユーザープログラムがTRAPAという命令を発行するとCPUのモードは非特権モードから特権モードに自動的に切り替わる。その上で、プログラムカウンタの値をあらかじめ設定された番地に変更する。この番地に特権モードで動くプログラム、すなわちシステムコールを処理するプログラムを置いておけば、自動的に実行される。特権モードで動くプログラムはRTEという命令を発行し、非特権モードのプログラムに戻る。RTEは自動的にCPUのモードを特権モードから非特権モードに切り替える。

例外

例外はユーザプログラムやカーネルが誤った操作をしたときに発生する。たとえば、書き込みが禁止されている領域にデータを書き込もうとした場合や、0で割り算をしようとした場合などである。この場合にも、システムコールと同じように、CPUはプログラムカウンタを特定の番地に変更する。この場所に例外を処理するプログラムを置いておくことで、例外の後片付けをすることが出来る。

TLBミス

TLBミスは特殊な例外だ。TLBはアドレス変換のデータを格納するキャッシュである。あるCPUではTLBへのデータの格納はハードウェアが自動的に行ってしまう。SH-4Aにはソフトウェアで設定できるTLBが搭載されている。したがって、TLBへデータを格納するには適切なプログラムを呼び出してやる必要がある。このプログラムを呼び出すためにTLBミスという例外がある。TLBミスは変換したいアドレスがTLBに見つけられないと発生する。

割り込み

割り込みとは周辺デバイスを処理するための仕組みである。たとえばキーボードのキーを押すと割り込みが発生する。L4は割り込み処理のほとんどをユーザープログラムで処理するような構造になっている。カーネルは割り込みを受け取り、それを適切な形でユーザープログラムに転送する。

設計

SH-4Aの例外処理

SH-4Aの例外の多くは、VBR(ベクタベースアドレス)に配置されたプログラムで処理されるようになっている。VBRはソフトウェアで自由に設定できる。VBRに設定できる例外処理プログラム(例外ハンドラ)はそのアドレスから三つに分類される。

一般例外(オフセット0×100):多くの例外がここで処理される。システムコールを発行するTRAPAもここで処理される。処理の内容としては大きく三つに分けることが出来る。

  1. システムコール:システムコールを実行
  2. ページフォルト:ページャに転送
  3. エラー:カーネルで処理、もしくはページャに転送

一般例外のハンドラでは、この場合分けを行う。詳細な処理はそれぞれ専用の関数をハンドラのコンテキストで呼び出し、処理を任せる。

命令実行前ユーザーブレーク・命令実行後ユーザーブレーク
デバッガに転送

命令TLB保護違反例外(故障検知)
アクセスがITLBの保護情報に反する場合に発生する。つまり、ユーザモードで特権領域のコードにアクセスすると発生する。 特権領域にあるのはカーネルコードだけなはずだから、これはデバッガに落ちるか、プロセスの強制終了になる。

一般不当命令例外・スロット不当命令例外

命令アドレスエラー・データアドレスエラー(故障検知)
データ境界以外からの命令フェッチもしくはアクセス、またはユーザーモードでの特権領域へのアクセスで発生する。

データTLB保護違反例外(故障検知、もしくはページフォルトに変換)
アクセスがUTLBの保護情報に反する場合に発生する。これは二つの場合が考えられる。一つ目は、ユーザプロセスが特権領域のアドレスにアクセスした場合、もう一つは、ユーザプロセスが非特権領域のアドレスで許可されていないアクセスをした場合、もしくはカーネルプロセスが特権領域のアドレスで許可されていないアクセスをした場合である。後者は例えば、書き込みの許可されていないアドレスに書き込みアクセスをした場合である。 特権領域へのアクセスは、システム全体で禁止されているので、そのプロセスは強制終了させられる。非特権領域でのこの例外はページフォルトとしてページャに転送される。

FPU例外
例外としてユーザプロセスに転送する。

初期ページ書き込み例外(ページフォルトに変換)
ストアアクセスでTLBにヒットしたが、ダーティビットはゼロの時に発生する。つまり、アドレスはTLBに格納されているのだが、そのアドレスにまだ書き込みがされていない状態で発生する。何らかの理由によってTLBエントリだけを設定していたり、アドレスに対して読み込みだけしていたりする場合にこの状態になる。 L4上ではこの例外は上がってきてはいけない。万が一この例外が補足された場合には書き込み要求のページフォルトに変換する。

無条件トラップ
システムコール

TLBミス(オフセット0×400):TLBミスを処理する。多くの場合は、ページフォルトとしてページャに転送されるものと予想する。

割り込み(オフセット0×600):割り込みを処理する。ユーザーレベルのプログラムに転送する。

例外処理中の例外

例外の処理はソフトウェアで行われるため、例外処理中に他の例外事象が発生する可能性がある。これに対し、例外処理中に他の例外処理をする仕組みと、例外処理中に他の例外処理を禁止する仕組みを考える必要がある。

CPUは例外によって特権モードに移行する。SH-4Aは二種類のレジスタセット(レジスタバンク)を提供しており、モードによって自動的に切り替 わる。バンク0のレジスタR0からR7はユーザーモードのレジスタR0からR7に割り当てられる。バンク1のレジスタR0からR7は特権モードのレジスタ R0からR7に割り当てられる。バンク0のレジスタは特権モードでもアクセスしたり、R0からR7に割り当てることが可能だが、バンク1のレジスタは特権 モードでしか操作できない。例外ハンドラ起動時にはレジスタR0からR7はバンク1のR0からR7に割り当てられている。R8からR15はモードによる切 り替えはない。

例外処理中に他の例外を処理するためにはバンクを1から0に切り替えておく必要がある。

例外の優先順位

例外には優先順位がつけられており、これを実装にどのように反映すべきか考える必要がある。

実装

バンクの切り替え

バンクの切り替えはSRレジスタのMDビットの操作で行う。SRレジスタの操作には少なくとも一つの汎用レジスタ(R0からR15のいずれか)の使用を伴うので、場合によっては汎用レジスタの内容を退避する必要がある。

コンテキストの退避と復旧

例外処理以前の状態に復帰するためにコンテキストを退避しておきたい場合がある。多くのカーネルの実装と同じく、ここでもレジスタの値をスタックに保存する。

ハンドラの配置

一般例外、TLBミス、割り込みの各ハンドラを適切なアドレスに配置する必要がある。これにはbalign命令を使う。

一般例外

無条件トラップ:システムコールハンドラへ分岐。システムコールを実行する。

TLB保護違反例外と初期ページ書き込み例外:TLBハンドラへ分岐。条件によってはページフォルトに変換する。

ユーザーブレーク:カーネルデバッガへ分岐。

その他:一般例外ハンドラへ分岐。プロセスの強制終了などを判断する。

Machineクラスはビルドの設定に使うクラスである。tools/machines.py内に基底クラスが定義されている。

各メンバを解説する。等号は初期値

virtual = True

ターゲットの種類が仮想的なものであればTrue、そうでなければFalseを設定する。仮想的なターゲットはバイナリを生成しない。

default_toolchain = None

標準のクロスコンパイラを指定する。ツールチェインの名前はtools/toolchains.pyで定義する。

as_flags = []

アセンブラに渡すフラグを設定する。

c_flags = []

Cコンパイラに渡すフラグを設定する。

cpp_flags = []

Cプリプロセッサに渡すフラグを設定する。

cpp_defines = []

Cプリプロセッサに渡す定義を設定する。

cxx_flags = []

C++コンパイラに渡すフラグを設定する。

link_flags = []

リンカに渡すフラグを設定する。

drivers = []

カーネルと共にビルドするドライバのリスト。

device_core = None

iguanaのデバイスドライバフレームワークで使われる。

wordsize = None

標準のワードサイズ。単位はビット。

endian = None

“little”か”big”を指定する。

timer_driver = None

タイマードライバーの名前。

serial_driver = None

シリアルポートドライバーの名前

cpu = “”

CPUの名前。

memory = { ‘virtual’ : [], ‘physical’ : [], ‘rom’ : [], ‘tcm’ : [], }

有効なアドレスの範囲を設定する。

base_vaddr = 0×100000

プログラムを配置するときのベ基準アドレス。

preferred_alignment = 0×10000

標準のアラインメントサイズ。起動時のプログラムの配置に使われる。

min_alignment = 0×1000

最小のアラインメントサイズ。

smp = False

マルチプロセッサ構成かどうか。

zero_bss = False

BSSをゼロクリアするかどうか。

boot_binary = False

ブートイメージを生成するかどうか。

copy_elf = False

ELFイメージをビルドディレクトリにコピーするかどうか。

cust = “okl”

顧客の名前

default_feature_profile = “EXTRA”

simulate_cache = False

ターゲット

今回の移植ターゲットはSH2007(http://sh2000.sh-linux.org)。SH-4A(http://japan.renesas.com)のインストラクションセットを持つ。まずはシングルプロセッサのSH2007がターゲットである。SH2007はSH7780を搭載する。

OKL4ソースツリー

OKL4 3.0をhttp://wiki.ok-labs.com/Release/3.0からダウンロードする。

クロスコンパイラ

CrossToolsを利用する。http://www.kegel.com/crosstool/からダウンロードする。現時点でバージョンは0.43。クロスコンパイラのビルドは基本的に説明通り。gccのバージョンは3.4.5とした。Ubuntuの場合、これ以外にビルトに使うコンパイラを4.2とする必要があるかもしれない。

demo-sh4.shの変更点

gcc 4.2を指定することと、3.4.5をビルドするよう指定すること。

export CC=gcc-4.2
eval `cat sh4.dat gcc-3.4.5-glibc-2.3.5.dat` sh all.sh --notest

ディレクトリの追加

アーキテクチャ依存コードを格納するディレクトリを追加する。現段階ではマイクロカーネルと周辺ライブラリのみの移植を目指す。追加したディレクトリは以下のようになる。

arch/sh/ktest
arch/sh/libs/atomic_ops/include
arch/sh/libs/atomic_ops/src
arch/sh/libs/c/crt
arch/sh/libs/c/include
arch/sh/libs/c/src
arch/sh/libs/compat/include
arch/sh/libs/kernel/include
arch/sh/libs/l4/cpu/sh7780
arch/sh/libs/l4/include
arch/sh/libs/l4/src
arch/sh/libs/okl4/include
arch/sh/libs/soc/include
arch/sh/pistachio/cpu/sh7780/include
arch/sh/pistachio/include
arch/sh/pistachio/kdb
arch/sh/pistachio/src/gnu
arch/sh/tools
platform/sh2007/pistachio/include
platform/sh2007/pistachio/kdb
platform/sh2007/pistachio/src
platform/sh2007/tools

ビルドスクリプトの変更

OKL4のビルドは以下のようなコマンドで実行する。

./tools/build.py PYFREEZE=false MACHINE=pxa PROJECT=iguana

これを以下のようにしたい。

./tools/build.py PYFREEZE=false MACHINE=sh2007 PROJECT=ktest

まずは、SHクロスコンパイラを起動するようにビルドスクリプトを変更する。編集するのはtools/toolchains.py。クラスsh_toolchainを定義し、登録する。

class sh_toolchain(generic_gcc):
    def __init__(self, *args):
        generic_gcc.__init__(self, *args)
        # gccのフラグに追加
        self.dict["CCFLAGS"] += ["-m4-nofpu"]
        # g++のフラグに追加
        self.dict["CXXFLAGS"] += ["-m4-nofpu"]

gnu_sh_toolchain = sh_toolchain("sh-linux-")

アーキテクチャ毎の設定

アーキテクチャ毎の設定はarch/sh/tools/machines.pyに書く。各アーキテクチャはクラスとして表現される。ここでクロスコンパイラを指定する。

from machines import Machine, Region
from toolchains import gnu_sh_toolchain

class sh(Machine):
    # サポートしているページサイズ(1KiB, 4Kib, 64KiB, 1MiB)
    page_sizes = [0x400, 0x1000, 0x10000, 0x100000]
    # プログラムをアラインするときの標準のサイズ
    preferred_alignment = 0x10000L
    # 標準のワードサイズ
    wordsize = 32
    # アーキテクチャの名前
    #(アーキテクチャ依存コードを格納するディレクトリの名前)
    arch = "sh"
    # エンディアン
    endian = "little"
    # エミュレータを指定する。SHのエミュレータがないのでここは空。
    default_method = ''
    # 標準のクロスコンパイラ
    default_toolchain = gnu_sh_toolchain

# SH-4 (SH7750 series)
class sh4(sh):
    # 有効なアドレス範囲のリスト。tools/machines.pyで定義されている。
    memory = sh.memory.copy()
    # 有効な仮想アドレスの範囲を定義
    memory['virtual'] = [Region(0x1000, 0x7ef00000)]
    # アーキテクチャのバージョン(SH4なので4とした)
    arch_version = 4

# SH-4A (SH7780 series)
class sh4a(sh):
    memory = sh.memory.copy();
    memory['virtual'] = [Region(0x1000, 0x7ef00000)]
    # アーキテクチャのバージョン
    #(アルファベットを指定できないので4とした)
    arch_version = 4

class sh7780(sh4a):
    # CPU依存コードを格納するディレクトリの名前
    cpu = "sh7780"

class sh7785(sh4a):
    cpu = "sh7785"

プラットフォーム毎の設定

プラットフォーム毎の設定はplatform/tools/machines.pyに記述する。各プラットフォームはクラスとして表現される。

import copy

class sh2007(sh7780):
    virtual = False
    # ビルドスクリプトを起動するときにMACHINEで指定する文字列
    platform = "sh2007"
    # プラットフォーム依存コードを格納するディレクトリ
    platform_dir = "sh2007"
    # タイマードライバーの名前
    timer_driver = ""
    # シリアルポートドライバーの名前
    serial_driver = ""
    # ビルドするドライバーのリスト
    drivers  [timer_driver, serial_driver] + sh7780.drivers

    memory = sh7780.memory.copy()
    # RAMがマッピングされる仮想アドレス
    memory['physical'] = [Region(0x88000000, 0x90000000)]
    # ROMがマッピングされる仮想アドレス
    memory['rom'] = [Region(0xA0000000, 0xA80000000)]
    # プリプロセッサに与える文字列。ソースコード内で
    # プラットフォーム依存部を切り替えるのに使うことが出来る。
    cpp_defines = sh7780.cpp_defines = [("PLATFORM_SH2007", 1)]

pyelf

pyelfはELFファイルを編集するプログラムである。ELFとは実行可能ファイルの形式の一種である。ELFファイルには機種依存の部分がある。したがって、IA32アーキテクチャ、ARMアーキテクチャ、SHアーキテクチャのELFファイルはそれぞれ微妙に異なる。pyelfはELFファイルを編集するのでこの微妙な違いを理解しなければならない。機種依存のコードは、tools/pyelf/elf/abiに格納する。SHのコードは他のファイルに倣ってsh.pyとする。

ELFファイルの機種依存部の形式はgccでも定義されているので、gccのソースコードから必要な部分だけをコピーしてくる。

ディレクトリ構成

さて、どうやってOKL4を新しいアーキテクチャに移植するのか、それを考えなければならない。OKL4のソースツリーを見ると、アーキテクチャ依存コードは、archディレクトリとplatformディレクトリにまとめられている。

archディレクトリ

archディレクトリにはプロセッサ依存コード、つまり、ARMプロセッサのコードであれば、archディレクトリの下にarmというディレクトリがあり、そこにARM関連のソースコードが格納される。プロセッサ毎の細かな違い、たとえば、ARMコアはバージョン毎で機能が異なるのだが、このような違いもこのディレクトリ内のソースコードで吸収される。

platformディレクトリ

platformディレクトリはボード依存コードが格納される。同じARMプロセッサを使っていてもボードによっては異なる周辺装置を搭載しているので、その違いをこのディレクトリ内で吸収する。

その他

tools/pyelf/elf/abiディレクトリにもアーキテクチャ依存コードがある。

方針

ここでは全体的な方針について簡単に議論する。今回の移植の方針はズバリ、ツールに助けてもらう、だ。とにかくコンパイラやリンカのエラーをつぶしていく。その上で人間が見て足りない部分を補い、最終的には実行時のエラーを見て修正していく。人間は間違えたり、見逃したりするということを前提にすれば、コンパイラやリンカのようなツールを使って網羅的にエラーを発見していくことは効率がよいと考える。

ビルド環境を整える

まずは、ビルド環境を整える必要がある。ビルド環境がないとコンパイルすることすら出来ない。すなわち、コンパイルエラーやリンクエラーを見ることが出来ない。

  1. クロスコンパイラのインストール
  2. ビルドスクリプトの変更

ARMのコードを見ながら

移植の方針その一は、ARMのディレクトリ(arch/arm)を見ながらファイルを追加していく方法である。まずはARMのファイルを観察する。どのようなクラスがあり、どのようなコードが必要とされているのかを大まかに把握する。

コンパイルエラーを見ながら

ディレクトリやファイルを適当に追加したらコンパイルしてみる。コンパイルエラーを一つ一つ潰していく。ARMのソースコードを持ってきたときにはコンパイルできてしまう場合があるので、それで正しいのかどうか注意する。

リンクエラーを見ながら

コンパイルが通るようになったら、リンクエラーを見つつ、未定義の関数を実装していく。

導入

OKL4とはL4マイクロカーネルの系譜にあるマイクロカーネルの実装である。OKL4はオーストラリアの大学で開発が開始され、現在はOKLabという企業が開発を主導している。

動機

現在、OKL4が動作するのは公式にはARMプロセッサだけである。私自身のプロセッサの勉強も兼ねて、OKL4のSH4aへの移植を行う。

ソースコードを公開した。http://www.dcl.info.waseda.ac.jp/~ishikawa/okl4.html