ページング開始
startKernel関数のページング開始処理(PageManager::setup)まで移植が進みました(リビジョン5)。
新たに移植したモジュールは、
- IDManager
- PageManager
- Segment
- Messenger
です。
Paging開始処理に重点をおいたために、一部不完全で以下のようになっています。
- Segments.cpp
- Scheduler::FindProcess()呼び出し部分を無効
- PageManager.cpp
- DMA/VRAM領域のマッピングを無効
- Process::getStackBottom()呼び出し部分を無効
- ThreadOperation::Kill()呼び出し部分を無効
- Messenger.cpp
- Scheduler::send/EventComes()呼び出し部分を無効
- Kernel.cpp
- systemcall_mutex_create()呼び出し部分を無効
- Paging設定後、panicで停止
移植概要
オリジナル(x86)のカーネル領域のページング初期化処理の概要は以下の通りです。
- ページサイズは4KBを採用
- マッピングサイズはトータル8MB
- 従ってページテーブルは2つ(4M×2)
- 8MBの後ろ500KBをDMAのページにマップ
- VRAM用のページもマップ(詳細未調査)
上記に対してMonarmでは以下のように設定を行いました。
- ページサイズは4KBを採用
- マッピングサイズはトータル64MB
- ページテーブルは64個(ARMでは1テーブルで1MBまで表現)
- DMAとVRAMはとりあえず保留
以前のエントリでカーネルを最低限動作可能にするため、既にMMUは開始済みでPagingも有効になっていましたが、PageManagerによる正式な初期設定を移植しました。
このため、実質は、
-
- 固定メモリ領域(0x3100-0000)に生成した変換テーブル
から
-
- カーネルメモリから動的に取得した領域に生成した変換テーブル
への置き換え*1のみでOKなので、既に有効であるMMUを再度有効にする(startPaging関数の実行)のは気持ちが悪いですので時間があったら見直したいと思います…。
ページング概要
Monarmカーネル領域のページングの概要はこんな感じになります。
カーネル領域である、SDRAMの先頭64MBはファインページテーブル経由の4KBのsmallページでのページングになります。
それ以外のI/O領域等のページは1MBのセクションページです。
次いで、各ディスクリプタの内訳です。
としています。
来るユーザ領域もsmallページになるかと思いますが、カーネルのページのディスクリプタとアクセス許可のみが異なるだけOKと踏んでいます。
ドメインは現状、ドメイン0のみの設定で、クライアント設定に指定しています(アクセス許可を行う設定)。
(ドメインの設定はastart.S設定で行っています)
割り込みコントローラ初期化
割り込みコントローラ初期化(pic_init)とインターバルタイマ設定(10ms)の移植が完了しました(リビジョン4)。
ただし、割り込みハンドラの移植が済んでいないため、タイマとしては機能しません(テストコード上で動作確認)。
メモ
セグメント機構はx86固有でARMには該当する機能がないと認識しているので*1、移植対象外としました。
それと、システムメモリのサイズを把握する処理で、オリジナルでは1MBオフセットしたアドレスからラスト(4GB)まで、1MB単位の先頭4バイトに対して直接のライト/リード/コンペアでのメモリ有効チェックを行っています。
ARMはメモリマップドI/Oであり、且つ現状、使用中のページテーブルがあったりするので、固定値を使用するようにしました。
また、前回までの初期状態のページテーブルは1ページのみのエントリでしたが、今回でシステムメモリをフルにエントリへ登録しています。
新たな問題
かなり#ifdefの嵐になってまいりました。まずいまずいと思いながらも既存コードとの比較をしながらコードを書きたいためにどうしてもやってしまいます。
何か良い方法は無いものか…
お次は…
今の状態はこんな感じでしょうか。
(by やまざきメソッド ジェネレーター)
今までは一本筋で進められましたが、以降はプロセスを回すための核の項目となり、各々が密に関連しているため同時作業せざる終えません。
なので開始以来の山場になりそうです。
*1:データシートを確認すべき…
MMUオン
とりあえずですが、MMUを有効にしてみました(リビジョン3)。
変換を行ったのは以下のページ(kernel領域)で、それ以外のページはストレートにマッピングしました。
kernelイメージは、アドレス0からの仮想アドレスを前提にビルドを行っているため、以前のリビジョン(2)の状態では、dataセクションのアクセスは正常ではありませんでした。
(textセクションは相対アドレスのため動作はしていました)
今回の更新によって、startKernel関数に到達後の移植が進められるようになりました。
現在の具体的な状況としては、startKernel関数のはじめで、メモリ領域の初期後、ログを出力してビジーループで停止させています。
実行方法
実行までの手順を以下に示します(予め、クロスツールとqemu-bishopがインストールされている事が前提です)。
- share/configs/Makefile.inc の MONADIR と PREFIX を環境に合わせて修正
- トップディレクトリでmake
- bin ディレクトリの runqemuスクリプトのBishopエミュレータのパスを環境に合わせて修正
- runqemu実行
すると、
('V`)< Mona version.0.3.0Alpha9 on ARM920T(S3C2440A/Bishop) $Date:: 2008-04-29 11:35:00 +0900#$ / 0x00000300
のようなログが出力されると思います。
(qemuモニタへ遷移するには、^C-a cです。一度改行すればモニタのプロンプトが見えます)
いいわけ
現在、移植に伴う数ある懸念の中で特に悩んでいるのが、アーキテクチャに依存するコードの切り分けです。
特定のアーキテクチャやプラットフォームに縛られず、複数のそれらをサポートするOSはその依存する部分を個別のディレクトリに管理していると思います。
そして、コンフィグレーションによってアーキテクチャの選択やコンポーネントの取捨ができるようになっており、その設定の結果がMakeやコードに反映されます。
これは、それぞれのOSで様々なルールがあり、一長一短があると思います。
このようなルールが無い場合、実際に移植を始める前に良く練るべきですが、これはかなりセンスが要ると思います。
そのセンスがなく、それを考えるだけでも大変なので、現在はad hocに進めています。
また遊び半分のため、必要になったらその時にまた考えるようにしたいと思います。
Monarm
MonaOSをARM上に移植してみます。
同OSはx86べったりだと思いますが、なるべくオリジナルの設計を崩さないように考慮したいと思います。
環境等はARMの擬似環境構築時のものを使用します。
MonaOSは 0.3.0alpha9 です。
とりあえずKernel開始(cstart関数)に到達するところまで移植できたので恥を惜しまず公開します。
追記/お詫び
記載していたsvnリポジトリは、登録ユーザ以外、閲覧することができませんでした。
以下に別のリポジトリを設けましたのでこちらを参照してください。
http://www.assembla.com/spaces/monarm/trac_subversion_tool
メモリマップ
移植前にまず、メモリマップを考えました。オリジナルはこちらのようになっています。
とりあえず以下のようにしました。
最終的なリセットベクタは0からに配置予定です。
従ってオリジナルがBaseとしている0x1200に合致しないのですが、そのあたりは追々考えていきたいと思います。
ブートローダ
オリジナルのブートシーケンスは、
- firstboot(先頭セクタ)
- secondboot
と2本立てになっています。これを1本化して、現状、
といった手順を踏みます。
本来ならば、オリジナルで行っているサーバプロセスの読み込みが必要ですが、そこまでに至っていません。
Kernel
本体であるKernel.cppにはまだ到達していません。
cstart.cppでメッセージを出して止めています。
なんの面白みもありません。これからこれから。
割り算ルーチンには、ARM向けlibgcc.aをリンクするようにしました。
また同ライブラリ内のdiv0関数がraise関数を呼び出すため、とりあえずダミースタブを作成して対応しました。
(in cstart.c)
近々の課題
*1:u-bootを参考にしました
ゴリ押しでgccにてMakeを通す
id:higepon氏が発足し今尚開発が行われている、自作OS界隈では云わずと知れたMonaOS - Free Operating Systemですが、そのLinux上におけるtoolchainにはMINGWが使用されています。
そこで、GNU toolchainによってMakeを無理やり通してみた結果、何とか起動し動作が確認できたので、ざっくばらんに手順を記録しておきたいと思います。
はじめに
前提として以下の緩いルールを設けました。
- 実行形式
- PEフォーマット、圧縮形式はナシ(.BIN と .ELF のみ)
- ライブラリ
- 共有ライブラリはナシ(全てstaticリンク)
- アセンブラ
- nasmを使用する(gasへの変換はしない)
また、今回の環境は以下の通りです。
Mona | mona-0.3.0alpha9 |
Host | colinux-2.6.12-co-default |
GCC | 4.1.2(Debian 4.1.1-21) |
share/configs
GNUのldのリンカスクリプト取得(User用)
- --verboseオプションにて。ファイル名はmonapi.ldsとする
- syntax errorになるような先頭数行と最終行は削除、またはコメントアウト
- __executable_start と SIZEOF_HEADERS への値をカレントアドレス(.)へと修正
- PROVIDE (__executable_start = .); . = SIZEOF_HEADERS;
- .ctorセクションに、__CTOR_LIST__、__CTOR_END__とフラグ(0xFFFFFFFF/0)を定義
__CTOR_LIST__ = .; LONG(-1); …コンストラクタの定義… LONG(0); __CTOR_END__ = .;
- .dtorセクションも同様に__DTOR_LIST__、__DTOR_END__とフラグ(0xFFFFFFFF/0)を定義
Makefile.inc
Makefile.incはconfigureスクリプトにて生成されるので一旦、MINGWとしてconfigureスクリプトをトップディレクトリにて実行します。
% ./configure --mingw-prefix=/usr/bin/i586-mingw32msvc-
生成されたMakefile.incを以下のように修正しました。
monapi-bin.inc
- .cppサフィックスルール
- g++へのオプションに -fno-use-cxa-atexitを追加
- TARGETルール
- ldに-Tオプションでmonapi.ldsを指定
- -T $(SHAREDIR)/configs/monapi.lds
- ldに-Tオプションでmonapi.ldsを指定
-fno-use-cxa-atexit指定しない場合、__static_initialization_and_destruction_0関数がリンクされて、その関数内で参照している __dso_handletと__cxa_at_exitが無いと文句を言われてしまうためです。
2007-02-03 - memologueにて詳細な解説がなされています。
core/kernel
Makefile
- ldに-Tオプションでkernel.ldsを指定
- -T $(SHAREDIR)/configs/kernel.lds
core/monalibc
Makefile
- TARGET変数定義 に += で $(CRT_OBJECT)を追加
- BUILD_TARGET=ELFだと追加されてないため
- LINK変数定義の-lmonapi-imp を -lmonapi に変更
- g++のオプションに以下を追加
-nostdinc -nostdlib -fno-exceptions -fno-rtti -fno-strict-aliasing
-
- gxx_personarity_v0エラーが出るため
core/monapi
Makefile
- installターゲットに$(CRT_OBJECT)を追加
_alloca.asm
- シンボルの _ プレフィックス削除
messages.cpp
- server_names の ".BN5"、".EX5" を ".BIN"、".ELF" に変更
core/shell_server
Makefile
- BUILD_TARGET=ELF であった場合のinclude文を monapi-el5.inc から monapi-elf.inc へ
main.cpp
- LookupMainThread("SCREEN.EX5")の引数を"SCREEN.ELF"に指定
Shell.cpp
- commandExecuteでコマンドライン処理している辺りの name + ".EX5" や command + ".EX5" を ".ELF" に
core/monitor_server
main.cpp
- すべての ".BN5" と ".EX5" を ".BIN"、".ELF"に
core/keyboard_server
Makefile
- BUILD_TARGET=ELF であった場合のinclude文を monapi-el5.inc から monapi-elf.inc へ
- ADDLINK変数定義の-lmonalibc-imp を -lmonalibc に変更
core/screen_server
Makefile
- BUILD_TARGET=ELF であった場合のinclude文を monapi-el5.inc から monapi-elf.inc へ
core/mouse_server
- screen_serverと同様
core/pe_server
Makefile
- include文を monapi-bn5.inc から monapi-bin.inc へ
tool/mkimg
MONITOR.CFG
- すべての ".BN5" と ".EX5" を ".BIN"、".ELF"に
- PEサーバとSCHEMEサーバの行をコメントしそれ以外は有効に
AUTOEXEC.MSH
- testプログラム自動実行文を追加(バナー出力後あたりに)
- @EXEC /APPS/TEST.APP/TEST.ELF
include/sys
types.h
- 以下の定義を追記
#define __fastcall __attribute__((__fastcall__)) #define __declspec(x) /* */
Makeと実行
ソーストップディレクトリで以下を実行
% make % cd tool/mkimg % qemu -L /usr/share/qemu -m 128 -fda mona.img -no-kqemu -nographic -cdrom mona.iso -boot d -serial stdio
すると、printfしている内容が文字単位で<>で囲まれて出力されると思います。
グラフィック出力のないcolinuxでの動作確認のためグラフィック出力を無効(-nographic)にしています。*1
出力可能な環境であれば、-nographicを外すとシェルが動くはずです…。
超初歩的なmakeまとめ
これまでGNU環境で、プログラムを作成するに当たってMakefileが必要になったとき、公開されている既存のものを参考にして、必要な部分のみの修正で事が足りていました。
しかし、1からMakefileを作成するとなると、やはりその基本を抑えておく必要があります。
そこで、いろいろと調べた結果などをまとめたいと思います。
尚、今回使用したmakeのバージョンは GNU Make 3.81 です。
超基本的なMakefile文法解説
Makefileは以下のような構文ブロック(エントリ)から成り立っています。
ターゲット: コンポーネント # 依存関係行/ルール行 <tab>コマンド # コマンド行
- ターゲットに対してコンポーネントは複数指定でき、省略することも可能。
- コンポーネントは、同Makefile中の別のエントリのターゲットを指定することも可能。
- コマンド行は先頭がtabで、ターゲットのためのコマンドラインを記述。
- コマンド行は1エントリ中に複数書く事ができ、省略することも可能。
- ターゲットは複数指定することも可能。
- #以降はコメント
超基本的な動作解説
makeの基本的な動作の流れをつかむため、先の文法に倣い、以下のようなファイルを作成します。
A : C B echo A B : D C : echo C D : echo D
ファイル名をMakefileで保存し、makeを実行すると、
% make -s C D A
と出力されました。(-s は進捗状況出力を抑えます。結果を見やすくするために指定しました)
ポイントは、
- コンポーネントが指定されている場合は、左側のコンポーネントから順次評価される。
- そのコンポーネントが別のエントリのターゲットの場合、構成するコンポーネントを評価…と末尾まで評価されていく。
- make実行時、ターゲットを省略した場合、Makefileの一番上に記述されたエントリのみが評価される。
という点です。
次に、以下のようなMakefileを考えます。
hoge: foo bar cat foo bar > hoge foo: touch foo bar: touch bar
このMakefileに対して立て続けに2回実行してみると、警告を発して終了します。
% make touch foo touch bar cat foo bar > hoge % make make: `hoge' は更新済みです
理由は、ターゲットを評価する時点で、
この両方が成立すると、makeはターゲットを生成しないためです。
試しに以下のように外部からコンポーネントを更新しmakeすると、
% touch foo % make cat foo bar > hoge
上記の条件は成立しなくなるため、最終ターゲットは生成されます。
サフィックスルール
コンパイル/アセンブルを行う際、構築のためのコマンドに対するソースファイルと、その結果となる出力ファイルのサフィックスは、言語によって通例的に決まっています。(C言語で言うならccで.c→.o)
簡単に言ってしまうと、このサフィックスの関係の定義がサフィックスルールです。
入力/出力も含めて、サフィックスルールで登場するサフックスの定義は以下のように行います。
.SUFFIXES: サフィックス サフィックス ...
そして、サフィックスルールはエントリと同じように定義しますが、依存関係行を以下のようにします。
.サフィックス.サフィックス:
出力用の2つ目のサフィックスは省略可能で、その場合、出力ファイルはサフィックスがないものとして扱われます。
また、コンポーネントは指定しても無視されます。
以下のMakefileは実際にサフィックスルールを利用したものです。
.SUFFIXES: .x .y .z dst: src.z mv src.z dst .y.z: cp src.y src.z .x.y: cp src.x src.y src.x: touch src.x
1行目がサフィックスルールで使用するサフィックスの定義、2・3つ目がサフィックスルールを定義したエントリです。
結果は以下のようになります。
% make touch src.x cp src.x src.y cp src.y src.z mv src.z dst rm src.y
依存関係の流れの概要は、
- 最終ターゲットdstはコンポーネントsrc.zに依存しており、
- src.zは2つ目のルール「*.y→*.z」が適用されて、src.yに依存し、
- src.yがターゲットである3つ目のルール「*.x→*.y」が適用され、src.xに依存し、
- srx.xをターゲットとする4つ目のエントリのコマンドから順次処理されていく。
となります。
最後に記述にないrmが実行されていますが、これは、サフィックスルールを適用する過程で生成され、最終的に不要になるコンポーネントをmakeが削除しているためです。
また、サフィックスルールは標準で定義されており(デフォルトルール)、暗黙のうちに適用されるものがあります。
デフォルトルールを確認するにはmake実行時に-pオプションを指定します。
その他特殊ターゲット
その他、makeには特殊なターゲットが用意されています。以下にまとめます。
ターゲット | 効能 |
---|---|
.SILENT: | コマンド行の状況を出力しない。-sと同等。 |
.IGNORE: | コマンド行が失敗しても処理を継続する。 |
.DEFAULT: | Makefileにエントリがなかった場合に評価されるターゲットエントリ。 |
.PRECIOUS:ファイル名 | 指定したファイルをmakeが消去しないようにする。 |
.PHONY:ターゲット | ファイルが不要なターゲットを定義する。 |
.PRECIOUSはサフィックスルールの解説例で、src.yを指定すれば最後に勝手に削除されるのを防止できます。
.PHONYは以下のようなcleanターゲットでよく利用されます。
.PHONY: clean clean: rm *.o
理由は、仮にcleanというファイルが存在した場合、コンポーネントを持たないターゲットとして扱われ、コマンドが実行されません。
そのため、.PHONYによってcleanというターゲットはファイル名ではない事を指示します。
マクロ
Makefile中に環境変数のような感覚で、マクロを利用する事ができます。
マクロを定義する場合の例は以下の通りです。
CORE = main.o low.o PLATFORM = peripheral.o ${CORE} print_objs: echo ${PLATFORM}
定義したマクロを参照する際の{と(はどちらでもOKです。1文字のマクロは括弧不要です。
exportされている環境変数はマクロとして扱う事ができます。
また、以下のようにコマンドラインからマクロを定義する事も可能です。
% MACRO1=macro1 make MACRO2=macro2
makeコマンドの前後どちらでも定義可能ですが、以下の同名マクロの優先順位付けにより意味が異なります。
- makeで定義された標準のマクロ
- 環境変数とコマンドラインのmakeより前に定義されたマクロ(上の例ではMACRO1)
- Makefile中に定義されたマクロ
- コマンドラインのmakeより後に定義されたマクロ(上の例ではMACRO2)
1.の標準で定義されているマクロから、よく使われるものを以下に示します。
マクロ | 効能 | 注意点 |
---|---|---|
$@ | ターゲット名を示す | 依存関係行では$$@ |
$? | 指定されたコンポーネントのうち、ターゲットより新しいもの全てを示す | サフィックスルールでは使用不可 |
$ | ターゲットより後に更新されたコンポーネント名 | サフィックスルールと.DEFAULTエントリのみ使用可 |
$* | $<のサフィックスを除いたもの | サフィックスルールのみ使用可 |
その他、デフォルトで定義されているマクロはオプション-pで確認する事ができます。