ページング開始

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のセクションページです。


次いで、各ディスクリプタの内訳です。

  • カーネルページはキャッシュ・バッファ有効、SVCモードのみアクセス可
  • それ以外のページはキャッシュ・バッファ無効、SVCモードのみアクセス可

としています。
来るユーザ領域もsmallページになるかと思いますが、カーネルのページのディスクリプタとアクセス許可のみが異なるだけOKと踏んでいます。

ドメインは現状、ドメイン0のみの設定で、クライアント設定に指定しています(アクセス許可を行う設定)。
(ドメインの設定はastart.S設定で行っています)

後は

  • ihandler
  • syscall
  • Scheduler
  • Process

を移植できれば、まがいなりにもカーネル初期化処理は完了して、カーネル内からユーザプロセスを生成するぐらいは可能になるかと思います。
先は長いー。

*1:CP15/レジスタ2の書き換え。x86で言うCR3

割り込みコントローラ初期化

割り込みコントローラ初期化(pic_init)とインターバルタイマ設定(10ms)の移植が完了しました(リビジョン4)。
ただし、割り込みハンドラの移植が済んでいないため、タイマとしては機能しません(テストコード上で動作確認)。

メモ

セグメント機構はx86固有でARMには該当する機能がないと認識しているので*1、移植対象外としました。


それと、システムメモリのサイズを把握する処理で、オリジナルでは1MBオフセットしたアドレスからラスト(4GB)まで、1MB単位の先頭4バイトに対して直接のライト/リード/コンペアでのメモリ有効チェックを行っています。


ARMはメモリマップドI/Oであり、且つ現状、使用中のページテーブルがあったりするので、固定値を使用するようにしました。
また、前回までの初期状態のページテーブルは1ページのみのエントリでしたが、今回でシステムメモリをフルにエントリへ登録しています。

新たな問題

かなり#ifdefの嵐になってまいりました。まずいまずいと思いながらも既存コードとの比較をしながらコードを書きたいためにどうしてもやってしまいます。
何か良い方法は無いものか…

お次は…

今の状態はこんな感じでしょうか。

  (by やまざきメソッド ジェネレーター)


今までは一本筋で進められましたが、以降はプロセスを回すための核の項目となり、各々が密に関連しているため同時作業せざる終えません。
なので開始以来の山場になりそうです。

*1:データシートを確認すべき…

MMUオン

とりあえずですが、MMUを有効にしてみました(リビジョン3)。
変換を行ったのは以下のページ(kernel領域)で、それ以外のページはストレートにマッピングしました。

マッピングサイズ
セクション(1MB)
仮想アドレス
物理アドレス
0x30000000(SDRAM)
変換テーブルの場所
0x31000000(SDRAM)


kernelイメージは、アドレス0からの仮想アドレスを前提にビルドを行っているため、以前のリビジョン(2)の状態では、dataセクションのアクセスは正常ではありませんでした。
(textセクションは相対アドレスのため動作はしていました)


今回の更新によって、startKernel関数に到達後の移植が進められるようになりました。
現在の具体的な状況としては、startKernel関数のはじめで、メモリ領域の初期後、ログを出力してビジーループで停止させています。

実行方法

実行までの手順を以下に示します(予め、クロスツールqemu-bishopがインストールされている事が前提です)。

  1. share/configs/Makefile.inc の MONADIR と PREFIX を環境に合わせて修正
  2. トップディレクトリでmake
  3. bin ディレクトリの runqemuスクリプトのBishopエミュレータのパスを環境に合わせて修正
  4. 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に合致しないのですが、そのあたりは追々考えていきたいと思います。

ブートローダ

オリジナルのブートシーケンスは、

  1. firstboot(先頭セクタ)
  2. secondboot

と2本立てになっています。これを1本化して、現状、

  1. armboot(MMU-offリセットベクタ)
    • コア初期化
    • 周辺初期化(割り込み、バス、クロック等)*1
    • mona.imgをROMからRAMへ
    • RAM上にjmp

といった手順を踏みます。
本来ならば、オリジナルで行っているサーバプロセスの読み込みが必要ですが、そこまでに至っていません。

Kernel

本体であるKernel.cppにはまだ到達していません。
cstart.cppでメッセージを出して止めています。
なんの面白みもありません。これからこれから。


割り算ルーチンには、ARM向けlibgcc.aをリンクするようにしました。
また同ライブラリ内のdiv0関数がraise関数を呼び出すため、とりあえずダミースタブを作成して対応しました。
(in cstart.c)


近々の課題

  • デバッグメッセージをUART送信バッファへ直接書き出しているのを何とかする(LogConsole移植)
  • MMU-ON
  • 割り込み処理(とりあえずタイマぐらい)
  • とりあえずな編集をきれいにする(特にMakefile)。

