Last Updated 2007/07/27
Programming Tips Windows  索 引 
物理アドレス使用
2005/08/30

物理アドレスで指定されたメモリにアクセスしたいが,以下のコードだと保護違反になる.

    BYTE *p = 物理アドレス値;
    x = *p;


Windows は仮想メモリの元で動いているのでプログラムが使用するアドレス値は物理アドレスではなく仮想アドレス値でなければならない.

プログラムが仮想アドレス値を指定すると CPU や OS が変換テーブルを元にこの仮想アドレス値から実際の物理アドレス値を計算する.
(普通のアプリだけではなくドライバも同様)

物理アドレスを指定してアクセスするには,CPU や OS が使用するアドレス変換テーブル内に自分がアクセスしたい物理アドレスを指すエントリを作成し,そのエントリを指す仮想アドレスを取得しなければならない.
これをメモリ・マッピングという.
上記プログラムが保護違反になるのは BYTE *p に入れたアドレスが仮想アドレス値として処理され有効な変換テーブルに辿りつけなかったためと推測される.

解決策は以下のとおり.


1.物理ドライバを作成する
もっとも安全で Microsoft が保証する手段です。
Windows はシステムレベルの物理ドライバに対して,物理アドレス→仮想アドレスの変換サービス (マッピングサービス) を提供している.
物理ドライバは自分がアクセスしたい物理アドレスをこのサービスに渡して,そこを指す仮想アドレス値を受け取る.メモリのアクセスには返された仮想アドレス値を使用する.

例 1-1: Win95 VxD の例 その1
_MapPhysToLinear を用います。

例 2-2: Win95 VxD の例 その2
/* 物理アドレスのマッピングを行う例 */
リニアアドレス = _PageReserve(ナンタラ, ページ数、PG_FIXED);
_PageCommitPhys(リニアアドレスのページ番号, ページ数, 物理アドレスのページ番号, PC_INCR|PC_WRITABLE);
_LinPageLock(リニアアドレスのページ番号, ページ数, 0);
/* 物理アドレスのマッピングを破棄する例 */
_LinPageUnlock(リニアアドレスのページ番号, ページ数, 0);
_PageFree(リニアアドレス);

[注]
ページ番号 = 32bitアドレス / 4096 又は 32bitアドレス >> 12;
ページ数 = (バイト数 / 4096) + 1;


注: ここに記したのはドライバ用サービスであり、アプリからは呼べません。


2. WinRT を使う
WinRT とは汎用 VxD (汎用物理ドライバ) で,アプリから I/O や物理アドレス変換要求を出すと,それを行ってくれる物理ドライバである.これを用いる場合は,WinRT VxD を呼び出すアプリを書くだけで良い.


3. DPMI を使う (Win3.1)
物理ドライバを書くスキルはなく,WinRT を買うお金もないという人は,Win3.1 アプリから DPMI を発行するという方法もある.
DPMI 機能番号 800h 「物理アドレスをリニアアドレス空間にマップ」を呼び出して,物理アドレスをリニアアドレス空間にマップする.
      /* 物理アドレスをリニアアドレス空間にマップ */
      ax ← 0x0800  ;DPMI function 800h
      bx:cx ← 物理アドレス
      si:di ← サイズ
      int  31h
      リニアアドレス値 ← bx:cx
16bit アプリはこれでもらったリニアアドレスを直接扱う事ができないので,新たなディスクリプタを割り当て,そのディスクリプタがこのリニアアドレスを指す様にする.
Win3.1 API にこれを行うものがあります。
      新しいセレクタ値 = AllocSelector(自分の DS レジスタの値);
      SetSelectorBase(新しいセレクタ値、返されたリニアアドレス値);
      SetSelectorLimit(新しいセレクタ値, サイズ - 1);
      Win3.1 用 FAR ポインタ = セレクタ値 << 16 + 0;

(注)
この DPMI 呼び出しで返されたアドレスを使っても H/W frame buffer を見れないというケースもある様です。その場合は、PC/AT 互換機 + MS Win3.1 (又は IBM 版) で動作するかどうか確認してみるのもよいでしょう。


4. DPMI を使う (Win95)
Win3.1 でなく Win32 アプリから行うという場合,汎用的な手段はない.
Win32 アプリはプラットフォーム非依存となる様に Microsoft がこの様なサービスをアプリに提供していないからである.
Win95 依存な方法は無い事もない.
Win95 の非公開機能を用いて DPMI 800h を呼び出し,返されたリニアアドレス値をそのまま 32bit アドレスとして用いる事ができない事もない.
しかしこれを行うには,ビルド方法も含めて多くの裏技が必要である.
(しかもこの方法が将来も機能するという保証もない)


参照
VxDのCreateFile
Windows95でのセクタ入出力(FDD/HDD)
前後のTips
物理アドレス使用

DSS ProgrammingTipsCGI Ver2.02