
最近、レイモンドチェンは1年半前に始まった一連の投稿を完了し、プロセッサからのサポートなしで仮想メモリの管理に専念しました。 (システムバスで発行される)は、セグメントとオフセットの巧妙な追加によって実行されます-「アクセス制御」、「無効なアドレス」はありません。 すべてのアドレスは誰でも利用できます。 同時に、複数のプログラムがWindowsで同時に動作し、互いに干渉することはありません。 Windowsはメモリ内のセグメントを移動し、未使用のセグメントをアンロードし、必要に応じて、場合によっては他のアドレスにロードし直すことができます。
(興味深いことに、これらの並外れた能力を知っているホリボルスキキは、「オペレーティングシステムではなく、グラフィカルシェルでした」という既存のホリボルシキ?)
そして、彼女はどのように管理しましたか?
データ管理

リアルタイムWindowsにはスワップはありませんでした。 不変データ(リソースなど)はメモリから削除され、必要に応じて実行可能ファイルから再度読み込まれました。 可変データはアンロードできませんでしたが、(他のデータと同様に)移動できました。メモリブロックを操作するアプリケーションはアドレスを使用せず、ハンドルを使用します。 また、データにアクセスするときは、ブロックを「修正」してアドレスを取得し、必要に応じてWindowsが移動できるように「解放」します。 十数年後に.NETに似たものが登場し、すでにピン止めと呼ばれていました。
関数
GlobalLock /
GlobalUnlockおよび
LockResource /
FreeResourceは、メモリブロック(リソースを含む)がWin32APIで移動することはありませんでしたが、これらの古代との互換性のために
FreeResourceに保持されました。
LockSegmentおよび
UnlockSegment (ハンドルではなく、アドレスでメモリを
UnlockSegment /解放する)は、「廃止、使用しない」とマークされたドキュメントにしばらく残っていましたが、メモリが残っていません。
長期間メモリを修正する必要がある人のために、
GlobalWire機能もありました-「ブロックがアドレス空間の中央に
GlobalWireないように、メモリの下端に移動して修正します」。
GlobalUnwireと一致し、
GlobalUnwireと完全に同等です。 このペアの関数は、驚いたことに、kernel32.dllでまだ機能していますが、ドキュメントからは既に削除されています。 現在は、
GlobalLock /
GlobalUnlockです。

保護モードのWindowsでは、
GlobalLock機能
GlobalLock 「スタブ」
GlobalLock置き換えられました。Windowsは、アプリケーションに表示される「仮想アドレス」を変更せずにメモリブロックをシャッフルできるようになりました(セレクター:オフセット)-これは、アプリケーションがアップロードできないオブジェクトを修正する必要がなくなったことを意味します。 言い換えれば、ピン留めはブロックのアンロードを防止しますが、(アプリケーションには見えない)ブロックの移動は防止しません。 したがって、物理メモリ内のデータを「実際に」修正するために、それだけが必要な人(たとえば、外部デバイスで作業するため)に、
GlobalFix /
GlobalUnfixペアを追加しました。
GlobalWire /
GlobalUnwireと同様に、Win32ではこれらの関数は役に立たなくなりました。 また、同じようにドキュメントから削除されますが、kernel32.dllに残り、
GlobalLock /
GlobalUnlock GlobalLock GlobalUnlock 。
コード管理
最も難しいのはここから始まります。 コードのブロック-不変データ-はメモリから削除され、実行可能ファイルからロードされました。 しかし、Windowsは、プログラムがアンロードされたブロック内の関数を呼び出そうとしないことをどのように確認しましたか? ハンドルを使用して関数にアクセスし、各関数呼び出しの前に仮想の
LockFunction呼び出すことが
LockFunctionます。 ただし、ウィンドウの表示やDDEコマンドの実行など、多くの関数が「メッセージループ」をひねり、この時間にアンロードすることもできます。 実際、現時点ではそれらのコードは必要ありません。 ただし、「関数ハンドル」を使用する場合、関数セグメントは、呼び出し元の関数に制御を戻すまで解放されません。

代わりに、Windowsは、現在実行されていない関数をアンロードできると想定することから始めます。 Windowsメモリマネージャーのコードが現在実行されているため、
すべての関数
をアンロード
できます。 この関数がアンロードする前に戻らなかった場合、リンクはプログラムコードまたはスタックのいずれかに残ることができます。
そのため、Windowsは実行中のすべての
タスクのスタック(プロセスとスレッドを分離するまでWindowsのいわゆる実行コンテキスト)を調べ、アンロードされたセグメント内の先頭のリターンアドレスを見つけ、
リロードサンクのアドレスで置き換えます。何も起こらなかったかのように、実行可能ファイルから、制御をその中に転送します。
Windowsがスタック上を歩くことができるように、プログラムは
正しい形式でそれをサポートする必要があります。FPOなし、スタックフレームは呼び出し関数のフレームへの
BPポインターで始まる必要があります。 (スタックは16ビットワードで構成されるため、
BP値は常に偶数です。)さらに、Windowsはスタック内のセグメント内(「閉じる」)呼び出しとセグメント間(「遠」)呼び出しを区別する必要があります。それらは、アンロードされたセグメントに正確にはつながりません。 したがって、彼らは、スタック内の
BPの奇数の値は遠方の呼び出し、つまり すべての遠隔機能は、
INC BP; PUSH BP; MOV BP,SPプロローグで開始する必要があります
INC BP; PUSH BP; MOV BP,SP INC BP; PUSH BP; MOV BP,SP INC BP; PUSH BP; MOV BP,SP 、およびエピローグ
POP BP; DEC BP; RETF終わる
POP BP; DEC BP; RETF POP BP; DEC BP; RETF POP BP; DEC BP; RETF (実際には、プロローグとエピローグは
より複雑でしたが、これは今ではそうではありません。)

