Mobileメモ:ラージメモリ領域

Last-modified: 2009-05-09 (土) 14:44:57
Last update: 2009-05-09 (土) 14:44:57

Mobileメモ:ラージメモリ領域


ラージメモリ領域

32MB制限

WindowsMobileでは1つのプロセスの使用できる仮想アドレス空間は、基本的に下位32MB(0x00000000~0x1FFFFFF)に制限されている*1*2

実行コード、ヒープ領域、およびDLLの占有するアドレス空間がこの制限を超えてしまうと、malloc()はNULLを返す。

このため、LoadLibrary()によるDLLのロード等も失敗する。

仮想アドレス空間の制限なので、物理メモリの空きは関係ない。

ラージメモリ領域について

VirtualAlloc()に2MB以上の大きさを指定してアドレス空間の割り当てを要求すると、0x42000000~0x7FFFFFFFまでのラージメモリ領域が割り当てられる。

以下のコードで256MB分の仮想アドレス空間が割り当てられる。

PVOID ptr = VirtualAlloc( (LPVOID)NULL, 256 * 1024 * 1024, MEM_RESERVE, PAGE_NOACCESS );

この仮想アドレス空間では物理メモリを4KB単位でコミットすることができる。

コミットされたメモリはmalloc()で確保したメモリと同様に扱うことができる*3

ラージメモリ領域は全プロセスで共有されるグローバル領域であるため、他プロセスからもアクセスできてしまう点に注意が必要。

VirtualAlloc()の使い方

簡単な例

以下のサンプルコードでは512MBのメモリを確保している。

メモリ確保の回数が少なければこれだけで問題なく使えるはず。

unsigned char* data = (unsigned char*)NULL;
unsigned long size = 512 * 1024 * 1024;	// 512[MB]
data = (unsigned char*)VirtualAlloc( (LPVOID)NULL, (DWORD)size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE );
// dataの処理
VirtualFree( (LPVOID)data, (DWORD)0, MEM_RELEASE );
data = (unsigned char*)NULL;

本来の使い方

本来はVirtualAlloc()はMEM_RESERVEMEM_COMMITの2段階でメモリを確保する。

簡単な例を本来の使い方で記述すると

LPVOID memblock = (LPVOID)NULL;
unsigned char* data = (unsigned char*)NULL;
unsigned long size = 512 * 1024 * 1024;	// 512[MB]
memblock = VirtualAlloc( (LPVOID)NULL, (DWORD)size, MEM_RESERVE, PAGE_READWRITE );
data = (unsigned char*)VirtualAlloc( memblock, (DWORD)size, MEM_COMMIT, PAGE_READWRITE );
// dataの処理
VirtualFree( (LPVOID)data, (DWORD)size, MEM_DECOMMIT );
VirtualFree( memblock, (DWORD)0, MEM_RELEASE );
data = (unsigned char*)NULL;

のようになる。

この例に基づきMEM_RESERVEとMEM_COMMITの2段階のメモリ確保について説明する。

VirtualAlloc()のプロトタイプは以下の通り。

LPVOID VirtualAlloc (LPVOID lpAddress, DWORD dwSize,
                     DWORD flAllocationType,
                     DWORD flProtect);

以後はこの引数名を使用して記述する。

最初に以下の引数でVirtualAlloc()をコールしてメモリの予約を行う。

  • lpAddress=NULL
  • flAllocationType=MEM_RESERVE

この段階ではメモリの領域の予約のみで、戻り値のアドレスは(malloc()のアドレス戻り値のように)使用できない。

次に以下の引数でVirtualAlloc()をコールしてメモリの確保を行う。

  • lpAddress=最初のVirtualAlloc()のアドレス戻り値
  • flAllocationType=MEM_COMMIT

これで予約した領域の中からメモリを確保して、使用できるようにしている。

この操作をコミットというらしい。

簡単な例ではMEM_RESERVEMEM_COMMITの両方を同時に行っている。

MEM_COMMIT時にlpAddress=NULLとすると別の新たな領域を予約してコミットされる。

メモリの解放を行っているVirtualFree()についても同様に2段階で解放している。

MEM_DECOMMITで割り当てたメモリを解放し、MEM_RELEASEで予約した領域を開放している。

VirtualFree()のプロトタイプは以下の通り。

BOOL VirtualFree(
  LPVOID lpAddress,   // 領域のアドレス
  SIZE_T dwSize,      // 領域のサイズ
  DWORD dwFreeType    // 操作タイプ
);

MEM_DECOMMITではdwSizeにMEM_COMMITで確保したときのメモリサイズを渡す必要がある。

MEM_RELEASEではdwSizeに0を渡す必要がある。

本当の本来の使い方

本当の本来はMEM_RESERVEで大きく予約したメモリをMEM_COMMITで複数に分けて確保する。

