Palm Programmer's Laboratory

トップ 差分 一覧 ソース 検索 ヘルプ RSS ログイン

Palm OS Programmer's Companion Volume I/5-3

← 2 節に戻る ↑5 章トップへ 4 節に進む →


5-3 メモリマネージャ

 
Palm OS のメモリマネージャは、不揮発性ストレージ、揮発性ストレージ、および ROM にある全てのメモリチャンクの位置とサイズを管理する責任を持っています。メモリマネージャはチャンクの新規アロケート、破棄、リサイズ、ロックおよびアンロック、断片化した場合の圧縮などのための API を提供します。Palm ハンドヘルドは RAM やプロセッサ資源に制限があるため、メモリマネージャは計算資源やメモリの使用に関して効率的になっています。

このセクションでは、Palm OS におけるメモリ管理に関する背景と、メモリマネージャ API の概要を提供します。以下のトピックがあります。

  • メモリマネージャの構造
  • メモリマネージャの使い方

 

メモリマネージャの構造

このセクションでは、メモリマネージャが使用する以下の構造について説明します。

  • ヒープの構造
  • チャンクの構造
  • ローカルIDの構造

 

ヒープの構造

IMPORTANT
ヒープの構造は将来変更される可能性があります。ヒープの操作には API を使用して下さい。

ヒープはヒープヘッダ、マスタポインタテーブル、およびヒープチャンク群で構成されています。

  • ヒープヘッダ

ヒープヘッダはヒープの先頭に配置されます。ヒープのサイズや各種のフラグを保持しており、メモリマネージャに情報 ―― 例えばそのヒープがROMベースか、など ―― を提供します。

  • マスタポインタテーブル

ヒープヘッダの次にはマスタポインタテーブルがあります。これはヒープ内の移動可能なチャンクを指す32ビットポインタを格納するのに使用されます。

メモリマネージャがヒープを詰めるためにチャンクを移動する場合、そのチャンクを指すマスタポインタテーブル内のポインタはチャンクの移動先に更新されます。アプリケーションが移動可能なチャンクの参照を追跡するために使用するハンドルは、チャンク自体ではなくマスタポインタテーブル内のアドレスを指しています。これにより、ハンドルはチャンクを移動した後でも有効であり続けます。OS は利用可能な連続領域がアロケートのリクエストに応えられるほど十分でない場合、ヒープを自動的に最適化します。

マスタポインタテーブルが一杯になると、新しいテーブルがアロケートされてそのオフセットが既存のテーブルの nextMstrPtrTable フィールドに保存されます。この方法により、マスタポインタテーブルをいくつでもつなげることができます。追加のマスタポインタテーブルは移動不可能なため、すぐ次の“ヒープチャンク”で説明しているガイドラインに従って、ヒープの最後方にアロケートされます。

  • ヒープチャンク

マスタポインタテーブルの次には、ヒープの実際のチャンクが配置されます。移動可能なチャンクは通常ヒープの先頭にアロケートされ、移動不可能なチャンクはヒープの後方にアロケートされます。移動不可能なチャンクはメモリマネージャによって再配置されることはありえないため、マスタポインタテーブルにエントリを追加する必要はありません。

チャンクヘッダにはチャンクのサイズが含まれているため、アプリケーションはチャンクからチャンクへ簡単に移動できます。全ての移動可能および移動不可能なチャンクは、この方法でチャンクヘッダのフラグをチェックすることで見つけることができます。

 
ヒープは ROM ベースでもありえるため、ヒープの使用において変更されうるような情報はヘッダには含まれません。また、ROM ベースのヒープには移動不可能なチャンクしか含まれず、マスタポインタテーブルのエントリ数は 0 になります。

 

チャンクの構造

IMPORTANT
チャンクの構造は将来変更される可能性があります。チャンクの操作には API を使用して下さい。

それぞれのチャンクは 8 バイトのヘッダ情報で始まり、チャンクのデータがそれに続きます。チャンクヘッダは Flags:size 調整バイト、3 バイトのサイズ情報、lock:owner バイト、3 バイトの hOffset 情報で構成されています。

