Plugs紹介----Net/C#オープンソースオペレーティングシステム学習シリーズ6
44091 ワード
原文住所:http://www.codeproject.com/KB/cs/CosmosPlugs.aspx
紹介する
この文章はCOSMOSの中でどのようにWINDOWS API呼び出しと内部呼び出しを実現するかを示す.Netコード.また、COSMOS、アセンブリ、またはX#言語を使用してハードウェアと直接対話する方法も含まれています.
コスモスって何?
Cosmosは、開発環境としてVisual Studioを使用するオペレーティングシステム開発ツール(development kit)です.それでも、いかなる根拠も.Netの言語、VBを含みます.NET、Fortran、Delphi Prism、IronPython、F#などが開発に利用できます.Cosmos本省もカーネル運転もC#で書かれているのでCosmosと名付けられました.しかも、NOSMOS(.NET Open Source Management Operating System)はバカに聞こえる.
Cosmosは伝統的なオペレーティングシステムではなく、オペレーティングシステムツール(Operating System Kit)と呼ぶべきか、私が言ったように「Operating System Legos」と呼ぶべきか(どのように翻訳するか分からないが、へへへ).Cosmosでは、Visual StudioとC#を使用してアプリケーションを作成するようにオペレーティングシステムを作成できます.ほとんどのユーザーは、数分以内に独自のオペレーティングシステムを自分で書き込み、起動することができます.これらはすべてVisual Studioで完了できます.Cosmosは、Visual Studioと統合されたプロジェクトタイプ、デバッガ、ブレークポイントツール、およびそれを観察する(watchers)などを提供します.C#またはVBをデバッグできます.NETアプリケーションのようにオペレーティングシステムをデバッグします.
どんな場合にPlugsが必要か
以下の3つのシーンでPlugsを使用する必要があります.
1.内部コール(Internal Calls)
2. P/Invoke
3.ダイレクトアセンブリ
内部呼び出しとP/Invokeの場合
はい.Netフレームワークが提供する各種クラスのうち、一部は使用しない.Netコードで実現されるのではなく、使用されるローカルコードです.このような理由は2つあります.
1.実現される方法はWindows APIに依存する(P/Invokeの場合)
2.実現方法は高度に最適化されたC++に依存するか、または.Net実行時のアセンブリコード(内部呼び出しの場合)
P/Invokeは、既存のWindow encryption APIにアクセスし、ネットワークにアクセスし、他の類似の機能にアクセスするためにスクリーンに図を描くために使用されます.
内部呼び出しには直接アクセスが必要である.Net実行時のクラスで使用します.たとえば、メモリ管理のクラスに直接アクセスしたり、速度が要求されたりします.MathのようにPowメソッドは内部呼び出しを使用します.
Plugsは、C#、アセンブリ、または任意のものを用いることができる.Net言語で実現します.
ダイレクトアセンブリの場合
ハードウェアと直接会話する場合、CosmosはPICバス、CPU IOバス、メモリなどと対話できる必要があります.アクセスメモリでは、非タイプのセキュリティポインタ(unsafe pointers)がよく使用されますが、場合によっては自分でアセンブリコードを手書きで作成する必要があります.Plugsはインタフェースに相当し、C#へのアセンブリコードへの直接アクセスを提供し、アセンブリ呼び出しへのアクセスをC#コードのメソッド呼び出しのようにする.
CosmosにX 86アセンブリを書く
クラスをCosmosで使用してX 86アセンブリを実装できます
しかし、Cosmosはより高いレベルの呼び出しX#もサポートしています.X#は、X 86アセンブリに直接対応する安全なアセンブリ言語です.X#は以下の通りです.
Plugsの実装を開始
まず私たちのplugが何をするかを決めなければなりません.例えば、Math.Abs(double)は内部呼び出しを実現するために使用される
この方法を直接使用してCosmosに対応するplugがない場合、コンパイラは「plug needed」エラーを発生します.IL 2 CPUではILコードがないのでX 86コードにコンパイルします.したがって、「plug needed」エラーは、内部呼び出しまたはP/Invokeに依存する方法が必要であることを意味します.そうしないと、Cosmosはコンパイルできません.
MathでPowのこの例では、Cosmosのカーネルには、コンパイル時に自動的に呼び出されるplugが含まれているため、コンパイル可能である.
コンパイラは、実行時にplugを使用して実際のコードを置き換えます.Plugで提供されるコードは、CosmosがWindowsまたはCLRの下で実行されていないため、Cosmosで直接使用できない内部呼び出しおよびWINDOWS APIの呼び出しを置き換えるために使用される.Plugはこのような強制埋め込みと置換のフォーマットである(It’s a form of forced inlining and replacement)
Plugを作成するには、新しいクラスを作成する必要があります.カーネル内のPlugは個々の個別のプログラムセット(assemblies)として作成され、カーネルによって個別に参照される.これにより、IL 2 CPUはplugを含み、使用することができる.
ここでは1つの方法を示したが,実際にはPlugクラスには複数の方法が含まれる.この例ではPlugの属性(attribute)が重要な要素である.IL 2 CPUというplugはSystemを置き換えるために使われていると伝えています.Mathクラスのメソッド.そしてIL 2 CPUはSystemと探しに行きます.Mathのメソッドに対応するメソッドを交換します.
ダイレクトアセンブリplug
ダイレクトアセンブリのplugは、C#ダイレクトとX 86アセンブリのインタラクションを実行するために使用されるコードである.例えば、IOPortクラスは、デバイスドライバがハードウェアデバイスと通信する必要があるときにCPUバスに直接アクセスすることを可能にする.
まず、空のC#クラスを作成し、置き換えられる空のメソッドを作成します.この置換メソッドの戻りタイプがVOIDでない場合、plugのメソッドは、C#コンパイラがコンパイルできるように任意に値を返す必要があります.それでも、plugは置換されたターゲットメソッドを無視し、plugで実装されたメソッドで元のメソッドを置換するため、このメソッドの戻り値は使用されません.
ご覧のように「Write」メソッドは空ですが、「Read」メソッドには名目上の戻り値が必要です.
このクラスはコードで置き換えられます.
注意:この例のコード(アセンブリのように見えるコードを指す-訳者注)はX#コードではありません.私たちの古いplugは古い文法を使って書いています.
C#を使用してIOPortというクラスに直接アクセスできるplugがあります.次の例はATAクラスから抜粋します
その他のplugの例
BCL(Binary Classes Library?.netのフレームワークで提供されるクラス)では、Consoleクラスで使用される内部呼び出しの一部は通常、WINDOWS APIに使用されます.各呼び出し階層を個別に置き換える必要はありません.WINDOWS API呼び出しに直接マッピングする必要はありません.より高い呼び出し階層ツリーでTextScreenクラスを呼び出すだけで、これらの方法を完全に置き換えることができます.(We don't need to plug only the methods that directly map to Windows API calls, but instead we plug methods much higher up the tree and completely replace the implementation to call our TextScreen class instead.)
(翻訳はこれで終わります.レベルが限られています.ご指導を歓迎します)
紹介する
この文章はCOSMOSの中でどのようにWINDOWS API呼び出しと内部呼び出しを実現するかを示す.Netコード.また、COSMOS、アセンブリ、またはX#言語を使用してハードウェアと直接対話する方法も含まれています.
コスモスって何?
Cosmosは、開発環境としてVisual Studioを使用するオペレーティングシステム開発ツール(development kit)です.それでも、いかなる根拠も.Netの言語、VBを含みます.NET、Fortran、Delphi Prism、IronPython、F#などが開発に利用できます.Cosmos本省もカーネル運転もC#で書かれているのでCosmosと名付けられました.しかも、NOSMOS(.NET Open Source Management Operating System)はバカに聞こえる.
Cosmosは伝統的なオペレーティングシステムではなく、オペレーティングシステムツール(Operating System Kit)と呼ぶべきか、私が言ったように「Operating System Legos」と呼ぶべきか(どのように翻訳するか分からないが、へへへ).Cosmosでは、Visual StudioとC#を使用してアプリケーションを作成するようにオペレーティングシステムを作成できます.ほとんどのユーザーは、数分以内に独自のオペレーティングシステムを自分で書き込み、起動することができます.これらはすべてVisual Studioで完了できます.Cosmosは、Visual Studioと統合されたプロジェクトタイプ、デバッガ、ブレークポイントツール、およびそれを観察する(watchers)などを提供します.C#またはVBをデバッグできます.NETアプリケーションのようにオペレーティングシステムをデバッグします.
どんな場合にPlugsが必要か
以下の3つのシーンでPlugsを使用する必要があります.
1.内部コール(Internal Calls)
2. P/Invoke
3.ダイレクトアセンブリ
内部呼び出しとP/Invokeの場合
はい.Netフレームワークが提供する各種クラスのうち、一部は使用しない.Netコードで実現されるのではなく、使用されるローカルコードです.このような理由は2つあります.
1.実現される方法はWindows APIに依存する(P/Invokeの場合)
2.実現方法は高度に最適化されたC++に依存するか、または.Net実行時のアセンブリコード(内部呼び出しの場合)
P/Invokeは、既存のWindow encryption APIにアクセスし、ネットワークにアクセスし、他の類似の機能にアクセスするためにスクリーンに図を描くために使用されます.
内部呼び出しには直接アクセスが必要である.Net実行時のクラスで使用します.たとえば、メモリ管理のクラスに直接アクセスしたり、速度が要求されたりします.MathのようにPowメソッドは内部呼び出しを使用します.
Plugsは、C#、アセンブリ、または任意のものを用いることができる.Net言語で実現します.
ダイレクトアセンブリの場合
ハードウェアと直接会話する場合、CosmosはPICバス、CPU IOバス、メモリなどと対話できる必要があります.アクセスメモリでは、非タイプのセキュリティポインタ(unsafe pointers)がよく使用されますが、場合によっては自分でアセンブリコードを手書きで作成する必要があります.Plugsはインタフェースに相当し、C#へのアセンブリコードへの直接アクセスを提供し、アセンブリ呼び出しへのアクセスをC#コードのメソッド呼び出しのようにする.
CosmosにX 86アセンブリを書く
クラスをCosmosで使用してX 86アセンブリを実装できます
View Code
new
Move(Registers.DX, (xComAddr
+
1
).ToString());
new
Move(Registers.AL,
0
.ToString());
new
Out(
"
dx
"
,
"
al
"
);
//
disable all interrupts
new
Move(Registers.DX, (xComAddr
+
3
).ToString());
new
Move(Registers.AL,
0x80
.ToString());
new
Out(
"
dx
"
,
"
al
"
);
//
Enable DLAB (set baud rate divisor)
new
Move(Registers.DX, (xComAddr
+
0
).ToString());
new
Move(Registers.AL,
0x1
.ToString());
new
Out(
"
dx
"
,
"
al
"
);
//
Set diviso (low byte)
new
Move(Registers.DX, (xComAddr
+
1
).ToString());
new
Move(Registers.AL,
0x00
.ToString());
new
Out(
"
dx
"
,
"
al
"
);
//
//
set divisor (high byte)
しかし、Cosmosはより高いレベルの呼び出しX#もサポートしています.X#は、X 86アセンブリに直接対応する安全なアセンブリ言語です.X#は以下の通りです.
UInt16 xComStatusAddr
=
(UInt16)(aComAddr
+
5
);
Label
=
"
WriteByteToComPort
"
;
Label
=
"
WriteByteToComPort_Wait
"
;
DX
=
xComStatusAddr;
AL
=
Port[DX];
AL.Test(
0x20
);
JumpIfEqual(
"
WriteByteToComPort_Wait
"
);
DX
=
aComAddr;
AL
=
Memory[ESP
+
4
];
Port[DX]
=
AL;
Return(
4
);
Label
=
"
DebugWriteEIP
"
;
AL
=
Memory[EBP
+
3
];
EAX.Push();
Call
<
WriteByteToComPort
>
();
AL
=
Memory[EBP
+
2
];
EAX.Push();
Call
<
WriteByteToComPort
>
();
AL
=
Memory[EBP
+
1
];
EAX.Push();
Call
<
WriteByteToComPort
>
();
AL
=
Memory[EBP];
EAX.Push();
Call
<
WriteByteToComPort
>
();
Return();
Plugsの実装を開始
まず私たちのplugが何をするかを決めなければなりません.例えば、Math.Abs(double)は内部呼び出しを実現するために使用される
.method
public
hidebysig
static
float64 Abs(float64
'
value
'
) cil managed internalcall
{
.custom instance
void
System.Security.SecuritySafeCriticalAttribute::.ctor()
}
この方法を直接使用してCosmosに対応するplugがない場合、コンパイラは「plug needed」エラーを発生します.IL 2 CPUではILコードがないのでX 86コードにコンパイルします.したがって、「plug needed」エラーは、内部呼び出しまたはP/Invokeに依存する方法が必要であることを意味します.そうしないと、Cosmosはコンパイルできません.
MathでPowのこの例では、Cosmosのカーネルには、コンパイル時に自動的に呼び出されるplugが含まれているため、コンパイル可能である.
コンパイラは、実行時にplugを使用して実際のコードを置き換えます.Plugで提供されるコードは、CosmosがWindowsまたはCLRの下で実行されていないため、Cosmosで直接使用できない内部呼び出しおよびWINDOWS APIの呼び出しを置き換えるために使用される.Plugはこのような強制埋め込みと置換のフォーマットである(It’s a form of forced inlining and replacement)
Plugを作成するには、新しいクラスを作成する必要があります.カーネル内のPlugは個々の個別のプログラムセット(assemblies)として作成され、カーネルによって個別に参照される.これにより、IL 2 CPUはplugを含み、使用することができる.
[Plug(Target
=
typeof
(
global
::System.Math))]
public
class
MathImpl {
public
static
double
Abs(
double
value) {
if
(value
<
0
) {
return
-
value;
}
else
{
return
value;
}
}
ここでは1つの方法を示したが,実際にはPlugクラスには複数の方法が含まれる.この例ではPlugの属性(attribute)が重要な要素である.IL 2 CPUというplugはSystemを置き換えるために使われていると伝えています.Mathクラスのメソッド.そしてIL 2 CPUはSystemと探しに行きます.Mathのメソッドに対応するメソッドを交換します.
ダイレクトアセンブリplug
ダイレクトアセンブリのplugは、C#ダイレクトとX 86アセンブリのインタラクションを実行するために使用されるコードである.例えば、IOPortクラスは、デバイスドライバがハードウェアデバイスと通信する必要があるときにCPUバスに直接アクセスすることを可能にする.
まず、空のC#クラスを作成し、置き換えられる空のメソッドを作成します.この置換メソッドの戻りタイプがVOIDでない場合、plugのメソッドは、C#コンパイラがコンパイルできるように任意に値を返す必要があります.それでも、plugは置換されたターゲットメソッドを無視し、plugで実装されたメソッドで元のメソッドを置換するため、このメソッドの戻り値は使用されません.
public
abstract
class
IOPortBase {
public
readonly
UInt16 Port;
//
all ctors are internal - Only Core ring can create it.. but hardware ring can use it.
internal
IOPortBase(UInt16 aPort)
{
Port
=
aPort;
}
internal
IOPortBase(UInt16 aBase, UInt16 aOffset)
{
//
C# math promotes things to integers, so we have this constructor
//
to relieve the use from having to do so many casts
Port
=
(UInt16)(aBase
+
aOffset);
}
//
TODO: Reads and writes can use this to get port instead of argument
static
protected
void
Write8(UInt16 aPort,
byte
aData) { }
//
Plugged
static
protected
void
Write16(UInt16 aPort, UInt16 aData) { }
//
Plugged
static
protected
void
Write32(UInt16 aPort, UInt32 aData) { }
//
Plugged
static
protected
byte
Read8(UInt16 aPort) {
return
0
; }
//
Plugged
static
protected
UInt16 Read16(UInt16 aPort) {
return
0
; }
//
Plugged
static
protected
UInt32 Read32(UInt16 aPort) {
return
0
; }
//
Plugged
ご覧のように「Write」メソッドは空ですが、「Read」メソッドには名目上の戻り値が必要です.
このクラスはコードで置き換えられます.
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
Cosmos.IL2CPU.Plugs;
using
Assembler
=
Cosmos.Compiler.Assembler.Assembler;
using
CPUx86
=
Cosmos.Compiler.Assembler.X86;
namespace
Cosmos.Core.Plugs
{
[Plug(Target
=
typeof
(Cosmos.Core.IOPortBase))]
public
class
IOPortImpl
{
[Inline]
public
static
void
Write8(UInt16 aPort,
byte
aData)
{
//
TODO: This is a lot of work to write to a single port.
//
We need to have some kind of inline ASM option that can
//
emit a single out instruction
new
CPUx86.Move { DestinationReg
=
CPUx86.Registers.EDX, SourceReg
=
CPUx86.Registers.EBP, SourceDisplacement
=
0x0C
, SourceIsIndirect
=
true
};
new
CPUx86.Move { DestinationReg
=
CPUx86.Registers.EAX, SourceReg
=
CPUx86.Registers.EBP, SourceDisplacement
=
0x08
, SourceIsIndirect
=
true
};
new
CPUx86.Out { DestinationReg
=
CPUx86.Registers.AL };
}
[Inline]
public
static
void
Write16(UInt16 aPort, UInt16 aData)
{
new
CPUx86.Move { DestinationReg
=
CPUx86.Registers.EDX, SourceReg
=
CPUx86.Registers.EBP, SourceIsIndirect
=
true
, SourceDisplacement
=
0x0C
};
new
CPUx86.Move { DestinationReg
=
CPUx86.Registers.EAX, SourceReg
=
CPUx86.Registers.EBP, SourceIsIndirect
=
true
, SourceDisplacement
=
0x08
};
new
CPUx86.Out { DestinationReg
=
CPUx86.Registers.AX };
}
[Inline]
public
static
void
Write32(UInt16 aPort, UInt32 aData)
{
new
CPUx86.Move { DestinationReg
=
CPUx86.Registers.EDX, SourceReg
=
CPUx86.Registers.EBP, SourceIsIndirect
=
true
, SourceDisplacement
=
0x0C
};
new
CPUx86.Move { DestinationReg
=
CPUx86.Registers.EAX, SourceReg
=
CPUx86.Registers.EBP, SourceIsIndirect
=
true
, SourceDisplacement
=
0x08
};
new
CPUx86.Out { DestinationReg
=
CPUx86.Registers.EAX };
}
[Inline]
public
static
byte
Read8(UInt16 aPort)
{
new
CPUx86.Move { DestinationReg
=
CPUx86.Registers.EDX, SourceReg
=
CPUx86.Registers.EBP, SourceIsIndirect
=
true
, SourceDisplacement
=
0x08
};
//
TODO: Do we need to clear rest of EAX first?
//
MTW: technically not, as in other places, it _should_ be working with AL too..
new
CPUx86.Move { DestinationReg
=
CPUx86.Registers.EAX, SourceValue
=
0
};
new
CPUx86.In { DestinationReg
=
CPUx86.Registers.AL };
new
CPUx86.Push { DestinationReg
=
CPUx86.Registers.EAX };
return
0
;
}
[Inline]
public
static
UInt16 Read16(UInt16 aPort)
{
new
CPUx86.Move { DestinationReg
=
CPUx86.Registers.EDX, SourceReg
=
CPUx86.Registers.EBP, SourceIsIndirect
=
true
, SourceDisplacement
=
0x08
};
new
CPUx86.Move { DestinationReg
=
CPUx86.Registers.EAX, SourceValue
=
0
};
new
CPUx86.In { DestinationReg
=
CPUx86.Registers.AX };
new
CPUx86.Push { DestinationReg
=
CPUx86.Registers.EAX };
return
0
;
}
[Inline]
public
static
UInt32 Read32(UInt16 aPort)
{
new
CPUx86.Move { DestinationReg
=
CPUx86.Registers.EDX, SourceReg
=
CPUx86.Registers.EBP, SourceIsIndirect
=
true
, SourceDisplacement
=
0x08
};
new
CPUx86.In { DestinationReg
=
CPUx86.Registers.EAX };
new
CPUx86.Push { DestinationReg
=
CPUx86.Registers.EAX };
return
0
;
}
}
}
注意:この例のコード(アセンブリのように見えるコードを指す-訳者注)はX#コードではありません.私たちの古いplugは古い文法を使って書いています.
C#を使用してIOPortというクラスに直接アクセスできるplugがあります.次の例はATAクラスから抜粋します
public
override
void
ReadBlock(UInt64 aBlockNo, UInt32 aBlockCount,
byte
[] aData) {
CheckDataSize(aData, aBlockCount);
SelectSector(aBlockNo, aBlockCount);
SendCmd(Cmd.ReadPio);
IO.Data.Read8(aData);
}
その他のplugの例
BCL(Binary Classes Library?.netのフレームワークで提供されるクラス)では、Consoleクラスで使用される内部呼び出しの一部は通常、WINDOWS APIに使用されます.各呼び出し階層を個別に置き換える必要はありません.WINDOWS API呼び出しに直接マッピングする必要はありません.より高い呼び出し階層ツリーでTextScreenクラスを呼び出すだけで、これらの方法を完全に置き換えることができます.(We don't need to plug only the methods that directly map to Windows API calls, but instead we plug methods much higher up the tree and completely replace the implementation to call our TextScreen class instead.)
namespace
Cosmos.System.Plugs.System.System {
[Plug(Target
=
typeof
(
global
::System.Console))]
public
static
class
ConsoleImpl {
private
static
ConsoleColor mForeground
=
ConsoleColor.White;
private
static
ConsoleColor mBackground
=
ConsoleColor.Black;
public
static
ConsoleColor get_BackgroundColor() {
return
mBackground;
}
public
static
void
set_BackgroundColor(ConsoleColor value) {
mBackground
=
value;
Cosmos.Hardware.Global.TextScreen.SetColors(mForeground, mBackground);
}
(翻訳はこれで終わります.レベルが限られています.ご指導を歓迎します)