分割して確保する場合は以下のようになる。

memblock = VirtualAlloc( (LPVOID)NULL, (DWORD)( size * 2 ), MEM_RESERVE, PAGE_READWRITE );
data1 = (unsigned char*)VirtualAlloc( memblock,        (DWORD)size, MEM_COMMIT, PAGE_READWRITE );
data2 = (unsigned char*)VirtualAlloc( memblock + size, (DWORD)size, MEM_COMMIT, PAGE_READWRITE );
VirtualFree( (LPVOID)data1, (DWORD)size, MEM_DECOMMIT );
VirtualFree( (LPVOID)data2, (DWORD)size, MEM_DECOMMIT );
VirtualFree( memblock, (DWORD)0, MEM_RELEASE );

2回目以降のVirtualAlloc()のlpAddressには直前に確保したメモリの次のアドレスを渡す必要がある。

当然、解放したメモリのアドレスでも問題はないが、サイズが解放前より大きくなって、次の領域に被った場合はどうなるんだろう…。

data1 = (unsigned char*)VirtualAlloc( memblock,        (DWORD)size, MEM_COMMIT, PAGE_READWRITE );
data2 = (unsigned char*)VirtualAlloc( memblock + size, (DWORD)size, MEM_COMMIT, PAGE_READWRITE );
VirtualFree( (LPVOID)data1, (DWORD)size, MEM_DECOMMIT );
data1 = (unsigned char*)VirtualAlloc( memblock,        (DWORD)( size + 10 ), MEM_COMMIT, PAGE_READWRITE );

失敗例

MSDNに以下のような失敗例が載っていました。

そのまま抜粋しておきます。


使用可能な RAM を VirtualAlloc によって割り当てるには、最初の呼び出しでメモリ空間を予約し、2 回目の呼び出しで物理 RAM をコミットするために VirtualAlloc を 2 回呼び出す方法と、flAllocationType パラメータに MEM_RESERVE フラグと MEM_COMMIT フラグの両方を指定して VirtualAlloc を 1 回だけ呼び出す方法があります。

予約フラグとコミット フラグを合わせて使用すると、短いコードで迅速かつ簡単にメモリを割り当てることができます。この技法は Windows XP アプリケーションでよく使用されますが、Windows CE アプリケーションでは好ましい技法ではありません。次のコードは、この問題を表しています。

INT i;
PVOID pMem[512];
for ( i = 0; i < 512; i++ ) {
   pMem[i] = VirtualAlloc( 0, PAGE_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE );
}

このコードは問題がないように見えます。このコードは、各ブロックのサイズを 1 ページとして、512 個のメモリ ブロックを割り当てます。問題は、Windows CE システムでこのコードを実行すると、RAM に数 MB の空きがあるシステムの場合でも常に失敗することです。これは Win32 オペレーティング システムでのメモリ領域の予約方法の問題です。

Windows CE .NET を含むすべての Win32 オペレーティング システムでは、仮想メモリ空間の領域を予約する場合に、64 KB 境界に予約領域を配置します。したがって、上記のコードは、それぞれが 64 KB 境界に配置された 512 個の領域の予約を試みます。Windows CE アプリケーションで問題となるのは、32 MB の仮想メモリ空間の範囲内に予約領域を配置しなければならないことです。アプリケーションのメモリ空間全体のうち、この仮想空間には 512 個の 64 KB 境界しかありませんが、アプリケーションのメモリ空間の一部は、アプリケーション コード、ローカル ヒープ、アプリケーションが読み込む DLL の領域として必要になります。通常、上記のコードは、VirtualAlloc をおよそ 470 回呼び出した後に失敗します。

この問題の解決策は、すべての割り当てに必要な領域を最初に予約し、後から必要に応じて RAM をコミットすることです。次のコードは、この解決策を表しています。

INT i;
PVOID pBase, pMem[512];
pBase = VirtualAlloc (0, 512*PAGE_SIZE, MEM_RESERVE, PAGE_READWRITE);
for (i = 0; i < 512; i++) {
   pMem[i] = VirtualAlloc (pBase + (i * PAGE_SIZE), PAGE_SIZE,
                           MEM_COMMIT, PAGE_READWRITE);
}

この問題について理解しておくと、問題を避ける手掛かりになります。512 個の領域に制限された Windows CE アプリケーションのアドレス空間の問題は、アプリケーションのさまざまな部分に影響を及ぼします。ここで取り上げた問題は、ほんの一部にすぎません。



コメント


*1 例外的に一部のDLLは0x2000000~0x3FFFFFFまでのアドレス空間にロードできる
*2 Mobile 6では2GBらしい… [2009/04/02追記]
*3 DLLそのものをラージメモリ領域にロードすることはできない