訳注:以下に登場するニブル( nibble )という用語は、1バイトの
   半分( すなわち 4 ビット )を意味しています。
  • Flags:sizeAdj バイト

このバイトは、上位ニブルにフラグがあり、下位ニブルにはサイズ調整情報があります。フラグのニブルは現在1ビットだけが定義されています。そのビットは移動可能なチャンクの場合にセットされます。サイズ調整ニブルは与えられた実際のサイズからチャンクの要求サイズを計算するのに使用されます。要求サイズはチャンクヘッダのサイズ情報からヘッダのサイズとサイズ調整フィールドの値を差し引くことで求められます。チャンクは常にワード境界から始まるため、チャンクの実際のサイズは常に2の倍数になります。

  • サイズフィールド(3バイト)

この3バイト値は、チャンクヘッダ自身のサイズを含む、アプリケーションから要求されたサイズよりも大きなチャンクサイズを格納しています。チャンクの最大データサイズは、64 KB 弱です。

  • Lock:owner バイト

サイズ情報の次のバイトは、上位ニブルにロックカウント、下位ニブルにオーナーIDを格納するバイトです。ロックカウントはチャンクがロックされるたびにインクリメントされ、アンロックされるたびにデクリメントされます。移動可能なチャンクは、アンロックせずに最大で 14 回ロックできます。移動不可能なチャンクでは、ロックフィールドには常に 15 が格納されています。オーナーIDはメモリチャンクの所有者を決めるためのもので、アロケート時にメモリマネージャによって設定されます。オーナーID情報はデバッグやアプリケーションが異常終了した場合のガベージコレクションに有用です。

  • hOffset フィールド(3バイト)

チャンクヘッダの最後の3バイトには、そのチャンクのマスタポインタからチャンクヘッダまでの距離を2で割った値が設定されます。マスタポインタテーブルがチャンク自身よりも大きなアドレスにある場合、このオフセットが負の値になり得ることに注意して下さい。移動不可能なチャンクはマスタポインタテーブルにエントリが必要ないため、このフィールドは0になります。

 

ローカル ID の構造

IMPORTANT
ローカルIDの構造は将来変更される可能性があります。チャンクを操作する場合はAPIを使用して下さい。

データベースレコードやその他のデータベース情報を含むチャンクは、データマネージャによってローカルID経由で管理されています。ローカルIDはカードに関連付けられており、カードがどのメモリスロットに入っていても有効です。ローカル ID はカードのベースアドレスがわかってしまえば簡単にポインタあるいはハンドルに変換することができます。

ローカル ID の上位 31 ビットは、カードの先頭からチャンクまたはマスタポインタまでのオフセットです。最下位ビットは、それがハンドルのローカルIDであればセットされ、ポインタのローカルIDであればクリアされます。

MemLocalIDToGlobal 関数は、ローカル ID とカード番号( 0 か 1 のいずれか )をポインタまたはハンドルに変換します。この関数はローカルIDをそのカード上のポインタまたはハンドルに変換するために、カード番号を見てカードのベースアドレスを加算します。

 

メモリマネージャの使用

ダイナミックヒープに(動的確保、スタック、大域変数などのための)メモリをアロケートする場合はメモリマネージャAPIを使用し、ストレージヒープに(ユーザーデータ用の)メモリをアロケートする場合はデータマネージャAPIを使用します。データマネージャは低水準のアロケーションを実行するために必要に応じてメモリマネージャをコールします(詳細は 「 6-1 データマネージャ 」 を参照して下さい)。

 

メモリマネージャAPIの概要

