基本的なアイデアを決めたので、私はどこから開発を始めるべきか考え始めました。さらに、興味を失うことなく困難に直面しました。 公平に言うと、これは私の最初の試みではないことに注意してください。 たとえば、私が最後にブートローダーを書くことから始めたのは、私の心の単純さです。 リアルモードとプロテクトモードで十分にプレイした後、私はプロトタイプで作業することになりました。 現在の試みは、APIから始める価値があるという意識的な理解から始まりました。このため、セグメント記述子と密接な関係に入る必要はありません。
ネイティブAPI(C / C ++)は、いくつかの理由で適切ではありませんでした。 まず、アドレス空間の分割が必要であり、IPCとカーネルの相互作用に適切なオーバーヘッドが必要です。 現代のトレンドに触発されて、1つのアドレススペースのOSが必要でした。 第二に、ネイティブAPIは、異なるアーキテクチャ間のバイナリコードの互換性を提供しません。 そして最後に、そのようなAPIはリモート呼び出しの透明性を妨げます。 そのため、仮想マシンが必要でした。 彼女と一緒に始めることにしました。
以下は、自分自身を描いた基本的な要件です。
1.メモリ保護(1つのモジュールのコードが別のモジュールのメモリを破壊することはできません);
2.通話の透過性の直接サポート。
3. Occamのカミソリ(KISS)を使用します。
4.効率;
したがって、クラス、マークアンドスイープGCなどの「脂肪」の抽象化を放棄する必要があります(最後の2つのポイント)。 さらに、仮想マシンは64ビットの論理演算と算術演算のみをサポートする必要があります(3番目のポイント)。 実際、システムが完全に仮想アドレス空間に配置されるという事実を考えると、32ビットに焦点を当てるのは意味がありません(今日の4ギガバイトは、スマートフォンでも十分ではありません)。 さらに、仮想マシンのアーキテクチャは、スタックではなく登録する必要があります(最後の段落)。
変数
1.スレッドがプログラムを実行すると、変数を読み取って変更します。
2.変数は、同じサイズの要素の配列として編成された連続したメモリ領域です。
3.変数のタイプは、変数要素の構造、および変数記述子-要素とフラグ(追加のプロパティ)の数を決定します。
4.変数のタイプ。次のフィールドが含まれます。
a。 バイト-算術および論理演算に使用可能な要素のバイト数。
b。 vrefs-変数記述子の配列。 他の変数への要素参照に対応します。
c。 prefs-手続き型識別子の配列。 プロシージャへの要素参照に対応します。
要素の構造と変数内の要素の数を異なるエンティティ(変数の型と記述子)に分割する理由は明らかではありません。 実際には、変数の型はさまざまな操作で使用されるより普遍的な概念であり、要素の数は特定の変数にのみ関連付けられています。
C ++では、次の変数タイプ定義(vm / vmdefs.h)があります。
typedef uint32_t VarTypeId ;
typedef uint32_t ProcTypeId ;
struct VarSpec {
uint32_tフラグ;
VarTypeId vtype ;
size_tカウント。
} ;
struct VarType {
size_tバイト。
std :: vector < VarSpec > vrefs ;
std :: vector < ProcTypeId > prefs ;
} ;
上記の定義からわかるように、変数への参照も要素の数を決定します。 実際、要素の数が可変の変数がありますが、現時点では省略されています。 手順への参照についても(今は省略します)。 実際、変数は構造体の配列の類似物であり、各構造体には単純なフィールド、他の変数へのポインター、および他の関数へのポインターがあることに注意してください。 唯一の違いは、ここでは構造体フィールドが意味的に明確に3つのクラスに分割されていることです。 各クラスには異なる指示があります。 さらに、簡単にするために、各クラスのフィールドは、メモリの連続したセクション(バイトの配列、変数参照の配列、プロシージャ参照の配列)にグループ化されます。 変数のタイプは、作成中にモジュールの本体で静的に設定され、変更できません。
登録
仮想マシンの命令は、変数を直接操作しません。 レジスタを使用してこれを行います。 レジスタ-コードの実行中に変数に関連付けられる番号。これにより、命令を介してコードを読み取ったり変更したりできます。 ケースは変数記述子です。 変数タイプのようなレジスターは、作成時にモジュール本体に静的に設定され、変更できません。
レジスタは、PUSH、PUSHR、POP命令を使用して変数から「バインド」および「アンティ」されます。 PUSHおよびPUSHR命令は、引数としてレジスタを取ります。 最初はスタックに新しい変数を割り当てて、このレジスタに関連付けます。 2番目は、スタック上の変数へのリンクを割り当て、それをこのレジスタに関連付けます。 2番目のケースでは、特別な命令を使用して変数がヒープに割り当てられるまで、レジスタを読み取り/変更に使用できません(ヒープに割り当てられた変数の機能の詳細な説明は省略します)。 POP命令は、最後のPUSHまたはPUSHRをキャンセルします。 これにより、変数(または変数への参照)が削除され、対応するレジスタが変数の以前の割り当てに戻ります。
結論として、階乗を計算する1つの外部プロシージャ(関数)でモジュールを作成するコードを提供します。 詳細については次の投稿まで議論しますが、今のところは感想を述べます(vm / test / modules.cpp):
void createFactorialModule ( Module & module ) {
ModuleBuilderビルダー;
// void fact(unsigned int * io){
// if(* io)
// go l1;
// * io = 1;
//リターン;
// l1:
// {
// unsigned int pr = 1;
// l2:
// pr = * io * pr;
// if(-* io)
// goto l2;
// * io = pr;
//}
//}
VarTypeId vtype =ビルダー。 addVarType ( 8 ) ;
RegId io =ビルダー。 addReg ( 0 、vtype ) ;
ProcTypeId ptype = builder。 addProcType ( 0 、io ) ;
ProcId proc =ビルダー。 addProc ( PFLAG_EXTERNAL、ptype ) ;
ビルダー。 addProcInstr ( proc、JNZInstr ( io、 3 ) ) ;
ビルダー。 addProcInstr ( proc、CPI8Instr ( 1 、io ) ) ;
ビルダー。 addProcInstr ( proc、RETInstr ( ) ) ;
RegId pr =ビルダー。 addReg ( 0 、vtype ) ;
ビルダー。 addProcInstr ( proc、PUSHInstr ( pr ) ) ;
ビルダー。 addProcInstr ( proc、CPI8Instr ( 1 、pr ) ) ;
ビルダー。 addProcInstr ( proc、MULInstr ( io、pr、pr ) ) ;
ビルダー。 addProcInstr ( proc、DECInstr ( io ) ) ;
ビルダー。 addProcInstr ( proc、JNZInstr ( io、 -2 ) ) ;
ビルダー。 addProcInstr ( proc、CPBInstr ( pr、io ) ) ;
ビルダー。 addProcInstr ( proc、POPInstr ( ) ) ;
ビルダー。 addProcInstr ( proc、RETInstr ( ) ) ;
ビルダー。 createModule (モジュール) ;
}