この章では、アプリケーション開発者から見た Solaris オペレーティングシステムの仮想メモリーと CPU の管理について説明します。
「メモリー管理インタフェース」では、インタフェースとキャッシュ制御について説明します。
「ライブラリレベルの動的メモリー」では、ライブラリレベルの動的メモリーの割り当てとデバッグについて説明します。
「その他のメモリー制御インタフェース」では、その他のメモリー制御インタフェースについて説明します。
「CPU パフォーマンスカウンタ」では、CPU 性能カウンタ (CPC) の使用方法について説明します。
仮想メモリー機能を使用するとき、アプリケーションはいくつかのインタフェースを使用します。この節では、このようなインタフェースの要約について説明します。この節ではまた、このようなインタフェースの使用例も示します。
mmap(2) は、名前付きファイルシステムオブジェクトのプロセスアドレス空間へのマッピングを確立します。名前付きファイルシステムオブジェクトは部分的にもプロセスアドレス空間にマッピングできます。この基本的なメモリー管理インタフェースはとても簡潔です。open(2) を使用してファイルを開いてから、mmap(2) を使用して適切なアクセスオプションと共有オプションを持つマッピングを作成します。そのあと、ユーザーのアプリケーションを処理します。
mmap(2) でマッピングを確立すると、指定されたアドレス範囲にあった以前のマッピングは置き換えられます。
MAP_SHARED フラグと MAP_PRIVATE フラグはマッピングのタイプを指定します。これらのフラグはどちらか 1 つを指定する必要があります。MAP_SHARED を設定すると、書き込みが行われたときに、マッピングされたオブジェクトが変更されます。オブジェクトを変更するとき、これ以外の操作は必要ありません。MAP_PRIVATE を設定すると、マッピングされた領域に最初に書き込みが行われた時に、ページのコピーが作成されます。以降の書き込みではコピーが参照されます。コピーが作成されるのは、変更されたページだけです。
fork(2) を行なっても、マッピングのタイプは保持されます。
mmap(2) でマッピングを確立したあと、呼び出しで使用されたファイル記述子は二度と使用されません。ファイルを閉じても、munmap(2) でマッピングを取り消すまで、マッピングは有効です。新しいマッピングを作成すると、既存のマッピングは失われます。
切り捨ての呼び出しを行うと、マッピングされたファイルが短くなることがあります。(短くなって) 失われた領域にアクセスしようとすると、SIGBUS シグナルが発生します。
/dev/zero をマッピングすると、0 で初期化された仮想メモリーブロックが呼び出し元プログラムに提供されます。ブロックのサイズは、mmap(2) への呼び出しに指定します。次のコードは、このテクニックを使用して、0 で初期化された記憶領域のブロックをプログラム内に作成する例を示しています。このブロックのアドレスはシステムが選択します。
removed to fr.ch4/pl1.create.mapping.c
デバイスまたはファイルの中には、マッピングによってアクセスされるときだけ使用できるものもあります。たとえば、ビットマップ形式のディスプレイをサポートするときに使用するフレームバッファーデバイスなどです。ディスプレイのアドレスを直接操作する場合、ディスプレイ管理アルゴリズムはより簡単に実装できます。
munmap(2) は、呼び出し元プロセスの指定されたアドレス範囲にあるページのマッピングをすべて削除します。munmap(2) は、マッピングされていたオブジェクトにはまったく影響しません。
SunOS の仮想メモリーシステムは、プロセッサのメモリーがファイルシステムオブジェクトのデータをバッファリングするキャッシュシステムです。キャッシュの状態を制御または調査するために、次のようなインタフェースが提供されています。
mincore(2) インタフェースは、指定された範囲内のマッピングが示すアドレス空間にメモリーページが存在するかどうかを判定します。mincore がページをチェックしてからデータを返すまでの間にページの状態が変わっている可能性もあるので、mincore が返す情報は最新の状態を示していない場合があります。メモリーに残っていると保証されるのは、ロックされたページだけです。
mlock(3C) は、指定されたアドレス範囲内にあるページを物理メモリーにロックします。当該プロセスまたはほかのプロセスでロックされたページを参照しても、入出力操作が必要になるページフォルトは発生しません。このような入出力操作は仮想メモリーの通常の動作を妨害し、ほかのプロセスを遅くするので、mlock の使用はスーパーユーザーだけに制限されます。メモリーにロックできるページ数の制限はシステム構成によって異なります。この制限を超えると、mlock の呼び出しは失敗します。
munlock は、物理ページ上にロックされたページを解放します。1 つのマッピングのアドレス範囲で複数の mlock 呼び出しを行なっている場合も 1 回の munlock でロックを解放できます。ただし、mlock で同じページを異なるマッピングで処理した場合、このページのロックを解除するには、すべてのマッピングを解放する必要があります。
マッピングを削除することによってもロックを解放できます。つまり、mmap(2) でマッピングを置き換えるか、munmap(2) でマッピングを削除することで可能です。
前述の MAP_PRIVATE マッピングに関連する書き込み時コピーイベントは、コピー元ページからコピー先ページにロックを転送します。したがって、書き込み時コピー先を変更しても、MAP_PRIVATE マッピングを含むアドレス範囲上のロックは透過的に保持されます。この変更については、「マッピングの作成と使用」を参照してください。
mlockall(3C) と munlockall(3C) は mlock と munlock に似ていますが、mlockall と munlockall はアドレス空間全体に対して動作します。mlockall はアドレス空間にあるすべてのページにロックを設定し、munlockall はアドレス空間にある (mlock または mlockall で確立された) すべてのページのロックを解除します。
msync(3C) は、指定されたアドレス範囲内にある変更されたページのすべてを、これらのアドレスでマッピングされているオブジェクトにフラッシュ (実際に書き込み) します。このコマンドは fsync(3C) に似ていますが、こちらはファイルに対して動作します。
ライブラリレベルの動的メモリー割り当ては、動的メモリー割り当てに使いやすいインタフェースを提供します。
もっともよく使用されるインタフェースは次のとおりです。
その他の動的メモリー割り当てインタフェースは、memalign(3C)、valloc(3C)、および realloc(3C) です。
malloc は、最低でも要求されたメモリー量のメモリーブロックへのポインタを返します。この (メモリー) ブロックは、任意のタイプのデータを格納できるように境界整列されます。
free は、malloc、calloc、 realloc、memalign、または valloc で取得したメモリーをシステムメモリーに返します。動的メモリー割り当てインタフェースによる予約をしていないブロックを解放しようとするとエラーが発生し、プロセスがクラッシュする可能性があります。
calloc は、0 で初期化されたメモリーブロックへのポインタを返します。calloc で予約されたメモリーをシステムに返すには、watchmalloc、free いずれでも可能です。このメモリー (ブロック) は、指定されたサイズの要素数からなる配列を格納できるように割り当ておよび境界整列されます。
realloc は、プロセスに割り当てられているメモリーブロックのサイズを変更します。realloc は、割り当てられているメモリーブロックのサイズを増減するのに使用できます。realloc は、問題を起こさずにメモリー割り当てを減らすことができる唯一の方法です。再割り当てされたメモリーブロックの位置は変更される可能性がありますが、その内容はメモリー割り当てのサイズが変更されるまで変更されません。
Sun WorkShop ツールパッケージを使用すると、動的メモリーの使用中に発生するエラーを発見して削除することができます。Sun WorkShop の Run Time Checking (RTC) 機能は、動的メモリーの使用中に発生するエラーを発見します。
-g オプションを付けてプログラムをコンパイルしなくても、RTC はすべてのエラーを発見できます。しかし、特に初期化されていないメモリーから読み取る場合、エラーの正確性を保証するために、(-g で入手できる) シンボリック情報が必要になることもあります。したがって、シンボリック情報が入手できないと、ある種のエラーは抑制されます。このようなエラーには、a.out の rui や共有ライブラリの rui + aib + air があります。この動作を変更するには、suppress と unsuppress を使用します。
-access オプションは、アクセス権のチェックをオンにします。RTC は次のようなエラーを報告します。
不正な解放
重複する解放
整列されていない解放
整列されていない読み取り
整列されていない書き込み
メモリー不足
割り当てられていないメモリーからの読み取り
初期化されていないメモリーからの読み取り
読み取り専用メモリーへの書き込み
割り当てられていないメモリーへの書き込み
デフォルトの動作は、アクセス権エラーを発見するたびにプロセスを停止します。この動作を変更するには、rtc_auto_continue dbxenv 変数を使用します。on に設定した場合、RTC はアクセス権エラーをファイルに記録します。このファイル名は rtc_error_log_file_name dbxenv 変数の値で決定されます。デフォルトでは、一意なアクセス権エラーごとにエラーが発生した最初の時刻だけが報告されますが、この動作は、rtc_auto_suppress dbxenv 変数を使用して変更できます。この変数のデフォルト設定は on です。
-leaks オプションは、リークのチェックをオンにします。RTC は次のようなエラーを報告します。
メモリーリークの可能性 – 唯一のポインタがブロックの真ん中を指しています。
メモリーリークの可能性 – ブロックへのポインタがレジスタだけに存在します。
メモリーリーク – ブロックへのポインタが存在しません。
リークのチェックをオンにした場合、プログラムが終了したとき、リークレポートが自動的に報告されます。このとき、潜在的なリークを含むすべてのリークが報告されます。デフォルトでは、簡易レポートが生成されます。このデフォルトは dbxenv rtc_mel_at_exit を使用すると変更できます。リークレポートはいつでも要求できます。
-frames n 変数を使用した場合、リークが報告されるとき、n 個までのスタックフレームが個別に表示されます。-match m 変数を使用した場合、リークは結合されて表示されます。複数のリークが発生した割り当て時に、呼び出しスタックが m 個のフレームに一致した場合、これらのリークは結合されて、単一のリークレポートとして報告されます。n のデフォルト値は 8 または m の大きい方ですが、n の最大値は 16 です。m のデフォルト値は 2 です。
-memuse オプションはメモリー (ブロック) の使用状況のチェック (memuse) をオンにします。check -memuse を使用すると、check -leaks も自動的に使用されます。つまり、プログラムが終了したとき、リークレポートに加えて、(メモリー) ブロック使用状況レポート (biu) が報告されます。デフォルトでは、簡易 (メモリー) ブロック使用状況レポートが生成されます。このデフォルトは、dbxenv rtc_biu_at_exit によって制御されます。プログラムの実行中はいつでも、プログラム内のメモリーがどこに割り当てられているかを参照できます。
次の節では、-frames n と -match m 変数の機能について説明します。
check -access; check -memuse [-frames n] [-match m] と同じです。rtc_biu_at_exit dbxenv 変数の値は check -all では変更されません。そのため、デフォルトでは、プログラムが終了したとき、メモリー (ブロック) 使用状況レポートは作成されません。
check -all; suppress all; unsuppress all in funcs files loadobjects と同じです。このオプションを使用すると、気になる場所に RTC を集中させることができます。
この節では、その他のメモリー制御インタフェースについて説明します。
sysconf(3C) は、メモリーページのシステム依存サイズを返します。移植性のため、アプリケーションはページのサイズを指定する定数を埋め込まないでください。同じ命令セットの実装においても、ページのサイズが異なることは特に珍しいことではありません。
mprotect(2) は、指定されたアドレス範囲内にあるすべてのページに、指定された保護を割り当てます。保護は、配下のオブジェクトに許可されたアクセス権を超えることはできません。
break は、スタック内には存在しないプロセスイメージにおいて最大の有効なデータアドレスです。プログラムが実行を開始するとき、ブレーク値は通常、execve(2) によって、プログラムとそのデータ記憶領域によって定義される最大のアドレスに設定されます。
brk(2) を使用すると、さらに大きなアドレスにブレークを設定できます。また、sbrk(2) を使用すると、プロセスのデータセグメントに記憶領域の増分を追加できます。getrlimit(2) の呼び出しを使用すると、データセグメントの取得可能な最大サイズを取得できます。
caddr_t brk(caddr_t addr); caddr_t sbrk(intptr_t incr);
brk は、呼び出し元プログラムが使用していないデータセグメントの最低の位置を addr に設定します。この位置は、システムページサイズの次の倍数に切り上げられます。
sbrk (代替のインタフェース) は、呼び出し元プログラムのデータ空間に incr バイトを追加して、新しいデータ領域の開始場所へのポインタを返します。
この節では、CPU 性能カウンタ (CPC) を使用するための開発者インタフェースについて説明します。Solaris アプリケーションは、配下のカウンタアーキテクチャーに依存しない CPC を使用できます。
この節では、libcpc(3LIB) ライブラリに最近追加された API について説明します。以前のインタフェースについては、マニュアルページの libcpc を参照してください。
アプリケーションが CPC 機能を使用できるように準備するには、cpc_open() 関数を呼び出してライブラリを初期化します。この関数は、ほかのインタフェースで使用される cpc_t * パラメータを返します。cpc_open() 関数の構文は、次のとおりです。
cpc_t*cpc_open(intver);ver パラメータの値は、アプリケーションが使用中のインタフェースのバージョンを識別します。配下のカウンタがアクセス不可または使用不可の場合、cpc_open() 関数は失敗します。
uint_t cpc_npic(cpc_t *cpc); uint_t cpc_caps(cpc_t *cpc); void cpc_walk_events_all(cpc_t *cpc, void *arg, void (*action)(void *arg, const char *event)); void cpc_walk_events_pic(cpc_t *cpc, uint_t picno, void *arg, void(*action)(void *arg, uint_t picno, const char *event)); void cpc_walk-attrs(cpc_t *cpc, void *arg, void (*action)(void *arg, const char *attr));
cpc_npic() 関数は、配下のプロセッサ上の物理カウンタ数を返します。
cpc_caps() 関数は、uint_t パラメータを返します。そのパラメータ値は、配下のプロセッサがサポートする機能に対して実行されたビット単位の論理和操作の結果です。この関数には 2 つの機能があります。CPC_CAP_OVERFLOW_INTERRUPT 機能により、カウンタのオーバーフロー時に、プロセッサは割り込みを発生できます。CPC_CAP_OVERFLOW_PRECISE 機能により、プロセッサはどのカウンタがオーバーフローの割り込みを発生したかを判別できます。
カーネルは、配下のプロセッサがサポートするイベントのリストを管理します。単一チップ上の異なる物理カウンタが同じイベントのリストを使用する必要はありません。cpc_walk_events_all() 関数は、物理カウンタに関係なく、プロセッサがサポートするイベントごとにaction() ルーチンを呼び出します。cpc_walk_events_pic() 関数は、特定の物理カウンタで、プロセッサがサポートするイベントごとにaction() ルーチンを呼び出します。どちらの関数も、 arg パラメータを非解釈で呼び出し元から各 action() 関数の呼び出しに渡します。
プラットフォームは、配下のプロセッサがサポートする属性のリストを管理します。これらの属性によって、性能カウンタのプロセッサ固有の拡張機能にアクセスできます。cpc_walk_attrs() 関数は、属性名ごとにアクションルーチンを呼び出します。
cpc_set_t *cpc_set_create(cpc_t *cpc); int cpc_set_destroy(cpc_t *cpc, cpc_set_t *set); int cpc_set_add_request(cpc_t *cpc, cpc_set_t *set, const char *event, uint64_t preset, uint_t flags, uint_t nattrs, const cpc_attr_t *attrs); int cpc_set_request_preset(cpc_t *cpc, cpc_set_t *set, int index, uint64_t preset);
不透明なデータ型 cpc_set_t は要求のコレクションを表します。これらのコレクションはセットと呼ばれます。cpc_set_create() 関数は空のセットを作成します。cpc_set_destroy() 関数はセットを削除して、セットが使用していたメモリーをすべて解放します。セットを削除すると、そのセットが使用していたハードウェア資源が解放されます。
cpc_set_add_request() 関数は要求をセットに追加します。要求のパラメータは次のとおりです。
カウントのイベント名を指定する文字列。
カウンタの初期値に使用される 64 ビットの符号なし整数。
要求フラグのグループに適用される論理和操作の結果。
attrs が指す配列内の属性の数。
cpc_attr_t 構造体の配列へのポインタ。
有効な要求フラグは次のとおりです。
このフラグを使用すると、CPU がユーザーモードで実行している間に発生するイベントをカウントできます。
このフラグを使用すると、CPU が特権モードで実行している間に発生するイベントをカウントできます。
このフラグは、ハードウェアのカウンタオーバーフローの通知を要求します。
CPC インタフェースは、cpc_attr_t 構造体の配列として属性を渡します。
cpc_set_add_request() 関数が正常終了して戻った場合、この関数はインデックスを返します。インデックスは、cpc_set_add_request() 関数の呼び出しにより追加された要求が生成したデータを参照します。
cpc_set_request_preset() 関数は、事前に設定された要求の値を変更します。これによって、オーバーフローしたセットを新しい事前設定で再構築できます。
cpc_walk_requests() 関数は、ユーザーが提供した action() ルーチンを cpc_set_t 内の要求ごとに呼び出します。arg パラメータの値は、非解釈でユーザーのルーチンに渡されます。cpc_walk_requests() 関数を使用すると、アプリケーションはセット内の要求ごとに構成を出力できます。cpc_walk_requests() 関数の構文は、次のとおりです。
void cpc_walk_requests(cpc_t *cpc, cpc_set_t *set, void *arg, void (*action)(void *arg, int index, const char *event, uint64_t preset, uint_t flags, int nattrs, const cpc_attr_t *attrs));
この節のインタフェースは、セット内の要求を物理ハードウェアにバインドして、カウンタを開始位置に設定します。
int cpc_bind_curlwp(cpc_t *cpc, cpc_set_t *set, uint_t flags); int cpc_bind_pctx(cpc_t *cpc, pctx_t *pctx, id_t id, cpc_set_t *set, uint_t flags); int cpc_bind_cpu(cpc_t *cpc, processorid_t id, cpc_set_t *set, uint_t flags); int cpc_unbind(cpc_t *cpc, cpc_set_t *set);
cpc_bind_curlwp() 関数は、セットを呼び出し元の LWP にバインドします。セットのカウンタはこの LWP に仮想化され、呼び出し元の LWP の実行中に CPU で発生したイベントをカウントします。cpc_bind_curlwp() ルーチンで有効なフラグは CPC_BIND_LWP_INHERIT だけです。
cpc_bind_pctx() 関数は、セットを libpctx(3LIB) を使って得られたプロセス内の LWP にバインドします。この関数に有効なフラグはありません。
cpc_bind_cpu() 関数は、セットを id パラメータで指定されたプロセッサにバインドします。セットを CPU にバインドすると、システム上にある既存の性能カウンタのコンテキストが無効になります。この関数に有効なフラグはありません。
cpc_unbind() 関数は性能カウンタを停止して、バインドされたセットに関連付けられたハードウェアを解放します。セットが CPU にバインドされている場合、cpc_unbind() 関数は、CPU から LWP をバインド解除して、CPC 仮想デバイスを解放します。
この節で説明するインタフェースを使用すると、カウンタからアプリケーションにデータを返すことができます。カウンタデータは、cpc_buf_t という不透明なデータ構造体内にあります。このデータ構造体は、バインドされたセットが使用しているカウンタの状態についてのスナップショットを取得し、次の情報を含みます。
各カウンタの 64 ビットの値
最新のハードウェアスナップショットの時刻表示
バインドされたセットでプロセッサが使用した CPU サイクルの数をカウントする累積 CPU サイクルカウンタ
cpc_buf_t *cpc_buf_create(cpc_t *cpc, cpc_set_t *set); int cpc_buf_destroy(cpc_t *cpc, cpc_buf_t *buf); int cpc_set_sample(cpc_t *cpc, cpc_set_t *set, cpc_buf_t *buf);
cpc_buf_create() 関数は、 cpc_set_t で指定されたセットからデータを格納するバッファーを作成します。cpc_buf_destroy() 関数は、指定した cpc_buf_t に関連付けられたメモリーを解放します。cpc_buf_sample() 関数は、指定されたセットの代わりにカウントしているカウンタのスナップショットを取得します。cpc_buf_sample() 関数を呼び出す前に、指定されたセットはあらかじめバインドされ、バッファーが作成されている必要があります。
バッファーへの抽出では、そのセットに関連付けられた要求の事前設定を更新しません。cpc_buf_sample() 関数を使ってバッファーが抽出され、次にバインド解除してから再度バインドすると、cpc_set_add_request() 関数の元の呼び出し内の要求の事前設定からカウンタが開始します。
次のルーチンにより、cpc_buf_t 構造体内のデータにアクセスできます。
int cpc_buf_get(cpc_t *cpc, cpc_buf_t *buf, int index, uint64_t *val); int cpc_buf_set(cpc_t *cpc, cpc_buf_t *buf, int index, uint64_t *val); hrtime_t cpc_buf_hrtime(cpc_t *cpc, cpc_buf_t *buf); uint64_t cpc_buf_tick(cpc_t *cpc, cpc_buf_t *buf); int cpc_buf_sub(cpc_t *cpc, cpc_buf_t *result, cpc_buf_t *left cpc_buf_t *right); int cpc_buf_add(cpc_t *cpc, cpc_buf_t *result, cpc_buf_t *left, cpc_buf_t *right); int cpc_buf_copy(cpc_t *cpc, cpc_buf_t *dest, cpc_buf_t *src); void cpc_buf_zero(cpc_t *cpc, cpc_buf_t *buf);
cpc_buf_get() 関数は、index パラメータで特定したカウンタの値を取得します。index パラメータは、セットがバインドされる前に cpc_set_add_request() 関数で返された値です。cpc_buf_get() 関数は、val パラメータが示す位置のカウンタを格納します。
cpc_buf_set() 関数は、index パラメータで特定したカウンタの値を設定します。index パラメータは、セットがバインドされる前に cpc_set_add_request() 関数で返された値です。cpc_buf_set() 関数は、val パラメータが示す位置の値にカウンタの値を設定します。cpc_buf_get() 関数または cpc_buf_set() 関数のどちらも、対応する CPC 要求の事前設定を変更しません。
cpc_buf_hrtime() 関数は、いつハードウェアが抽出されたかを示す高精度な時刻表示を返します。cpc_buf_tick() 関数は、LWP の実行中に経過した CPU クロックサイクルの数を返します。
cpc_buf_sub() 関数は、left と right パラメータで指定されたカウンタとクロック刻み値の違いを計算します。cpc_buf_sub() 関数は、result に結果を格納します。cpc_buf_sub() 関数の呼び出しでは、cpc_buf_t 値がすべて同じ cpc_set_t 構造体から由来する必要があります。result インデックスは、バッファー内の要求インデックスごとの left - right の計算結果を含みます。また、結果のインデックスは tick の違いも含みます。cpc_buf_sub() 関数は、出力先バッファーについての高精度な時刻表示を、left または right バッファーの最新時刻に設定します。
cpc_buf_add() 関数は、left と right パラメータで指定されたカウンタとクロック刻み値の合計を計算します。cpc_buf_add() 関数は、result に結果を格納します。cpc_buf_add() 関数の呼び出しでは、cpc_buf_t 値がすべて同じ cpc_set_t 構造体から由来する必要があります。result インデックスは、バッファー内の要求インデックスごとの left + right の計算結果を含みます。また、結果のインデックスは tick の合計も含みます。cpc_buf_add() 関数は、の出力先バッファーについての高精度な時刻表示を、left または right バッファーの最新時刻に設定します。
cpc_buf_copy() 関数は、src と同じdest を作成します。
cpc_buf_zero() 関数は、buf 内のすべてを 0 に設定します。
この節では、CPC の起動インタフェースについて説明します。
int cpc_enable(cpc_t *cpc); int cpc_disable(cpc_t *cpc);
この 2 つのインタフェースはそれぞれ、既存の LWP にバインドされたセットのカウンタを有効および無効にします。これらのインタフェースを使用すると、libpctx を使ってカウンタ設定を制御プロセスに任せながら、アプリケーションは対象のコードを指定できます。
この節では、CPC のエラー処理インタフェースについて説明します。
typedef void (cpc_errhndlr_t)(const char *fn, int subcode, const char *fmt, va_list ap); void cpc_seterrhndlr(cpc_t *cpc, cpc_errhndlr_t *errhndlr);
この 2 つのインタフェースによって、cpc_t ハンドルを渡すことができます。cpc_errhndlr_t ハンドルには、文字列のほかに整数のサブコードも指定します。整数の subcode は、 fn 引数が参照する関数によって発生した特定のエラーを示します。整数の subcode により、アプリケーションはエラー状況を簡単に認識できます。fmt 引数の文字列は、エラーサブコードについての国際化された説明を含み、出力に適しています。