移動可能なチャンクをアロケートする場合、MemHandleNew をコールして必要なチャンクのサイズを渡します。このチャンクのデータを読み書きする前に MemHandleLock をコールし、ハンドルをロックしてポインタを取得する必要があります。チャンクをロックするたびにロックカウントがインクリメントされます。エラーが返されるまでに、最大で14回チャンクをロックできます(移動不可能なチャンクのロックフィールドは15固定になっていることを思い出して下さい)。MemHandleUnlock は MemHandleLock と逆の効果を持ちます。つまり、ロックフィールドの値から1を減じます。ロックカウントが 0 まで減らされると、チャンクはメモリマネージャによって移動することが可能になります。

アプリケーションがダイナミックヒープからメモリをアロケートする場合、メモリマネージャはオーナー ID をアプリケーションと関連付けるために使用します。システムはさらに、オーナー ID 情報のスペシャルビットをセットすることで、そのチャンクが現在アクティブなアロケーションかどうかを識別します。アプリケーションが終了すると、ダイナミックヒープ内でこのビットがセットされた全てのチャンクは自動的に開放されます。

アプリケーションが終了しても破棄されないチャンクをシステムがアロケートしたい場合、システムは MemHandleSetOwner 関数または MemPtrSetOwner 関数をコールしてオーナー ID に 0 をセットします。特殊な状況を除き、これらの関数は通常アプリケーションからは使用しません。例えば、カレントアプリケーションが SysUIAppSwitch で起動する新しいアプリケーションにパラメータブロックを渡す場合、そのブロックのオーナーはシステムになっていなければなりません。さもないと、カレントアプリケーションが終了する際、システムがそのアプリケーションのアロケートしたメモリを開放した時点でブロックは削除されてしまいます。

IMPORTANT
MemPtrSetOwner() または MemHandleSetOwner() を呼び出してメモリチャンクの所有者をシステムに変更したら、それを自分で開放しないで下さい。オペレーティングシステムは SysUIAppSwitch() または SysAppLaunch() で起動されたアプリケーションが終了すると、そのチャンクを開放します。

移動可能なチャンクのサイズを取得するには、そのハンドルを MemHandleSize 関数に渡します。サイズを変更するには、MemHandleResize 関数をコールします。チャンクのすぐ後ろに空き領域がない限り、一般的にロックされたチャンクのサイズを増やすことはできません。チャンクがロックされていない場合、メモリマネージャはサイズを増やすためにヒープを別の場所に移動することができます。チャンクが不要になったら、MemHandleFree をコールします。この関数はチャンクがロックされていても開放します。

ロックされている移動可能なチャンクのポインタがあれば、MemPtrRecoverHandle をコールすることでそのハンドルを復元することができます。実際には、MemPtrSize 関数を含む全ての MemPtr〜 関数はロックされた移動可能なチャンクのポインタを操作しています。

移動不可能なチャンクをアロケートする場合、MemPtrNew をコールして必要なサイズを渡します。この関数は直接読み書きが可能なチャンクのポインタを返します。

NOTE
サイズが 0 バイトのチャンクをアロケートすることはできません。

移動不可能なチャンクのサイズを取得するには、MemPtrSize 関数をコールします。サイズを変更するには、MemPtrResize をコールします。チャンクのすぐ後ろに空き領域がない限り、一般的に移動不可能なチャンクのサイズを増やすことはできません。チャンクが不要になったら、MemPtrFree をコールします。この関数はチャンクがロックされていても開放します。

メモリをある場所から他へ移動させたり、メモリを特定の値で塗り潰したい場合、メモリマネージャのユーティリティ関数である MemMove や MemSet を使用します。

ほとんどの場合、メモリを開放する正しい方法は MemPtrFree 関数か MemHandleFree 関数をコールすることです。

NOTE
Palm ハンドヘルドにおける正しいメモリ使用に関する重要な注意事項と実用的なアドバイスについては、「 1 小箱の中の Palm OS プログラミング 」 の “頑丈なコードを書くこと”を参照してください。

 

ストレージヒープのサイズとメモリ管理の仕組み

