読者です 読者をやめる 読者になる 読者になる

Kernel完了

Kernelディレクトリ以下の移植が一通り完了しました。
変更を加えた箇所を清書してないのでソースが汚いですが、区切りがいいのでまとめたいと思います(リビジョン7)。
前回から今回の更新にかけて行った事は以下になります:

  • これまで一部無効にしていた箇所の有効化
    • Process.cpp
    • Sheduler.cpp
    • ihandlers.cpp
    • PageManager.cpp
  • syscalls.cppの移植
  • アボート例外の移植
    • astart.S(旧ihandler.asm)
  • Loader.cppの追加


以上から、前回から以下の事柄が可能になりました:

  • Loaderからプロセスを起動できるようになった
  • プロセス空間が切り替わるようになった
  • スレッドが規定のポリシーでスケジューリングされるようになった
  • デマンドページングが可能になった


これで積み残しの部分はほぼ解消しましたが、まだDMAページの設定が無効のままになっています。これに関してはデバイスドライバ移植までとっておきたいと思います。*1
また、細かい点では以下の修正・変更があります:

  • Kernelメモリを6MBに戻した(0x20000〜0x80000)
    • 常駐するカーネルページは8MBでオリジナルと同様
    • 当初64MBにしたのはカーネルメモリ内で物理ページをやりくりすると勘違いしていたため。実際はカーネルページ以降のページが割り当てられる。
  • システムメモリを64MBに(プラットフォーム上は128MB)
  • ページテーブルディスクリプタをファインページからコアースページに変更
    • テーブルエントリが1024→256に
    • 1ページテーブルあたりの仮想メモリ範囲は1MBで同様なので、同一アドレスの4回重複が無くなった*2
  • abortモード用のスタックを割り当てた(0x7000から)

カットアンドトライで進めているため無駄に修正をしていた部分が目立ちます。

移植概要

今回の移植でアーキテクチャに依存する箇所は以下の2点です。

■アボート例外

ARMではアボートとして以下があります*3

ソース 原因
アライメント 境界整列していない
変換時の外部アボート 外部メモリシステムで発生したエラー
変換フォルト セクションまたはページの記述子が無効として設定されている
ドメイン セクションまたはページのドメインがアクセス不可ドメインに設定されている
許可フォルト セクションまたはページのドメインがクライアントであった場合、ページ単位のアクセス許可されていない

また、上記各アボートに対し、

  • プリフェッチアボート
  • データアボート

と、例外の種類すなわちベクタが分かれており、それぞれ、

  • 命令にアクセスした時
  • データにアクセスした時

に物理ページへ変換を行う過程で上述の原因に適合した場合、各々の例外が発生します。


オリジナルではページフォルトが発生した際の例外をハンドリングしています。x86ベクタNo14に該当します。
従って変換フォルトが発生した際に、フォルトアドレスとエラー内容を引数に既存のフォルトハンドラを呼び出すよう、そのまま移植しました。
変換フォルト以外のアボートは対応していません。


MonaOSページフォルトの流れを簡単に示すと下図のようになると思います。

無効なページへのアクセスでページフォルトが発生しアセンブリで記述されたフォルトハンドラに制御が移り、以下の処理が行われます。

  • 現スレッド構造体にコンテキストを保存
  • フォルトアドレスとエラー内容をCPPで記述されたメインのフォルトハンドラの引数に設定
  • フォルトハンドラ(メイン)を呼び出す
  • フォルトしたアドレスに復帰する

MonaOSにおけるプロセスのメモリ空間は、セグメントとしてShared、Heap、Stackに区切られます。(それぞれに関しての詳細は追いきれてませんが、名称から推測してそのままの意味だと思います)
上述のフォルトハンドラから処理が移ったメインのフォルトハンドラでは、各セグメントに対するフォルト処理が行われます。
各々のセグメント処理を行う条件は以下の通りです。

Shared
エラー内容が無効なページへのアクセスによるものであり、かつ複数あるSharedセグメントにアクセスしたフォルトアドレスが含まれていた場合
Heap
Heapセグメントにアクセスしたフォルトアドレスが含まれていた場合
Stack
Stackセグメントにアクセスしたフォルトアドレスが含まれていた場合

上記に該当しなかった場合はそのプロセスを強制終了します。

システムコール

x86においては、ユーザ定義のソフトウェア割り込みはINT命令を使用します。
命令のオペランドであるベクタNoは32〜255を使用することができ、MonaOSでは、お決まりの0x80番をシステムコールにアサインしています。

ARMでも同様の命令としてSWI命令がありそれを使用しますが、x86の場合とは若干勝手が異なります。

命令 オペランドの扱い
INT システムコールベクタ番号であることを示す
SWI システムコールの種類(ID)であることを示す

つまり、SWI命令によって直接システムコールベクタに飛ぶので、ベクタ番号は命令にエンコードする必要がないのです。
SWI命令の32bit中、下位24bitが指定されたシステムコールIDになります。
SWI命令によって処理が移ったハンドラからシステムコールIDを取得するには、SWI命令が発生した(SWI命令がある)アドレス、つまり戻りアドレス-4のアドレスを取り出して下位24bitをマスクすれば取得できます。

従って、システムコールを発行する前段階のレジスタへの設定方法が異なります。
その対応は以下のようにしました。

用途 MonaOS Monarm
戻り値 eax r0
Call ID ebx なし
引数1 esi r2
引数2 ecx r3
引数3 edi r4
引数4 edx r5

またシステムコールの仕組みは割り込みやフォルトの流れとほとんど一緒です。

  • 現スレッド構造体にコンテキストを保存
  • システムコール本体を呼ぶ
  • 現スレッド構造体のメンバであるeaxの値をeaxレジスタに保存
  • 復帰

システムコールに関しても処理の流れ自体に変更はありません。

プロセスロードの仕組み

Loaderの追加によってプロセスの生成が可能になりました。
Loaderからプロセスを生成する流れを簡単に追ってみます。

INITプロセスの処理(青)

新規にプロセスを生成する側のプロセス(INIT)の処理:

  1. 0x80000000からサイズ0x32000を共有メモリ(Shared segment)として設定する
  2. 新しいプロセスを生成する(コンテキストの生成)
  3. 生成したプロセスのメモリ空間の0xA0000000からサイズ0x32000を共有メモリとして設定する
    • 共有メモリの識別IDを1.と同じ値にする
  4. 0x80000000にロードするプロセスのイメージをコピーする
  5. 0x80000000はマップされていないページのため、ページフォルトが発生する
    • 0x80000000は共有メモリとして設定されているため、Shared segmentのフォルトハンドラで対処
    • マッピングされていないため、新規に物理ページが割り当てられる
  6. ページングが行われて目的のイメージがメモリ上にコピーされる
Userプロセスの処理(紫)

起動される側のプロセス(User)の処理:

  1. 0xA0000000のコードをフェッチ(Userプログラムのエントリポイントは0xA0000000)
  2. 0xA0000000はマップされていないページのため、ページフォルトが発生する
    • 0xA0000000は共有メモリとして設定されているため、Shared segmentのフォルトハンドラで対処
  3. ページングが行われてコードがフェッチ可能となる

といった感じだと思います。
INITプロセスの0x80000000とUserの0xA0000000が同一の物理メモリ領域に対応するのは、共有メモリの識別IDが同一であるためです。

お次は

自分で書いたソース(特にアセンブリ)をきれいにしつつ、各サーバプロセスの起動に移ろうと思います。

*1:それ以外に忘れものがあるかも・・・

*2:はじめからそうしていれば

*3:優先度順、VMSAv6以降はもう少し種類がある