スタックからリンクを見つけましたが
、他のコードセグメントからのリンクはどうですか? もちろん、Windowsはメモリ全体を調べて、アンロードされた関数へのすべての呼び出しを見つけ、それらすべてをリロードサンクに置き換えることはできません。 代わりに、セグメント間呼び出し
は 、呼び出された関数がメモリ内にない可能性があるという事実を考慮して
コンパイルされ、実際に
はモジュール
入力テーブルの 「スタブ」を呼び出し
ます 。 このスタブは、
int 3fh命令と、関数を探す場所を示す3バイトのサービスバイトで構成されます。
int 3fhは、リターンアドレスでこれらのサービスバイトを見つけます。 目的のセグメントを定義します。 まだロードされていない場合は、メモリにロードします。 そして最後に、入力テーブルのスタブを関数本体への絶対遷移
jmp xxxx:yyyy上書きします。これにより、同じ関数への次の呼び出しは、中断することなく1つのセグメント間遷移だけで遅くなります。
これで、Windowsが関数をアンロードするときに、モジュール入力テーブル内の関数が挿入された遷移を
int 3fh戻すだけで十分です。 システムは、アンロードされた関数へのすべての呼び出しを検索する必要はありません-それらはすべてコンパイル時でも見つかりました! モジュールの「
WinMainテーブル」には、セグメント間呼び出しの存在についてコンパイラが知っているすべての遠隔関数(特に、エクスポートされた関数と
WinMain含まれます)、およびポインターによってどこかに渡されたすべての遠隔関数が含まれます。プログラムコードの外部からも、どこからでも呼び出されます(これには、
WndProc 、
EnumFontFamProc 、およびその他のコールバック関数が含まれます)。

離れた関数へのポインタの代わりに、スタブへのポインタがどこにでも渡されます。 つまり、
GetWindowLong(GWL_WNDPROC)および同様の呼び出しから取得したアドレスも、関数の本体ではなくスタブを指しているということです。
GetProcAddressでも
GetProcAddress関数のアドレスの代わりに、DLLエントリテーブルのスタブのアドレスを返します。 (Win32では、DLLの「入力テーブル」の類似物は、「エクスポートテーブル」という名前で残りました。)静的モジュール間呼び出し(DLLからインポートされた関数の呼び出し)は、同じ
GetProcAddressを使用して解決します
GetProcAddress 。 いずれにせよ、関数をアンロードするときに、スタブを修正するだけで十分であり、呼び出し元のコード自体に触れる必要はありません。
再配置可能なコードセグメントに関するこのすべての知恵は、DOSのオーバーレイリンカーから「継承によって」Windowsにもたらされました。 同様に、最初はスキーム全体-
まさにこの形式で -がZortech Cコンパイラに登場し、次にMicrosoft Cに登場しました。Windowsの実行可能ファイル形式が作成されたとき、DOSの既存のオーバーレイ形式が基礎として採用されました。

しかし、Windowsはアンロードするセグメントをどのように選択しますか? ランダムに選択するのは危険です。実行されたばかりのコードをすぐにダウンロードする必要があります。 そのため、Windowsはコードセグメントに
「アクセスビット」のようなものを使用します。関数へのすべてのセグメント間呼び出しがスタブを通過することを知って、命令
sar byte ptr cs:[xxx], 1 (
int 3fhまたは
jmp置き換える前)
int 3fh挿入する
int 3fhました
sar byte ptr cs:[xxx], 1 、関数が呼び出されるたびにバイトカウンターを1から0にリセットします。 この命令は5バイトで
int 3fhます。既存の実行可能ファイル形式を保存し、カウンター命令を散在させて、1つを介して
int 3fhをロードできます。
すべてのコードセグメントのカウンター値は1に初期化され、250ミリ秒ごとに、Windowsはすべてのモジュールをバイパスし、更新された値を収集し、LRUリスト内のコードセグメントを並べ替えます。 データセグメントへの呼び出しは、何のトリックもなしで追跡できます。そのような呼び出しはすべて、
GlobalLockまたは同様の関数への明示的な呼び出しによって
GlobalLockマークされて
GlobalLockます。 そのため、メモリを解放するためにセグメントをアンロードするときが来ると-Windowsは、最も長くアクセスされていないセグメントをアンロードしようとします:カウンタが最長時間0にリセットされていないコードセグメント、または最も長く続いていないデータセグメント修正されました。
GUIdebookで撮影されたWindows広告1.0-2.1