*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)を定義
MINGWのldのリンカスクリプト取得(Kernel用)
  • ファイル名はkernel.ldsとする
  • syntax errorになるような行は削除、またはコメントアウト
  • OUTPUT_FORMAT/OUTPUT_ARCH/SEARCH_DIRをGNUのものと同じにする
  • __section_alignment__ = 0x1000; を定義
Makefile.inc

Makefile.incはconfigureスクリプトにて生成されるので一旦、MINGWとしてconfigureスクリプトをトップディレクトリにて実行します。

% ./configure --mingw-prefix=/usr/bin/i586-mingw32msvc-

生成されたMakefile.incを以下のように修正しました。

  • BUILD_TARGET=ELF を追加
  • USER_START_FUNCTION のprefix(_)を削除
    • _user_start → user_start
  • SYMPREFIX を空指定に(_を削除)
  • NFLAGS
    • 出力フォーマットを"win32"から"elf"へ
    • -DBUILD_ON_LINUXを追加
monapi-bin.inc
  • .cppサフィックスルール
    • g++へのオプションに -fno-use-cxa-atexitを追加
  • TARGETルール
    • ldに-Tオプションでmonapi.ldsを指定
      • -T $(SHAREDIR)/configs/monapi.lds

-fno-use-cxa-atexit指定しない場合、__static_initialization_and_destruction_0関数がリンクされて、その関数内で参照している __dso_handletと__cxa_at_exitが無いと文句を言われてしまうためです。
2007-02-03 - memologueにて詳細な解説がなされています。

monapi-elf.inc
  • .cppサフィックスルール
    • g++へのオプションに -fno-use-cxa-atexitを追加
  • TARGETルール
    • ldに-Tオプションでmonapi.ldsを指定
      • -T $(SHAREDIR)/configs/monapi.lds
    • monaelfコマンドラインを削除

core/kernel

Makefile
  • ldに-Tオプションでkernel.ldsを指定
    • -T $(SHAREDIR)/configs/kernel.lds
apm_bios.asm
  • シンボルの _ プレフィックス削除

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 へ

core/test

Makefile
  • $(TARGET).EX5:(INSTFILES) → $(TARGET).ELF:(INSTFILES)
  • ADDLINK/ADDLINKDEP変数を変更
  • include文を monapi-ex5.inc から monapi-elf.inc へ
test.h
  • TEST_SUCCESS マクロでprintfがある方を有効にする
    • 動作確認のため

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を外すとシェルが動くはずです…。


まとめ

GNU/Linux初心者のため、コンパイラオプションの追加等はエラーを消すためにとりあえずなことろや、MonaOSの内部構造を全く知らないで無理やり変更しているので、いろいろ動かしてみると問題ありありだとおもいます。
完全に対応するためにはまだまだ課題が山積みですが、いろいろと勉強になりました。

*1:-nographic指定時、Ctrl-a c でコンソールをQEMUモニタに切り替え可能です

超初歩的な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 は進捗状況出力を抑えます。結果を見やすくするために指定しました)


ポイントは、

という点です。


次に、以下のような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' は更新済みです

理由は、ターゲットを評価する時点で、

  1. ターゲットと同名のファイル名がカレントディレクトリに存在する。
  2. コンポーネントが指定されている場合、タイムスタンプの関係がコンポーネント<ターゲット

この両方が成立すると、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コマンドの前後どちらでも定義可能ですが、以下の同名マクロの優先順位付けにより意味が異なります。

  1. makeで定義された標準のマクロ
  2. 環境変数コマンドラインのmakeより前に定義されたマクロ(上の例ではMACRO1)
  3. Makefile中に定義されたマクロ
  4. コマンドラインのmakeより後に定義されたマクロ(上の例ではMACRO2)

1.の標準で定義されているマクロから、よく使われるものを以下に示します。

マクロ 効能 注意点
$@ ターゲット名を示す 依存関係行では$$@
$? 指定されたコンポーネントのうち、ターゲットより新しいもの全てを示す サフィックスルールでは使用不可
$ ターゲットより後に更新されたコンポーネント サフィックスルールと.DEFAULTエントリのみ使用可
$* $<のサフィックスを除いたもの サフィックスルールのみ使用可

その他、デフォルトで定義されているマクロはオプション-pで確認する事ができます。

まとめ

見よう見まねでなんとなく理解していたつもりだったあったMakefileでしたが、改めていろいろと調べていくうちに新たな発見がありました。
ざっくりとしたまとめすぎて、今回取り上げ切れませんでしたが、他にもmakeには作業を効率化する機能が満載です。
これからは積極的にMakefileを記述していきたいと思います。
また、プログラム生成用途以外の作業をMakefileで記述してみると面白いかもしれません。