Palm OS のバージョン 1.0 では、個別のストレージヒープの上限サイズは 64KB に制限され、メモリマネージャがストレージヒープ間でオブジェクトを自動的に移動して、ストレージヒープのいずれかが満杯にならないようにしていました。この方式は大きなオブジェクトに使用できるような連続領域を減少させてしまうという傾向がありました。バージョン 2.0 のメモリマネージャはこのアプローチを捨て、ヒープの連続領域を維持するようになりましたが、個々のヒープの最大サイズはまだ 64 KB に制限されていました。Palm OS バージョン 3.0 ではヒープの 64 KB サイズ制限が廃止され、2つのヒープが作成されるようになりました。1つは 96KB のダイナミックヒープで、もう1つはカードの残り RAM サイズに等しいストレージヒープです。

Palm OS 3.5 からは、ダイナミックヒープのサイズは以下に示すように、システムが利用可能なメモリ量を基準として決められるようになりました。

デバイスのメモリサイズ ヒープのサイズ
2 MB 未満 64 KB
2 MB 以上 128 KB
4 MB 以上 256 KB

 

パフォーマンスの最適化

Palmハンドヘルドはヒープ領域やストレージに制限があるため、最適化が非常に重要になります。アプリケーションをできるだけ速く効率的にするために、ヒープの使用を第一に最適化し、2番目に速度を、最後にコードサイズを最適化します。

メモリの使用を最適化するために、以下のガイドラインに従って下さい。

  • ヒープの断片化を防ぐために、ハンドルをアロケートします。つまり、メモリの確保には可能な限り MemPtrNew ではなく MemHandleNew を使用します。
  • ソートを必要に応じて行ない、異なるソート状態のリストを保有しないようにします。これによりプログラムはよりシンプルになり、少ないストレージ消費で済むようになります。
  • ダイナミックメモリは潜在的なボトルネックになります。大きな構造体をスタック上に置かないで下さい。
  • 32K を越える関数呼び出しのジャンプを避けるようにサブルーチンを構成します。Palm OS アプリケーションは RAM に制約のある環境で動作しなければならないため、proper code segmentation は最高のパフォーマンスを実現することが重要になります。もしアプリケーションのセグメントが大き過ぎると、必要な大きさの連続するメモリ領域が利用できない場合にアプリケーションがうまく(あるいはまったく)動作しないかもしれません。逆に、アプリケーションのセグメントが小さ過ぎると、頻繁にリソースを探してロードするのに必要なオーバーヘッドがパフォーマンスを圧迫する可能性があります。残念ながら、全てのアプリケーションが高パフォーマンスで動作できるようなメモリチャンクのサイズを1つに決めることは不可能です。アプリケーションの応答性能や全体的なパフォーマンスを最適化するようなリソースチャンクのサイズと配置を発見するために、パフォーマンスを計測しながらいろいろな方法でセグメントを分割してみる必要があるでしょう。Palm OS デバッガや Metrowerks CodeWarrior デバッガ、および Palm OS シミュレータは、ヒープの内部構造体の検査や利用可能な空き領域の確認、メモリブロックの操作などを行うツールを提供します。
  • ダイナミックヒープの制限が厳しい環境でもアプリケーションが動作するように、以下のガイドラインに従って下さい。
    • 可能ならば、大域変数を使うかわりにメモリチャンクをアロケートして下さい。
    • ダイアログを積み重ねるように表示するのではなく、UI フォームを切り替えるようにします。そのためには、フォームの切り替えには FrmGotoForm を使用し、モーダルダイアログの切り替えには FrmDoDialog を使うようにします。FrmPopupForm の使用は避けて下さい。
    • データベースのレコードはインプレイスで編集し、ダイナミックヒープ上にコピーを作成しないようにします。
  • 大量のデータをスタック上に配置しないで下さい。ヒープの汚染はデバッグが困難です。大域変数はローカル変数より望ましいですが、チャンクは大域変数よりも望ましいのです。アプリケーションのスタック領域には制限があり、その量はシステムソフトウェアのバージョンに依存します。

 


← 2 節に戻る ↑5 章トップへ 4 節に進む →